يُعد هذا الدرس التطبيقي جزءًا من الدورة التدريبية المتقدّمة لنظام التشغيل 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
لإنشاء عرض مخصّص. - كيفية رسم عرض مخصّص دائري الشكل.
- كيفية استخدام المستمعين للتعامل مع تفاعل المستخدم مع العرض المخصّص.
- كيفية استخدام عرض مخصص في التنسيق.
الإجراءات التي ستنفذّها
يوضّح تطبيق 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
- أنشئ تطبيق Kotlin بعنوان
CustomFanController
باستخدام نموذج النشاط الفارغ. تأكَّد من أن اسم الحزمة هوcom.example.android.customfancontroller
. - افتح
activity_main.xml
في علامة التبويب النص لتعديل رمز XML. - استبدل
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"/>
- أضِف العنصر
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"/>
- استخرِج موارد السلسلة والأبعاد في عناصر واجهة المستخدم.
- انقر على علامة التبويب تصميم. يجب أن يبدو التنسيق كما يلي:
الخطوة 2: إنشاء فئة العرض المخصّص
- إنشاء صف لغة Kotlin جديد باسم
DialView
. - تعديل تعريف الفئة لتمديد
View
. استيرادandroid.view.View
عند مطالبتك بذلك. - انقر على
View
، ثم انقر على رمز المصباح الأحمر. اختَر إضافة طرق إنشاء Android View باستخدام '@JvmOverloads'. يضيف "استوديو Android" المُنشئ من فئةView
. توجّه التعليقات التوضيحية@JvmOverloads
إلى برنامج التجميع في Kotlin لإنشاء أعباء تراكمية لهذه الدالة التي تستبدل قيم المعلمات التلقائية.
class DialView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
- فوق تعريف الفئة
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);
}
- أسفل
enum
، أضِف هذه الثوابت. ستستخدم ذلك كجزء من رسم مؤشرات الطلب والتصنيفات.
private const val RADIUS_OFFSET_LABEL = 30
private const val RADIUS_OFFSET_INDICATOR = -35
- داخل الفئة
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 على الشاشة.
ويتم إنشاء هذه القيم وضبطها هنا بدلاً من رسمها، لضمان تنفيذ خطوة الرسم الفعلية بأسرع ما يمكن.
- ويجب أيضًا إعداد كائن
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)
}
- افتح
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 لتصنيف المؤشر على الشريط.
الخطوة الأولى: حساب المواضع ورسم العرض
- في الفئة
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()
}
- وأسفل
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
}
- يمكنك إلغاء الطريقة
onDraw()
لعرض العرض على الشاشة بالصفوف الدراسيةCanvas
وPaint
. استيرادandroid.graphics.Canvas
عند الطلب. في ما يلي بنية هيكل عظمي:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
}
- داخل
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
- أضف هذا الرمز لرسم دائرة للطلب، باستخدام طريقة
drawCircle()
. وتستخدِم هذه الطريقة عرض وارتفاع العرض الحالي للعثور على منتصف الدائرة ونصف قطر الدائرة ولون الطلاء الحالي. السمتانwidth
وheight
هما عضوان في الفئة العلياView
ويشيران إلى الأبعاد الحالية للملف الشخصي.
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
- أضف هذا الرمز التالي لرسم دائرة أصغر لعلامة مؤشر سرعة المروحة، وأيضًا باستخدام الطريقة
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)
- وأخيرًا، ارسم تصنيفات سرعة المروحة (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، مثلما تفعل مع أي عنصر آخر في واجهة المستخدم.
- في
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" />
- شغِّل التطبيق. ويظهر عرض التحكم في المروحة ضمن النشاط.
وتتمثل المهمة النهائية في تفعيل طريقة العرض المخصّصة لتنفيذ إجراء عندما ينقر المستخدم على طريقة العرض. يجب أن تنقل كل نقرة مؤشر الاختيار إلى الموضع التالي: out-1-2-3 ثم العودة إلى وضع إيقاف التشغيل. إذا كان الاختيار واحدًا أو أكثر، يمكنك تغيير الخلفية من الرمادي إلى الأخضر، للإشارة إلى أن طاقة المروحة قيد التشغيل.
لتفعيل طريقة العرض المخصّصة القابلة للنقر، يجب:
- ضبط موقع
isClickable
على الملف الشخصي علىtrue
ويؤدي ذلك إلى تفعيل طريقة العرض المخصّصة للاستجابة للنقرات. - تنفيذ
performClick
()
على مستوىView
لإجراء العمليات عند النقر على العرض. - وعليك استدعاء طريقة
invalidate()
. وهذا يطلب من نظام التشغيل Android استدعاء طريقةonDraw()
لإعادة رسم طبقة العرض.
عادةً، باستخدام طريقة عرض عادية في Android، يمكنك تنفيذ OnClickListener()
لتنفيذ إجراء عندما ينقر المستخدم على ذلك الملف الشخصي. للحصول على عرض مخصّص، يمكنك تنفيذ طريقة performClick
()
على مستوى View
بدلاً من ذلك، واستدعاء super
.performClick().
تتطلب طريقة performClick()
التلقائية أيضًا onClickListener()
، لذا يمكنك إضافة إجراءاتك إلى performClick()
وترك onClickListener()
متاحًا لإجراء المزيد من التخصيص من خلالك أو أي مطوّري برامج آخرين قد يستخدمون عرضك المخصص.
- في
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
}
}
- داخل الفئة
DialView
، قبل الطريقةonSizeChanged()
مباشرةً، أضِف كتلةinit()
. يؤدي ضبط خاصيةisClickable
في الملف الشخصي على "صحيح" إلى تفعيل هذا الملف الشخصي لقبول إدخال المستخدم.
init {
isClickable = true
}
- في ما يلي
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().
.
- شغِّل التطبيق. انقر على العنصر
DialView
لتحريك المؤشر من "إيقاف" إلى "1". من المفترض أن يتحوّل رمز الاتصال إلى اللون الأخضر. ويجب أن ينتقل المؤشر إلى الموضع التالي مع كل نقرة. وعندما يعود المؤشر إلى وضع الإيقاف، يجب أن يتحوّل الشريط إلى اللون الرمادي مرة أخرى.
يعرض هذا المثال الآليات الأساسية لاستخدام السمات المخصصة مع العرض المخصص. أنت تحدد سمات مخصصة للصف DialView
بلون مختلف لكل موضع اتصال بين المعجبين.
- إنشاء
res/values/attrs.xml
وفتحه. - داخل
<resources>
، أضف عنصر<declare-styleable>
. - داخل عنصر المورد
<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>
- افتح ملف التنسيق
activity_main.xml
. - في
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
وتخصيص قيم السمات للمتغيرات المحلية للتخزين المؤقت.
- افتح ملف الصف
DialView.kt
. - ضمن
DialView
، أعلن عن المتغيرات لتخزين قيم السمات مؤقتًا.
private var fanSpeedLowColor = 0
private var fanSpeedMediumColor = 0
private var fanSeedMaxColor = 0
- في مجموعة
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)
}
- استخدِم المتغيّرات المحلية في
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
- شغِّل تطبيقك وانقر على الشريط، ويجب أن يختلف إعداد الألوان لكل موضع، كما هو موضّح أدناه.
للاطّلاع على مزيد من المعلومات عن سمات الملف الشخصي المخصّصة، راجع إنشاء فئة عرض.
أدوات تمكين الوصول هي مجموعة من تقنيات التصميم والتنفيذ والاختبار التي تتيح استخدام تطبيقك للجميع، بما في ذلك الأشخاص الذين يعانون من عجز.
تشمل حالات العجز الشائعة التي يمكن أن تؤثر في استخدام الشخص لجهاز Android العمى وضعف النظر والعمى اللوني والصمم أو فقدان السمع والمهارات الحركية المحدودة. وعند تطوير تطبيقاتك مع وضع تسهيل الاستخدام في الاعتبار، ستساهم في تحسين تجربة المستخدم، ليس فقط للمستخدمين الذين يعانون من عجز هذه، ولكن لجميع المستخدمين الآخرين.
يوفر Android العديد من ميزات إمكانية الوصول تلقائيًا في طرق عرض واجهة المستخدم العادية، مثل TextView
وButton
. ولكن عند إنشاء عرض مخصص، يجب مراعاة كيفية تقديم هذا العرض المخصص لميزات يمكن الوصول إليها، مثل الأوصاف المنطوقة للمحتوى على الشاشة.
في هذه المهمة، ستتعرّف على TalkBack وقارئ الشاشة لنظام التشغيل Android وعدّل التطبيق ليتضمن تلميحات وأوصافًا قابلة للقراءة في طريقة العرض المخصّصة في DialView
.
الخطوة 1: استكشاف TalkBack
ميزة TalkBack هي قارئ شاشة مضمّن في نظام التشغيل Android. عند تفعيل TalkBack، يمكن للمستخدم التفاعل مع جهاز Android بدون رؤية الشاشة، لأن Android يصف عناصر الشاشة بصوت عالٍ. قد يعتمد المستخدمون الذين يعانون من عجز بصري على TalkBack لاستخدام تطبيقك.
في هذه المهمة، يمكنك تفعيل TalkBack لفهم آلية عمل برامج قراءة الشاشة وكيفية التنقُّل في التطبيقات.
- على جهاز أو جهاز محاكاة Android، انتقِل إلى الإعدادات > وإمكانية الوصول >؛ TalkBack.
- انقر على زر الإيقاف/التفعيل لتفعيل TalkBack.
- انقر على حسنًا لتأكيد الأذونات.
- أكِّد كلمة مرور جهازك، إذا طُلب منك ذلك. إذا كانت هذه هي المرة الأولى التي تشغيل فيها TalkBack، يتم تشغيل برنامج تعليمي. (قد لا يتوفّر البرنامج التعليمي على الأجهزة القديمة.)
- قد يكون من المفيد التنقل في البرنامج التعليمي مع إبقاء عينَيك مغمضتَين. لفتح الدليل التوجيهي مرة أخرى في المستقبل، انتقل إلى الإعدادات >؛ تسهيل الاستخدام >؛ TalkBack >؛ الإعدادات >؛ تشغيل البرنامج التعليمي عن TalkBack.
- اجمَع تطبيق
CustomFanController
وشغِّله، أو افتحه باستخدام زر النظرة العامة أو الزر الأحدث على جهازك. عند تفعيل TalkBack، لاحظ أن اسم التطبيق يُعلن عنه، بالإضافة إلى نص التصنيفTextView
(&"التحكّم بالمعجبين&;). ومع ذلك، إذا نقرت على طريقة العرضDialView
نفسها، لن يتم قول أي معلومات عن حالة العرض (الإعداد الحالي لطلب الاتصال) أو الإجراء الذي سيحدث عند النقر على العرض لتفعيله.
الخطوة الثانية: إضافة أوصاف المحتوى لتصنيفات الطلب
تصف أوصاف المحتوى معنى والغرض من مرات المشاهدة في تطبيقك. وتسمح هذه التصنيفات لبرامج قراءة الشاشة مثل ميزة TalkBack في Androidمثل شرح وظيفة كل عنصر بدقة. بالنسبة إلى طرق العرض الثابتة مثل ImageView
، يمكنك إضافة وصف المحتوى إلى العرض في ملف التنسيق باستخدام السمة contentDescription
. تستخدم طرق عرض النصوص (TextView
وEditText
) النص في العرض تلقائيًا كوصف للمحتوى.
للحصول على عرض مخصص للتحكّم في المروحة، يجب تحديث وصف المحتوى بشكلٍ ديناميكي في كل مرة يتم النقر على طريقة العرض، للإشارة إلى إعداد المروحة الحالي.
- في أسفل الفئة
DialView
، أعلن عن دالةupdateContentDescription()
بدون وسيطات أو نوع إرجاع.
fun updateContentDescription() {
}
- من داخل
updateContentDescription()
، غيِّر السمةcontentDescription
للملف الشخصي المخصص إلى مورد السلسلة المرتبط بسرعة المروحة الحالية (متوقف أو 1 أو 2 أو 3). هذه هي التصنيفات نفسها المستخدمة فيonDraw()
عند رسم الطلب على الشاشة.
fun updateContentDescription() {
contentDescription = resources.getString(fanSpeed.label)
}
- انتقِل للأعلى إلى قسم
init()
، وفي نهاية هذا الحظر، أضِف مكالمة إلىupdateContentDescription()
. يؤدي ذلك إلى إعداد وصف المحتوى عند إعداد العرض.
init {
isClickable = true
// ...
updateContentDescription()
}
- أضف مكالمة أخرى إلى
updateContentDescription()
باستخدام الطريقةperformClick()
، قبلinvalidate()
مباشرةً.
override fun performClick(): Boolean {
if (super.performClick()) return true
fanSpeed = fanSpeed.next()
updateContentDescription()
invalidate()
return true
}
- اجمع التطبيق وشغِّله وتأكّد من أن TalkBack مفعّل. انقر لتغيير إعداد عرض الطلب ولاحظ أن TalkBack يعلن الآن عن التصنيف الحالي (إيقاف، 1، 2، 3) بالإضافة إلى العبارة "&،"
الخطوة الثالثة. إضافة مزيد من المعلومات من أجل النقر
يمكنك التوقّف عند هذه المرحلة وستكون طريقة العرض قابلة للاستخدام في TalkBack. ولكن قد يكون من المفيد ألا تطّلع طريقة العرض على أنه يمكن تنشيطها فقط ("، أو "انقر مرّتين" لتنشيطها أو اقتصارها) أو لشرح ما سينطبق عندما يتم تنشيط العرض (&&;أو النقر مرتين للتغيير أو &&;أو النقر مرتين لإعادة الضبط&).
لتنفيذ ذلك، يمكنك إضافة معلومات حول إجراء العرض (هنا، نقرة أو إجراء انقر) إلى عنصر معلومات عقدة إمكانية الوصول، من خلال تفويض المفوّض. يتيح لك تفويض إمكانية الوصول تخصيص الميزات ذات الصلة بتطبيقك من خلال الإنشاء (بدلاً من الاكتساب).
بالنسبة إلى هذه المهمة، ستستخدم فئات تسهيل الاستخدام في مكتبات Android Jetpack (androidx.*
) لضمان التوافق مع الأنظمة القديمة.
- في
DialView.kt
، يمكنك حظر المفوَّض في طريقة العرض في الكائنinit
كعنصرAccessibilityDelegateCompat
جديد. استيرادandroidx.core.view.ViewCompat
وandroidx.core.view.AccessibilityDelegateCompat
عند الطلب تتيح هذه الاستراتيجية أكبر قدر من التوافق مع الأنظمة القديمة في تطبيقك.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
})
- داخل الكائن
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 عبر هذه العُقد من أجل العثور على معلومات عن طريقة العرض (مثل أوصاف المحتوى المقروء أو الإجراءات المحتملة التي يمكن تنفيذها على طريقة العرض هذه). وعند إنشاء عرض مخصّص، قد تحتاج أيضًا إلى إلغاء معلومات العُقدة لتوفير معلومات مخصّصة لتسهيل الاستخدام. في هذه الحالة، سيتم إلغاء معلومات العقدة للإشارة إلى أن هناك معلومات مخصصة لإجراء العرض.
- داخل
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 للإشارة إلى الإجراء.
- استبدِل السلسلة
"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)
)
}
})
- بعد أقواس الإغلاق لتعريف
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)
}
})
- في
res/values/strings.xml
، أضف موارد السلسلة لـ "Change" &"Reset".
<string name="change">Change</string>
<string name="reset">Reset</string>
- اجمَع التطبيق وشغِّله وتأكّد من تفعيل TalkBack. تذكّر الآن أنّ العبارة "&انقر مرّتين" لتفعيل خيار "&& للانضمام" أو " للانضمام إلى " تجدُر الإشارة إلى أنه يتم تقديم رسالة المطالبة "؛انقر مرّتين من أجل...&&;; عبر خدمة TalkBack نفسها.
نزِّل الرمز للدرس التطبيقي النهائي للترميز.
$ git clone https://github.com/googlecodelabs/android-kotlin-drawing-custom-views
بدلاً من ذلك، يمكنك تنزيل المستودع كملف Zip وفك ضغطه وفتحه في"استوديو Android".
- لإنشاء ملف شخصي مخصّص يكتسب مظهر فئة فرعية من
View
وسلوكها، مثلEditText
، يمكنك إضافة فئة جديدة تعمل على تمديد هذه الفئة الفرعية، وإجراء التعديلات من خلال إلغاء بعض طرق الفئة الفرعية. - لإنشاء عرض مخصّص بأي حجم وأي شكل، أضِف فئة جديدة تمتد إلى
View
. - يمكنك إلغاء طرق
View
، مثلonDraw()
، لتحديد شكل العرض ومظهره الأساسي. - استخدِم
invalidate()
لفرض رسم أو إعادة رسم طبقة العرض. - لتحسين الأداء، يمكنك تخصيص المتغيرات وتخصيص أي قيم مطلوبة للرسم والتلوين قبل استخدامها في
onDraw()
، مثل إعداد المتغيرات المتغيرة. - إلغاء
performClick()
بدلاً منOnClickListener
() إلى العرض المخصّص لتوفير السلوك التفاعلي view's. يؤدي هذا إلى تفعيل مطوّري برامج Android أو غيرهم من مطوّري البرامج الذين قد يستخدمون فئة العرض المخصّص لديك من استخدامonClickListener()
لتوفير المزيد من الإجراءات. - أضف طريقة العرض المخصصة إلى ملف تنسيق XML باستخدام سمات لتحديد مظهره، كما تفعل مع عناصر واجهة المستخدم الأخرى.
- يُرجى إنشاء الملف
attrs.xml
في المجلدvalues
لتحديد السمات المخصّصة. يمكنك بعد ذلك استخدام السمات المخصّصة للعرض المخصّص في ملف تنسيق XML.
دورة Udacity:
مستندات مطوّر برامج Android:
- إنشاء طرق عرض مخصّصة
@JvmOverloads
- المكوّنات المخصّصة
- كيفية رسم طرق العرض في Android
onMeasure()
onSizeChanged()
onDraw()
Canvas
Paint
drawText()
setTypeface()
setColor()
drawRect()
drawOval()
drawArc()
drawBitmap()
setStyle()
invalidate()
- عرض
- أحداث الإدخال
- طلاء
- مكتبة إضافات Kotlin android-ktx
withStyledAttributes
- مستندات Android KTX
- مدوّنة الإعلان الأصلي لنظام Android KTX
- تسهيل استخدام طرق العرض المخصّصة
AccessibilityDelegateCompat
AccessibilityNodeInfoCompat
AccessibilityNodeInfoCompat.AccessibilityActionCompat
فيديوهات:
يسرد هذا القسم المهام الدراسية المحتملة للطلاب الذين يعملون من خلال هذا الدرس التطبيقي حول الترميز في إطار دورة تدريبية يُديرها معلِّم. يجب أن ينفِّذ المعلّم ما يلي:
- يمكنك تخصيص واجب منزلي إذا لزم الأمر.
- التواصل مع الطلاب بشأن كيفية إرسال الواجبات المنزلية
- وضع درجات للواجبات المنزلية.
ويمكن للمعلّمين استخدام هذه الاقتراحات بقدر ما يريدون أو بقدر ما يريدون، ويجب عدم التردد في تخصيص أي واجبات منزلية أخرى مناسبة.
إذا كنت تستخدم هذا الدرس التطبيقي بنفسك، يمكنك استخدام هذه الواجبات المنزلية لاختبار معلوماتك.
السؤال 1
لحساب المواضع والأبعاد وأي قيم أخرى عند تعيين حجم العرض المخصص أولاً، ما الطريقة التي تلغيها؟
▢ onMeasure()
▢ onSizeChanged()
▢ invalidate()
▢ onDraw()
السؤال 2
للإشارة إلى أنك تريد إعادة رسم ملفك الشخصي باستخدام onDraw()
، ما الطريقة التي تطلبها من سلسلة محادثات واجهة المستخدم، بعد تغيير قيمة السمة؟
▢ onMeasure()
▢ onSizeChanged()
▢ invalidate()
▢ getVisibility()
السؤال 3
ما طريقة View
التي يجب إلغاءها لإضافة التفاعل إلى طريقة العرض المخصصة؟
▢ setOnClickListener()
▢ onSizeChanged()
▢ isClickable()
▢ performClick()
للحصول على روابط إلى دروس تطبيقية أخرى حول الترميز في هذه الدورة التدريبية، اطّلِع على الصفحة المقصودة للإصدارات المتقدّمة من Android في لغة ترميز Kotlin.