إنشاء طرق عرض مخصّصة

يُعد هذا الدرس التطبيقي جزءًا من الدورة التدريبية المتقدّمة لنظام التشغيل Android في لغة Kotlin. ستحصل على أقصى استفادة من هذه الدورة التدريبية إذا كنت تعمل من خلال الدروس التطبيقية حول الترميز بالتسلسل، ولكن هذا ليس إلزاميًا. يتم إدراج جميع الدروس التطبيقية حول ترميز الدورات التدريبية في الصفحة المقصودة لبرنامج Android المتقدّم في لغة ترميز Kotlin.

مقدمة

يقدّم نظام التشغيل Android مجموعة كبيرة من الفئات الفرعية لـ View، مثل Button أو TextView أو EditText أو ImageView أو CheckBox أو RadioButton. يمكنك استخدام هذه الفئات الفرعية لإنشاء واجهة مستخدم تتيح تفاعل المستخدم وتعرض المعلومات في تطبيقك. إذا لم تستوفِ أي من الفئات الفرعية View احتياجاتك، يمكنك إنشاء فئة فرعية من View تُعرف باسم طريقة عرض مخصّصة.

لإنشاء طريقة عرض مخصّصة، يمكنك توسيع فئة فرعية حالية على View (مثل Button أو EditText) أو إنشاء فئة فرعية خاصة بك من فئة View. من خلال تمديد View مباشرةً، يمكنك إنشاء عنصر واجهة مستخدم تفاعلي بأي حجم وأي شكل عن طريق إلغاء طريقة onDraw() لـ View لرسمه.

بعد إنشاء ملف شخصي مخصّص، يمكنك إضافته إلى تنسيقات نشاطك بالطريقة نفسها التي تضيف بها TextView أو Button.

يعرض لك هذا الدرس كيفية إنشاء عرض مخصص من البداية عن طريق تمديد View.

ما يجب معرفته

  • كيفية إنشاء تطبيق باستخدام نشاط وتشغيله باستخدام "استوديو Android"

ما ستتعرَّف عليه

  • كيفية تمديد View لإنشاء عرض مخصّص.
  • كيفية رسم عرض مخصّص دائري الشكل.
  • كيفية استخدام المستمعين للتعامل مع تفاعل المستخدم مع العرض المخصّص.
  • كيفية استخدام عرض مخصص في التنسيق.

الإجراءات التي ستنفذّها

  • توسيع View لإنشاء ملف شخصي مخصّص.
  • عليك إعداد العرض المخصّص باستخدام قيم الرسم والتلوين.
  • تجاوز onDraw() لرسم العرض.
  • استخدام المستمعين لتقديم سلوك العرض المخصص.
  • أضِف العرض المخصّص إلى تصميم.

يوضّح تطبيق CustomFanController كيفية إنشاء فئة فرعية مخصّصة للعرض عن طريق توسيع فئة View. يُطلق على الفئة الفرعية الجديدة اسم DialView.

يعرض التطبيق عنصر واجهة مستخدم دائريًا يشبه عنصر تحكّم في المروحة الفعلية، ويتضمّن إعدادات لإيقاف (0) ومنخفضة (1) ومتوسطة (2) وعالية (3). عندما ينقر المستخدم على العرض، ينتقل مؤشر الاختيار إلى الموضع التالي: 0-1-2-3، ثم يعود إلى 0. أيضًا، إذا كان الاختيار واحدًا أو أكثر، يتغيّر لون خلفية الجزء الدائري من العرض من اللون الرمادي إلى الأخضر (يشير إلى أن قوة المروحة قيد التشغيل).

الملفات الشخصية هي المكوّنات الأساسية لواجهة مستخدم التطبيق. توفر الفئة View العديد من الفئات الفرعية، المُشار إليها بأدوات واجهة المستخدم، التي تغطي العديد من احتياجات واجهة مستخدم تطبيق Android المعتادة.

تمثّل الوحدات الأساسية لإنشاء واجهة المستخدم، مثل Button وTextView، فئات فرعية توسّع فئة View. لتوفير الوقت والجهد المبذول في التطوير، يمكنك تمديد إحدى هذه الفئات الفرعية البالغ عددها View. ويكتسب العرض المخصّص شكل الأصل والسلوك، ويمكنك إلغاء سلوك أو مظهر معيّن تريد تغييره. على سبيل المثال، إذا قمت بتمديد EditText لإنشاء ملف شخصي مخصص، يعمل العرض بالطريقة نفسها التي يستخدمها ملف EditText الشخصي، ولكن يمكن تخصيصه أيضًا ليعرض، على سبيل المثال، زر X يمحو النص من حقل إدخال النص.

يمكنك توسيع أي فئة فرعية من View، مثل EditText، للحصول على طريقة عرض مخصّصة، لذلك يُرجى اختيار الفئة الأقرب إلى ما تريد تحقيقه. ويمكنك بعد ذلك استخدام طريقة العرض المخصّصة مثل أي فئة فرعية أخرى من View في تنسيق واحد أو أكثر كعنصر XML بسمات.

لإنشاء عرض مخصّص خاص بك من البداية، عليك توسيع فئة View نفسها. يلغي الرمز الخاص بك View طريقة لتحديد مظهر الملف الشخصي ووظائفه. يتمثل العامل الأساسي لإنشاء طريقة عرض مخصّصة لك في أنك مسؤول عن رسم عنصر واجهة المستخدم بالكامل من أي حجم وشكل على الشاشة. إذا استخدمت فئة فرعية حالية من الملفات الشخصية الحالية مثل Button، ستتعامل هذه الفئة مع الرسم نيابةً عنك. (ستتعرف على المزيد من المعلومات حول الرسم لاحقًا في هذا الدرس التطبيقي.)

لإنشاء طريقة عرض مخصّصة، اتّبع الخطوات العامة التالية:

  • أنشئ فئة عرض مخصّصة تمتد إلى View، أو توسّع فئة فرعية واحدة (View) (مثل Button أو EditText).
  • في حال توسيع فئة فرعية حالية على View، سيتم إلغاء السلوك أو جوانب المظهر التي تريد تغييرها فقط.
  • إذا مددت فئة View، ارسم شكل العرض المخصص وتحكم في مظهره عن طريق إلغاء طرق View مثل onDraw() وonMeasure() في الصف الجديد.
  • أضِف الرمز للرد على تفاعل المستخدم، وإذا لزم الأمر، أعِد رسم العرض المخصّص.
  • استخدم فئة العرض المخصص كأداة واجهة مستخدم في تنسيق XML لنشاطك. كما يمكنك تحديد سمات مخصصة للملف الشخصي، لتخصيص التخصيص في الملف الشخصي بتنسيقات مختلفة.

في هذه المهمة، يمكنك:

  • يمكنك إنشاء تطبيق باستخدام ImageView كعنصر نائب مؤقت للعرض المخصّص.
  • توسيع View لإنشاء عرض مخصّص.
  • عليك إعداد العرض المخصّص باستخدام قيم الرسم والتلوين.

الخطوة 1: إنشاء تطبيق باستخدام عنصر نائب في ImageView

  1. أنشئ تطبيق Kotlin بعنوان CustomFanController باستخدام نموذج النشاط الفارغ. تأكَّد من أن اسم الحزمة هو com.example.android.customfancontroller.
  2. افتح activity_main.xml في علامة التبويب النص لتعديل رمز XML.
  3. استبدل TextView الحالي بهذا الرمز. يعمل هذا النص كتصنيف في نشاط الملف الشخصي المخصص.
<TextView
       android:id="@+id/customViewLabel"
       android:textAppearance="@style/Base.TextAppearance.AppCompat.Display3"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:padding="16dp"
       android:textColor="@android:color/black"
       android:layout_marginStart="8dp"
       android:layout_marginEnd="8dp"
       android:layout_marginTop="24dp"
       android:text="Fan Control"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>
  1. أضِف العنصر ImageView إلى التنسيق. هذا عنصر نائب للعرض المخصص الذي ستنشئه في هذا الدرس التطبيقي حول الترميز.
<ImageView
       android:id="@+id/dialView"
       android:layout_width="200dp"
       android:layout_height="200dp"
       android:background="@android:color/darker_gray"
       app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       android:layout_marginLeft="8dp"
       android:layout_marginRight="8dp"
       android:layout_marginTop="8dp"/>
  1. استخرِج موارد السلسلة والأبعاد في عناصر واجهة المستخدم.
  2. انقر على علامة التبويب تصميم. يجب أن يبدو التنسيق كما يلي:

الخطوة 2: إنشاء فئة العرض المخصّص

  1. إنشاء صف لغة Kotlin جديد باسم DialView.
  2. تعديل تعريف الفئة لتمديد View. استيراد android.view.View عند مطالبتك بذلك.
  3. انقر على View، ثم انقر على رمز المصباح الأحمر. اختَر إضافة طرق إنشاء Android View باستخدام '@JvmOverloads'. يضيف "استوديو Android" المُنشئ من فئة View. توجّه التعليقات التوضيحية @JvmOverloads إلى برنامج التجميع في Kotlin لإنشاء أعباء تراكمية لهذه الدالة التي تستبدل قيم المعلمات التلقائية.
class DialView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
  1. فوق تعريف الفئة DialView، وأسفل عمليات الاستيراد مباشرةً، أضِف enum المستوى الأعلى لتمثيل سرعات المروحة المتاحة. يُرجى العِلم بأنّ نوع enum هذا هو من النوع Int لأنّ القيم عبارة عن موارد للسلسلة بدلاً من سلاسل فعلية. سيعرض "استوديو Android" أخطاء لموارد السلسلة المفقودة في كل من هذه القيم، وستعالجها في خطوة لاحقة.
private enum class FanSpeed(val label: Int) {
   OFF(R.string.fan_off),
   LOW(R.string.fan_low),
   MEDIUM(R.string.fan_medium),
   HIGH(R.string.fan_high);
}
  1. أسفل enum، أضِف هذه الثوابت. ستستخدم ذلك كجزء من رسم مؤشرات الطلب والتصنيفات.
private const val RADIUS_OFFSET_LABEL = 30      
private const val RADIUS_OFFSET_INDICATOR = -35
  1. داخل الفئة DialView، حدِّد متغيرات متعددة لرسمها في طريقة العرض المخصّصة. يمكنك استيراد android.graphics.PointF عند طلبها.
private var radius = 0.0f                   // Radius of the circle.
private var fanSpeed = FanSpeed.OFF         // The active selection.
// position variable which will be used to draw label and indicator circle position
private val pointPosition: PointF = PointF(0.0f, 0.0f)
  • يمثّل radius النطاق الجغرافي الحالي للدائرة. يتم تحديد هذه القيمة عند رسم طريقة العرض على الشاشة.
  • تُعدّ fanSpeed السرعة الحالية للمروحة، وهي إحدى القيم في قائمة تعداد FanSpeed. وتكون هذه القيمة OFF تلقائيًا.
  • أخيرًا، postPosition هي نقطة X،Y سيتم استخدامها لرسم العديد من عناصر view على الشاشة.

ويتم إنشاء هذه القيم وضبطها هنا بدلاً من رسمها، لضمان تنفيذ خطوة الرسم الفعلية بأسرع ما يمكن.

  1. ويجب أيضًا إعداد كائن Paint باستخدام مجموعة من الأنماط الأساسية ضمن تعريف الفئة DialView. استيراد android.graphics.Paint وandroid.graphics.Typeface عند الطلب كما هو الحال مع المتغيّرات، يتم إعداد هذه الأنماط هنا للمساعدة على تسريع خطوة الرسم.
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
   style = Paint.Style.FILL
   textAlign = Paint.Align.CENTER
   textSize = 55.0f
   typeface = Typeface.create( "", Typeface.BOLD)
}
  1. افتح res/values/strings.xml وأضِف موارد السلسلة لسرعات المروحة:
<string name="fan_off">off</string>
<string name="fan_low">1</string>
<string name="fan_medium">2</string>
<string name="fan_high">3</string>

بعد إنشاء عرض مخصص، يجب أن تتمكن من رسمه. عند تمديد فئة فرعية للسمة View مثل EditText، تحدّد هذه الفئة الفرعية مظهر وسمات العرض وترسم نفسها على الشاشة. وبالتالي، لست بحاجة إلى كتابة الرمز لرسم طريقة العرض. يمكنك إلغاء طرق رئيسية لتخصيص طريقة العرض بدلاً من ذلك.

إذا كنت تنشئ العرض الخاص بك من البداية (من خلال تمديد View)، فإنك مسؤول عن رسم العرض بأكمله في كل مرة يتم فيها تحديث الشاشة، وتجاوز طرق View التي تتعامل مع الرسم. لرسم عرض مخصص يمتد بشكلٍ صحيح ويمتد إلى View، تحتاج إلى:

  • احسِب حجم الملف الشخصي عند ظهوره لأول مرة، وفي كل مرة يتغيّر فيها حجم الملف الشخصي من خلال إلغاء طريقة onSizeChanged().
  • يمكنك إلغاء طريقة onDraw() لرسم طريقة عرض مخصّصة، وذلك باستخدام عنصر Canvas يصمّمه عنصر Paint.
  • وعليك باستدعاء طريقة invalidate() عند الاستجابة لنقرة مستخدم تؤدي إلى تغيير كيفية رسم الملف الشخصي لإلغاء صلاحية العرض بالكامل، ما يؤدي إلى استدعاء onDraw() لإعادة رسم العرض.

يتم استدعاء طريقة onDraw() في كل مرة يتم فيها تحديث الشاشة، وهو ما يمكن أن يكون عدة مرات في الثانية. لأسباب تتعلق بالأداء ولتجنّب العيوب المرئية، عليك بذل قصارى جهدك في onDraw(). وعلى وجه الخصوص، لا تعرِض التخصيصات في onDraw()، لأن التخصيصات قد تؤدي إلى جمع البيانات المهملة التي قد تتسبّب في حدوث تقطّع بصري.

تقدّم الفئات Canvas وPaint عددًا من اختصارات الرسم المفيدة:

  • رسم نص باستخدام drawText() يمكنك تحديد نمط الخط من خلال طلب setTypeface()، ولون النص من خلال الاتصال بـ setColor().
  • يمكنك رسم الأشكال الأولية باستخدام drawRect() وdrawOval() وdrawArc(). يمكنك تغيير ما إذا كانت الأشكال مليئة أو موضّحة أو كلاهما من خلال الاتصال setStyle().
  • ارسم صورًا نقطية باستخدام drawBitmap().

ستتعرّف على المزيد من المعلومات عن Canvas وPaint في درس تطبيقي لاحق حول الترميز. لمزيد من المعلومات حول كيفية سحب Android للمشاهدات، يرجى الاطّلاع على كيفية رسم Android للمشاهدات.

في هذه المَهمّة، سترسم طريقة عرض مخصّصة لوحدة التحكّم في المروحة على الشاشة، أي الشريط نفسه ومؤشر الموضع الحالي وتصنيفات المؤشر، باستخدام الطريقتين onSizeChanged() وonDraw(). ستنشئ أيضًا طريقة مساعد، computeXYForSpeed(), لحساب الموضع الحالي X، وY لتصنيف المؤشر على الشريط.

الخطوة الأولى: حساب المواضع ورسم العرض

  1. في الفئة DialView، تحت عمليات الإعداد، يمكنك إلغاء طريقة onSizeChanged() من الفئة View لحساب حجم الطلب المخصّص. استيراد kotlin.math.min عند الطلب.

    يتم استدعاء طريقة onSizeChanged() في أي وقت يتغير فيه حجم العرض، بما في ذلك أول مرة يتم فيها رسمه عند تضخيم التنسيق. يمكنك تجاوز onSizeChanged() لحساب المواضع والأبعاد وأي قيم أخرى ذات صلة بحجم العرض المخصّص، بدلاً من إعادة حسابها في كل مرة ترسم فيها. في هذه الحالة، يمكنك استخدام onSizeChanged() لحساب النطاق الجغرافي الحالي لعنصر دائرة الاتصال.
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
   radius = (min(width, height) / 2.0 * 0.8).toFloat()
}
  1. وأسفل onSizeChanged()، يمكنك إضافة هذا الرمز لتحديد دالة الإضافة computeXYForSpeed() للصف PointF . استيراد kotlin.math.cos وkotlin.math.sin عند الطلب تحسب وظيفة الإضافة هذه في الفئة PointF الإحداثيات X أو Y على الشاشة لتصنيف النص والمؤشر الحالي (0 أو 1 أو 2 أو 3)، مع الأخذ في الاعتبار موضع FanSpeed والنطاق الجغرافي الحاليين للطلب. ستستخدم هذا المحتوى خلال onDraw()..
private fun PointF.computeXYForSpeed(pos: FanSpeed, radius: Float) {
   // Angles are in radians.
   val startAngle = Math.PI * (9 / 8.0)   
   val angle = startAngle + pos.ordinal * (Math.PI / 4)
   x = (radius * cos(angle)).toFloat() + width / 2
   y = (radius * sin(angle)).toFloat() + height / 2
}
  1. يمكنك إلغاء الطريقة onDraw() لعرض العرض على الشاشة بالصفوف الدراسية Canvas وPaint. استيراد android.graphics.Canvas عند الطلب. في ما يلي بنية هيكل عظمي:
override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
   
}
  1. داخل onDraw()، أضِف هذا السطر لضبط لون الطلاء على اللون الرمادي (Color.GRAY) أو اللون الأخضر (Color.GREEN) بناءً على ما إذا كانت سرعة المروحة OFF أو أي قيمة أخرى. استيراد android.graphics.Color عند الطلب.
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
  1. أضف هذا الرمز لرسم دائرة للطلب، باستخدام طريقة drawCircle(). وتستخدِم هذه الطريقة عرض وارتفاع العرض الحالي للعثور على منتصف الدائرة ونصف قطر الدائرة ولون الطلاء الحالي. السمتان width وheight هما عضوان في الفئة العليا View ويشيران إلى الأبعاد الحالية للملف الشخصي.
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
  1. أضف هذا الرمز التالي لرسم دائرة أصغر لعلامة مؤشر سرعة المروحة، وأيضًا باستخدام الطريقة drawCircle() يستخدم هذا الجزء PointF.طريقة الإضافة computeXYforSpeed() لحساب إحداثي X وY لمركز المؤشر استنادًا إلى سرعة المروحة الحالية.
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
  1. وأخيرًا، ارسم تصنيفات سرعة المروحة (0، 1، 2، 3) في المواضع المناسبة حول قرص الساعة. يستدعي هذا الجزء من الطريقة PointF.computeXYForSpeed() مرة أخرى للحصول على موضع لكل تصنيف، ويعيد استخدام العنصر pointPosition في كل مرة لتجنُّب عمليات التخصيص. استخدِم drawText() لرسم التصنيفات.
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
   pointPosition.computeXYForSpeed(i, labelRadius)
   val label = resources.getString(i.label)
   canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}

تظهر طريقة onDraw() المكتملة على النحو التالي:

override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
   // Set dial background color to green if selection not off.
   paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
   // Draw the dial.
   canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
   // Draw the indicator circle.
   val markerRadius = radius + RADIUS_OFFSET_INDICATOR
   pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
   paint.color = Color.BLACK
   canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
   // Draw the text labels.
   val labelRadius = radius + RADIUS_OFFSET_LABEL
   for (i in FanSpeed.values()) {
       pointPosition.computeXYForSpeed(i, labelRadius)
       val label = resources.getString(i.label)
       canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
   }
}

الخطوة الثانية: إضافة العرض إلى التنسيق

لإضافة عرض مخصص إلى واجهة مستخدم التطبيق، يمكنك تحديده كعنصر في تنسيق XML للنشاط. يمكنك التحكم في مظهره وسلوكه باستخدام سمات عنصر XML، مثلما تفعل مع أي عنصر آخر في واجهة المستخدم.

  1. في activity_main.xml، غيّر علامة ImageView للسمة dialView إلى com.example.android.customfancontroller.DialView، واحذف السمة android:background. يكتسب DialView وImageView الأصليان السمات العادية من الفئة View، لذلك ليس من الضروري تغيير أيٍّ من السمات الأخرى. يبدو عنصر DialView الجديد على النحو التالي:
<com.example.android.customfancontroller.DialView
       android:id="@+id/dialView"
       android:layout_width="@dimen/fan_dimen"
       android:layout_height="@dimen/fan_dimen"
       app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       android:layout_marginLeft="@dimen/default_margin"
       android:layout_marginRight="@dimen/default_margin"
       android:layout_marginTop="@dimen/default_margin" />
  1. شغِّل التطبيق. ويظهر عرض التحكم في المروحة ضمن النشاط.

وتتمثل المهمة النهائية في تفعيل طريقة العرض المخصّصة لتنفيذ إجراء عندما ينقر المستخدم على طريقة العرض. يجب أن تنقل كل نقرة مؤشر الاختيار إلى الموضع التالي: out-1-2-3 ثم العودة إلى وضع إيقاف التشغيل. إذا كان الاختيار واحدًا أو أكثر، يمكنك تغيير الخلفية من الرمادي إلى الأخضر، للإشارة إلى أن طاقة المروحة قيد التشغيل.

لتفعيل طريقة العرض المخصّصة القابلة للنقر، يجب:

  • ضبط موقع isClickable على الملف الشخصي على true ويؤدي ذلك إلى تفعيل طريقة العرض المخصّصة للاستجابة للنقرات.
  • تنفيذ performClick() على مستوى View لإجراء العمليات عند النقر على العرض.
  • وعليك استدعاء طريقة invalidate(). وهذا يطلب من نظام التشغيل Android استدعاء طريقة onDraw() لإعادة رسم طبقة العرض.

عادةً، باستخدام طريقة عرض عادية في Android، يمكنك تنفيذ OnClickListener() لتنفيذ إجراء عندما ينقر المستخدم على ذلك الملف الشخصي. للحصول على عرض مخصّص، يمكنك تنفيذ طريقة performClick() على مستوى View بدلاً من ذلك، واستدعاء super.performClick(). تتطلب طريقة performClick() التلقائية أيضًا onClickListener()، لذا يمكنك إضافة إجراءاتك إلى performClick() وترك onClickListener() متاحًا لإجراء المزيد من التخصيص من خلالك أو أي مطوّري برامج آخرين قد يستخدمون عرضك المخصص.

  1. في DialView.kt، داخل تعداد FanSpeed، أضف دالة الإضافة next() التي تغيّر سرعة المروحة الحالية إلى السرعة التالية في القائمة (من OFF إلى LOW وMEDIUM وHIGH، ثم ارجع إلى OFF). ويبدو الآن التعداد الكامل على النحو التالي:
private enum class FanSpeed(val label: Int) {
   OFF(R.string.fan_off),
   LOW(R.string.fan_low),
   MEDIUM(R.string.fan_medium),
   HIGH(R.string.fan_high);

   fun next() = when (this) {
       OFF -> LOW
       LOW -> MEDIUM
       MEDIUM -> HIGH
       HIGH -> OFF
   }
}
  1. داخل الفئة DialView، قبل الطريقة onSizeChanged() مباشرةً، أضِف كتلة init(). يؤدي ضبط خاصية isClickable في الملف الشخصي على "صحيح" إلى تفعيل هذا الملف الشخصي لقبول إدخال المستخدم.
init {
   isClickable = true
}
  1. في ما يلي init(), تلغي الطريقة performClick() باستخدام الرمز أدناه.
override fun performClick(): Boolean {
   if (super.performClick()) return true

   fanSpeed = fanSpeed.next()
   contentDescription = resources.getString(fanSpeed.label)
  
   invalidate()
   return true
}

مكالمة صادرة إلى super.يجب أن يحدث performClick() أولاً، ما يسمح بتفعيل أحداث تسهيل الاستخدام بالإضافة إلى المكالمات onClickListener().

السطران التاليان يعملان على زيادة سرعة المروحة باستخدام طريقة next()، وتعيين وصف محتوى العرض على مورد السلسلة الذي يمثل السرعة الحالية (موقوفة أو 1 أو 2 أو 3).

أخيرًا، تؤدي الطريقة invalidate() إلى إلغاء صلاحية العرض بالكامل، ما يؤدي إلى استدعاء onDraw() لإعادة رسم العرض. إذا حدث أي تغيير في طريقة العرض المخصّصة لأي سبب، بما في ذلك تفاعل المستخدم والحاجة إلى عرض التغيير، اتّصِل بالرقم invalidate()..

  1. شغِّل التطبيق. انقر على العنصر DialView لتحريك المؤشر من "إيقاف" إلى "1". من المفترض أن يتحوّل رمز الاتصال إلى اللون الأخضر. ويجب أن ينتقل المؤشر إلى الموضع التالي مع كل نقرة. وعندما يعود المؤشر إلى وضع الإيقاف، يجب أن يتحوّل الشريط إلى اللون الرمادي مرة أخرى.

يعرض هذا المثال الآليات الأساسية لاستخدام السمات المخصصة مع العرض المخصص. أنت تحدد سمات مخصصة للصف DialView بلون مختلف لكل موضع اتصال بين المعجبين.

  1. إنشاء res/values/attrs.xml وفتحه.
  2. داخل <resources>، أضف عنصر <declare-styleable>.
  3. داخل عنصر المورد <declare-styleable>، أضِف ثلاثة عناصر attr، عنصر لكل سمة، مع name وformat. ويُعد format نوعًا، وهو في هذه الحالة color.
<?xml version="1.0" encoding="utf-8"?>
<resources>
       <declare-styleable name="DialView">
           <attr name="fanColor1" format="color" />
           <attr name="fanColor2" format="color" />
           <attr name="fanColor3" format="color" />
       </declare-styleable>
</resources>
  1. افتح ملف التنسيق activity_main.xml.
  2. في DialView، أضِف سمات fanColor1 وfanColor2 وfanColor3، واضبط قيمها على الألوان الموضحة أدناه. استخدِم app: كمقدمة للسمة المخصّصة (كما في app:fanColor1) بدلاً من android: لأن سماتك المخصّصة تنتمي إلى مساحة الاسم schemas.android.com/apk/res/your_app_package_name بدلاً من مساحة الاسم android.
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="#009688"

لاستخدام السمات في فئة DialView، يلزم استردادها. ويتم تخزين هذه البيانات في AttributeSet يتم تسليمها إلى صفك عند إنشائها. يمكنك استرداد السمات في init وتخصيص قيم السمات للمتغيرات المحلية للتخزين المؤقت.

  1. افتح ملف الصف DialView.kt.
  2. ضمن DialView، أعلن عن المتغيرات لتخزين قيم السمات مؤقتًا.
private var fanSpeedLowColor = 0
private var fanSpeedMediumColor = 0
private var fanSeedMaxColor = 0
  1. في مجموعة init، أضِف الرمز التالي باستخدام دالة الإضافة withStyledAttributes. يمكنك توفير السمات والعرض، وضبط المتغيرات المحلية. سيؤدي استيراد withStyledAttributes أيضًا إلى استيراد الدالة getColor() الصحيحة.
context.withStyledAttributes(attrs, R.styleable.DialView) {
   fanSpeedLowColor = getColor(R.styleable.DialView_fanColor1, 0)
   fanSpeedMediumColor = getColor(R.styleable.DialView_fanColor2, 0)
   fanSeedMaxColor = getColor(R.styleable.DialView_fanColor3, 0)
}
  1. استخدِم المتغيّرات المحلية في onDraw() لضبط لون الاتصال استنادًا إلى سرعة المروحة الحالية. استخدِم الرمز الوارد أدناه لاستبدال الخط الذي تم ضبط لونه (paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN).
paint.color = when (fanSpeed) {
   FanSpeed.OFF -> Color.GRAY
   FanSpeed.LOW -> fanSpeedLowColor
   FanSpeed.MEDIUM -> fanSpeedMediumColor
   FanSpeed.HIGH -> fanSeedMaxColor
} as Int
  1. شغِّل تطبيقك وانقر على الشريط، ويجب أن يختلف إعداد الألوان لكل موضع، كما هو موضّح أدناه.

للاطّلاع على مزيد من المعلومات عن سمات الملف الشخصي المخصّصة، راجع إنشاء فئة عرض.

أدوات تمكين الوصول هي مجموعة من تقنيات التصميم والتنفيذ والاختبار التي تتيح استخدام تطبيقك للجميع، بما في ذلك الأشخاص الذين يعانون من عجز.

تشمل حالات العجز الشائعة التي يمكن أن تؤثر في استخدام الشخص لجهاز Android العمى وضعف النظر والعمى اللوني والصمم أو فقدان السمع والمهارات الحركية المحدودة. وعند تطوير تطبيقاتك مع وضع تسهيل الاستخدام في الاعتبار، ستساهم في تحسين تجربة المستخدم، ليس فقط للمستخدمين الذين يعانون من عجز هذه، ولكن لجميع المستخدمين الآخرين.

يوفر Android العديد من ميزات إمكانية الوصول تلقائيًا في طرق عرض واجهة المستخدم العادية، مثل TextView وButton. ولكن عند إنشاء عرض مخصص، يجب مراعاة كيفية تقديم هذا العرض المخصص لميزات يمكن الوصول إليها، مثل الأوصاف المنطوقة للمحتوى على الشاشة.

في هذه المهمة، ستتعرّف على TalkBack وقارئ الشاشة لنظام التشغيل Android وعدّل التطبيق ليتضمن تلميحات وأوصافًا قابلة للقراءة في طريقة العرض المخصّصة في DialView.

الخطوة 1: استكشاف TalkBack

ميزة TalkBack هي قارئ شاشة مضمّن في نظام التشغيل Android. عند تفعيل TalkBack، يمكن للمستخدم التفاعل مع جهاز Android بدون رؤية الشاشة، لأن Android يصف عناصر الشاشة بصوت عالٍ. قد يعتمد المستخدمون الذين يعانون من عجز بصري على TalkBack لاستخدام تطبيقك.

في هذه المهمة، يمكنك تفعيل TalkBack لفهم آلية عمل برامج قراءة الشاشة وكيفية التنقُّل في التطبيقات.

  1. على جهاز أو جهاز محاكاة Android، انتقِل إلى الإعدادات &gt وإمكانية الوصول &gt؛ TalkBack.
  2. انقر على زر الإيقاف/التفعيل لتفعيل TalkBack.
  3. انقر على حسنًا لتأكيد الأذونات.
  4. أكِّد كلمة مرور جهازك، إذا طُلب منك ذلك. إذا كانت هذه هي المرة الأولى التي تشغيل فيها TalkBack، يتم تشغيل برنامج تعليمي. (قد لا يتوفّر البرنامج التعليمي على الأجهزة القديمة.)
  5. قد يكون من المفيد التنقل في البرنامج التعليمي مع إبقاء عينَيك مغمضتَين. لفتح الدليل التوجيهي مرة أخرى في المستقبل، انتقل إلى الإعدادات &gt؛ تسهيل الاستخدام &gt؛ TalkBack &gt؛ الإعدادات &gt؛ تشغيل البرنامج التعليمي عن TalkBack.
  6. اجمَع تطبيق CustomFanController وشغِّله، أو افتحه باستخدام زر النظرة العامة أو الزر الأحدث على جهازك. عند تفعيل TalkBack، لاحظ أن اسم التطبيق يُعلن عنه، بالإضافة إلى نص التصنيف TextView (&"التحكّم بالمعجبين&;). ومع ذلك، إذا نقرت على طريقة العرض DialView نفسها، لن يتم قول أي معلومات عن حالة العرض (الإعداد الحالي لطلب الاتصال) أو الإجراء الذي سيحدث عند النقر على العرض لتفعيله.

الخطوة الثانية: إضافة أوصاف المحتوى لتصنيفات الطلب

تصف أوصاف المحتوى معنى والغرض من مرات المشاهدة في تطبيقك. وتسمح هذه التصنيفات لبرامج قراءة الشاشة مثل ميزة TalkBack في Androidمثل شرح وظيفة كل عنصر بدقة. بالنسبة إلى طرق العرض الثابتة مثل ImageView، يمكنك إضافة وصف المحتوى إلى العرض في ملف التنسيق باستخدام السمة contentDescription. تستخدم طرق عرض النصوص (TextView وEditText) النص في العرض تلقائيًا كوصف للمحتوى.

للحصول على عرض مخصص للتحكّم في المروحة، يجب تحديث وصف المحتوى بشكلٍ ديناميكي في كل مرة يتم النقر على طريقة العرض، للإشارة إلى إعداد المروحة الحالي.

  1. في أسفل الفئة DialView، أعلن عن دالة updateContentDescription() بدون وسيطات أو نوع إرجاع.
fun updateContentDescription() {
}
  1. من داخل updateContentDescription()، غيِّر السمة contentDescription للملف الشخصي المخصص إلى مورد السلسلة المرتبط بسرعة المروحة الحالية (متوقف أو 1 أو 2 أو 3). هذه هي التصنيفات نفسها المستخدمة في onDraw() عند رسم الطلب على الشاشة.
fun updateContentDescription() {
   contentDescription = resources.getString(fanSpeed.label)
}
  1. انتقِل للأعلى إلى قسم init()، وفي نهاية هذا الحظر، أضِف مكالمة إلى updateContentDescription(). يؤدي ذلك إلى إعداد وصف المحتوى عند إعداد العرض.
init {
   isClickable = true
   // ...

   updateContentDescription()
}
  1. أضف مكالمة أخرى إلى updateContentDescription() باستخدام الطريقة performClick()، قبل invalidate() مباشرةً.
override fun performClick(): Boolean {
   if (super.performClick()) return true
   fanSpeed = fanSpeed.next()
   updateContentDescription()
   invalidate()
   return true
}
  1. اجمع التطبيق وشغِّله وتأكّد من أن TalkBack مفعّل. انقر لتغيير إعداد عرض الطلب ولاحظ أن TalkBack يعلن الآن عن التصنيف الحالي (إيقاف، 1، 2، 3) بالإضافة إلى العبارة "&،"

الخطوة الثالثة. إضافة مزيد من المعلومات من أجل النقر

يمكنك التوقّف عند هذه المرحلة وستكون طريقة العرض قابلة للاستخدام في TalkBack. ولكن قد يكون من المفيد ألا تطّلع طريقة العرض على أنه يمكن تنشيطها فقط (&quot، أو "انقر مرّتين" لتنشيطها أو اقتصارها) أو لشرح ما سينطبق عندما يتم تنشيط العرض (&&;أو النقر مرتين للتغيير أو &&;أو النقر مرتين لإعادة الضبط&).

لتنفيذ ذلك، يمكنك إضافة معلومات حول إجراء العرض (هنا، نقرة أو إجراء انقر) إلى عنصر معلومات عقدة إمكانية الوصول، من خلال تفويض المفوّض. يتيح لك تفويض إمكانية الوصول تخصيص الميزات ذات الصلة بتطبيقك من خلال الإنشاء (بدلاً من الاكتساب).

بالنسبة إلى هذه المهمة، ستستخدم فئات تسهيل الاستخدام في مكتبات Android Jetpack (androidx.*) لضمان التوافق مع الأنظمة القديمة.

  1. في DialView.kt، يمكنك حظر المفوَّض في طريقة العرض في الكائن init كعنصر AccessibilityDelegateCompat جديد. استيراد androidx.core.view.ViewCompat وandroidx.core.view.AccessibilityDelegateCompat عند الطلب تتيح هذه الاستراتيجية أكبر قدر من التوافق مع الأنظمة القديمة في تطبيقك.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   
})
  1. داخل الكائن AccessibilityDelegateCompat، استبدِل الدالة onInitializeAccessibilityNodeInfo() بعنصر AccessibilityNodeInfoCompat، واستدعِ إحدى الطرق المميّزة. استيراد androidx.core.view.accessibility.AccessibilityNodeInfoCompat عند مطالبتك بذلك.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)

   }  
})

وتحتوي كل طريقة عرض على شجرة عُقد إمكانية الوصول، والتي قد تتوافق أو لا تتوافق مع مكونات التنسيق الفعلية للملف الشخصي. وتتنقّل خدمات تسهيل الاستخدام في نظام التشغيل Android عبر هذه العُقد من أجل العثور على معلومات عن طريقة العرض (مثل أوصاف المحتوى المقروء أو الإجراءات المحتملة التي يمكن تنفيذها على طريقة العرض هذه). وعند إنشاء عرض مخصّص، قد تحتاج أيضًا إلى إلغاء معلومات العُقدة لتوفير معلومات مخصّصة لتسهيل الاستخدام. في هذه الحالة، سيتم إلغاء معلومات العقدة للإشارة إلى أن هناك معلومات مخصصة لإجراء العرض.

  1. داخل onInitializeAccessibilityNodeInfo()، أنشئ كائن AccessibilityNodeInfoCompat.AccessibilityActionCompat جديدًا، وخصِّصه إلى المتغيّر customClick. أدخِل الثابت"AccessibilityNodeInfo.ACTION_CLICK"وناشِر العنصر النائب. استورِد AccessibilityNodeInfo عند الطلب.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)
      val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
         AccessibilityNodeInfo.ACTION_CLICK,
        "placeholder"
      )
   }  
})

يمثّل AccessibilityActionCompat إجراءً في الملف الشخصي لأغراض تمكين الوصول. الإجراء النموذجي هو النقر أو الاستخدام الذي تستخدمه هنا، ولكن يمكن أن تتضمن الإجراءات الأخرى اكتساب التركيز أو فقدانه أو عملية الحافظة (قص/نسخ/لصق) أو التمرير داخل الملف الشخصي. تتطلب طريقة وضع هذه الفئة فئة "إجراء" (هنا، AccessibilityNodeInfo.ACTION_CLICK) وسلسلة يستخدمها TalkBack للإشارة إلى الإجراء.

  1. استبدِل السلسلة "placeholder" باستدعاء إلى context.getString() لاسترداد مورد السلسلة. بالنسبة إلى المورد المحدد، يمكنك اختبار سرعة المروحة الحالية. إذا كانت السرعة حاليًا FanSpeed.HIGH، تكون السلسلة هي "Reset". إذا كانت سرعة المروحة أمرًا آخر، ستكون السلسلة "Change." هي التي ستنشئ موارد السلسلة هذه في خطوة لاحقة.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)
      val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
         AccessibilityNodeInfo.ACTION_CLICK,
        context.getString(if (fanSpeed !=  FanSpeed.HIGH) R.string.change else R.string.reset)
      )
   }  
})
  1. بعد أقواس الإغلاق لتعريف customClick، استخدِم الطريقة addAction() لإضافة إجراء تسهيل الاستخدام الجديد إلى عنصر معلومات العقدة.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
       super.onInitializeAccessibilityNodeInfo(host, info)
       val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
           AccessibilityNodeInfo.ACTION_CLICK,
           context.getString(if (fanSpeed !=  FanSpeed.HIGH) 
                                 R.string.change else R.string.reset)
       )
       info.addAction(customClick)
   }
})
  1. في res/values/strings.xml، أضف موارد السلسلة لـ "Change" &"Reset".
<string name="change">Change</string>
<string name="reset">Reset</string>
  1. اجمَع التطبيق وشغِّله وتأكّد من تفعيل TalkBack. تذكّر الآن أنّ العبارة "&انقر مرّتين" لتفعيل خيار "&& للانضمام" أو " للانضمام إلى " تجدُر الإشارة إلى أنه يتم تقديم رسالة المطالبة &quot؛انقر مرّتين من أجل...&&;; عبر خدمة TalkBack نفسها.

نزِّل الرمز للدرس التطبيقي النهائي للترميز.

$  git clone https://github.com/googlecodelabs/android-kotlin-drawing-custom-views


بدلاً من ذلك، يمكنك تنزيل المستودع كملف Zip وفك ضغطه وفتحه في"استوديو Android".

تنزيل ملف Zip

  • لإنشاء ملف شخصي مخصّص يكتسب مظهر فئة فرعية من View وسلوكها، مثل EditText، يمكنك إضافة فئة جديدة تعمل على تمديد هذه الفئة الفرعية، وإجراء التعديلات من خلال إلغاء بعض طرق الفئة الفرعية.
  • لإنشاء عرض مخصّص بأي حجم وأي شكل، أضِف فئة جديدة تمتد إلى View.
  • يمكنك إلغاء طرق View، مثل onDraw()، لتحديد شكل العرض ومظهره الأساسي.
  • استخدِم invalidate() لفرض رسم أو إعادة رسم طبقة العرض.
  • لتحسين الأداء، يمكنك تخصيص المتغيرات وتخصيص أي قيم مطلوبة للرسم والتلوين قبل استخدامها في onDraw()، مثل إعداد المتغيرات المتغيرة.
  • إلغاء performClick() بدلاً من OnClickListener() إلى العرض المخصّص لتوفير السلوك التفاعلي view's. يؤدي هذا إلى تفعيل مطوّري برامج Android أو غيرهم من مطوّري البرامج الذين قد يستخدمون فئة العرض المخصّص لديك من استخدام onClickListener() لتوفير المزيد من الإجراءات.
  • أضف طريقة العرض المخصصة إلى ملف تنسيق XML باستخدام سمات لتحديد مظهره، كما تفعل مع عناصر واجهة المستخدم الأخرى.
  • يُرجى إنشاء الملف attrs.xml في المجلد values لتحديد السمات المخصّصة. يمكنك بعد ذلك استخدام السمات المخصّصة للعرض المخصّص في ملف تنسيق XML.

دورة Udacity:

مستندات مطوّر برامج Android:

فيديوهات:

يسرد هذا القسم المهام الدراسية المحتملة للطلاب الذين يعملون من خلال هذا الدرس التطبيقي حول الترميز في إطار دورة تدريبية يُديرها معلِّم. يجب أن ينفِّذ المعلّم ما يلي:

  • يمكنك تخصيص واجب منزلي إذا لزم الأمر.
  • التواصل مع الطلاب بشأن كيفية إرسال الواجبات المنزلية
  • وضع درجات للواجبات المنزلية.

ويمكن للمعلّمين استخدام هذه الاقتراحات بقدر ما يريدون أو بقدر ما يريدون، ويجب عدم التردد في تخصيص أي واجبات منزلية أخرى مناسبة.

إذا كنت تستخدم هذا الدرس التطبيقي بنفسك، يمكنك استخدام هذه الواجبات المنزلية لاختبار معلوماتك.

السؤال 1

لحساب المواضع والأبعاد وأي قيم أخرى عند تعيين حجم العرض المخصص أولاً، ما الطريقة التي تلغيها؟

onMeasure()

onSizeChanged()

invalidate()

onDraw()

السؤال 2

للإشارة إلى أنك تريد إعادة رسم ملفك الشخصي باستخدام onDraw()، ما الطريقة التي تطلبها من سلسلة محادثات واجهة المستخدم، بعد تغيير قيمة السمة؟

▢ onMeasure()

▢ onSizeChanged()

▢ invalidate()

▢ getVisibility()

السؤال 3

ما طريقة View التي يجب إلغاءها لإضافة التفاعل إلى طريقة العرض المخصصة؟

▢ setOnClickListener()

▢ onSizeChanged()

▢ isClickable()

▢ performClick()

للحصول على روابط إلى دروس تطبيقية أخرى حول الترميز في هذه الدورة التدريبية، اطّلِع على الصفحة المقصودة للإصدارات المتقدّمة من Android في لغة ترميز Kotlin.