إنشاء مكوّنات العرض المخصّصة

تجربة طريقة Compose
‫Jetpack Compose هي مجموعة أدوات واجهة المستخدم المقترَحة لنظام Android. تعرَّف على كيفية استخدام التنسيقات في Compose.

يوفّر Android نموذجًا متطوّرًا وقويًا من المكوّنات لإنشاء واجهة المستخدم، استنادًا إلى فئتَي التنسيق الأساسيتَين View و ViewGroup. تتضمّن المنصّة مجموعة متنوّعة من الفئتَين الفرعيتَين View وViewGroup المُنشأتين مسبقًا، واللتين تُعرفان على التوالي باسمَي الأدوات وعناصر التنسيق، ويمكنك استخدامهما لإنشاء واجهة المستخدم.

تشمل القائمة الجزئية للأدوات المتاحة Button, TextView, EditText, ListView, CheckBox, RadioButton, Gallery, Spinner, و AutoCompleteTextView, ImageSwitcher, و TextSwitcher، وهي أدوات أكثر تخصّصًا.

من بين عناصر التنسيق المتاحة LinearLayout وFrameLayout وRelativeLayout وغيرها. لمزيد من الأمثلة، اطّلِع على عناصر التنسيق الشائعة.

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

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

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

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

النهج الأساسي

في ما يلي نظرة عامة على مستوى عالٍ على ما تحتاج إلى معرفته لإنشاء مكوّنات View الخاصة بك:

  1. يمكنك توسيع فئة View حالية أو فئة فرعية باستخدام فئتك الخاصة.
  2. يمكنك إلغاء بعض الطُرق من الفئة الأساسية. تبدأ طُرق الفئة الأساسية التي يجب إلغاؤها بـ on، مثلاً onDraw() و onMeasure() و onKeyDown(). يشبه ذلك أحداث on في Activity أو ListActivity التي تلغيها من أجل ربط وظائف دورة الحياة والوظائف الأخرى.
  3. يمكنك استخدام فئة الإضافة الجديدة. بعد إكمالها، يمكنك استخدام فئة الإضافة الجديدة بدلاً من طريقة العرض التي استندت إليها.

المكوّنات المخصّصة بالكامل

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

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

لإنشاء مكوّن مخصّص بالكامل، ضَع في اعتبارك ما يلي:

  • إنّ طريقة العرض الأكثر عمومية التي يمكنك توسيعها هي View، لذا عادةً ما تبدأ بتوسيع هذه لإنشاء المكوّن الأساسي الجديد.
  • يمكنك توفير دالة إنشاء يمكنها أخذ السمات والمَعلمات من ملف XML، ويمكنك استخدام السمات والمَعلمات الخاصة بك، مثل لون ونطاق مقياس مستوى الصوت أو عرض وتخميد الإبرة.
  • من المحتمل أن تريد إنشاء مستمعي الأحداث وعناصر الوصول إلى الخصائص والمعدِّلات الخاصة بك، بالإضافة إلى سلوك أكثر تطورًا في فئة المكوّن.
  • من المؤكد تقريبًا أنّك تريد إلغاء onMeasure() ومن المحتمل أيضًا أن تحتاج إلى إلغاء onDraw() إذا كنت تريد أن يعرض المكوّن شيئًا. على الرغم من أنّ كلتيهما تتضمّنان سلوكًا تلقائيًا، فإنّ onDraw() التلقائية لا تفعل أي شيء، بينما تضبط onMeasure() التلقائية دائمًا حجمًا يبلغ 100×100، وهو ما من المحتمل ألا تريده.
  • يمكنك أيضًا إلغاء طُرق on الأخرى حسب الحاجة.

توسيع onDraw() وonMeasure()

توفّر الطريقة onDraw() عنصر Canvas يمكنك من خلاله تنفيذ أي شيء تريده: رسومات ثنائية الأبعاد أو مكوّنات أخرى عادية أو مخصّصة أو نص منمّق أو أي شيء آخر يخطر ببالك.

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

على مستوى عالٍ، يبدو تنفيذ onMeasure() على النحو التالي:

  • يتم استدعاء الطريقة onMeasure() التي تم إلغاؤها باستخدام مواصفات العرض والارتفاع التي تُعامَل كمتطلبات للقيود المفروضة على قياسات العرض والارتفاع التي تُنتجها. كلّ من المَعلمتَين widthMeasureSpec وheightMeasureSpec هما رمزَا عدد صحيح يمثّلان الأبعاد. يمكنك الاطّلاع على مرجع كامل لنوع القيود التي يمكن أن تتطلبها هذه المواصفات في المستندات المرجعية ضمن View.onMeasure(int, int) تشرح هذه المستندات المرجعية أيضًا عملية القياس بأكملها.
  • تحسب الطريقة onMeasure() في المكوّن عرضًا وارتفاعًا للقياس، وهما مطلوبان لعرض المكوّن. يجب محاولة الالتزام بالمواصفات التي تم تمريرها ، على الرغم من إمكانية تجاوزها. في هذه الحالة، يمكن للعنصر الرئيسي اختيار ما يجب فعله، بما في ذلك الاقتصاص أو التمرير أو عرض استثناء أو الطلب من onMeasure() المحاولة مرة أخرى، ربما باستخدام مواصفات قياس مختلفة.
  • عند حساب العرض والارتفاع، استدعِ الطريقة setMeasuredDimension(int width, int height) باستخدام القياسات المحسوبة. سيؤدي عدم إجراء ذلك إلى حدوث استثناء.

في ما يلي ملخّص للطُرق العادية الأخرى التي يستدعيها إطار العمل في طرق العرض:

الفئة الطرق الوصف
الإنشاء الشركات المصنِّعة هناك نموذج من دالة الإنشاء يتم استدعاؤه عند إنشاء طريقة العرض من الرمز ونموذج يتم استدعاؤه عند توسيع طريقة العرض من ملف تنسيق. يحلّل النموذج الثاني السمات المحدّدة في ملف التنسيق ويطبّقها.
onFinishInflate() يتم استدعاؤها بعد توسيع طريقة العرض وجميع العناصر التابعة لها من ملف XML.
التنسيق onMeasure(int, int) يتم استدعاؤها لتحديد متطلبات الحجم لطريقة العرض هذه وجميع العناصر التابعة لها.
onLayout(boolean, int, int, int, int) يتم استدعاؤها عندما يجب أن تحدّد طريقة العرض هذه حجمًا وموضعًا لجميع العناصر التابعة لها.
onSizeChanged(int, int, int, int) يتم استدعاؤها عند تغيير حجم طريقة العرض هذه.
الرسم onDraw(Canvas) يتم استدعاؤها عندما يجب أن تعرض طريقة العرض محتواها.
معالجة الأحداث onKeyDown(int, KeyEvent) يتم استدعاؤها عند وقوع حدث الضغط على مفتاح.
onKeyUp(int, KeyEvent) يتم استدعاؤها عند وقوع حدث رفع الإصبع عن مفتاح.
onTrackballEvent(MotionEvent) يتم استدعاؤها عند وقوع حدث حركة كرة التتبّع.
onTouchEvent(MotionEvent) يتم استدعاؤها عند وقوع حدث حركة شاشة اللمس.
التركيز onFocusChanged(boolean, int, Rect) يتم استدعاؤها عندما تكتسب طريقة العرض التركيز أو تفقده.
onWindowFocusChanged(boolean) يتم استدعاؤها عندما تكتسب النافذة التي تحتوي على طريقة العرض التركيز أو تفقده.
الإرفاق onAttachedToWindow() يتم استدعاؤها عند إرفاق طريقة العرض بنافذة.
onDetachedFromWindow() يتم استدعاؤها عند فصل طريقة العرض عن نافذتها.
onWindowVisibilityChanged(int) يتم استدعاؤها عند تغيير مستوى ظهور النافذة التي تحتوي على طريقة العرض.

عناصر التحكّم المركّبة

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

في Android، هناك طريقتَا عرض أخريان متاحتان بسهولة لإجراء ذلك، وهما Spinner وAutoCompleteTextView. على أي حال، يقدّم هذا المفهوم لمربّع التحرير والسرد مثالاً جيدًا.

لإنشاء مكوّن مركّب، اتّبِع الخطوات التالية:

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

هناك مزايا لاستخدام Layout كأساس لعنصر تحكّم مخصّص، بما في ذلك ما يلي:

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

تعديل نوع عرض حالي

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

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

إذا لم تكن قد فعلت ذلك بعد، استورِد نموذج NotePad إلى استوديو Android أو اطّلِع على المصدر باستخدام الرابط المقدَّم. على وجه الخصوص، اطّلِع على تعريف LinedEditText في NoteEditor.java الملف.

في ما يلي بعض النقاط التي يجب ملاحظتها في هذا الملف:

  1. التعريف

    يتم تعريف الفئة بالسطر التالي:
    public static class LinedEditText extends EditText

    LinedEditText يتم تعريفها كفئة داخلية ضمن نشاط NoteEditor ، ولكنّها عامة حتى يمكن الوصول إليها باسم NoteEditor.LinedEditText من خارج فئة NoteEditor.

    بالإضافة إلى ذلك، فإنّ LinedEditText هي static، ما يعني أنّها لا تُنشئ الـ التي تُعرف باسم "الطُرق التركيبية" التي تسمح لها بالوصول إلى البيانات من الفئة الرئيسية. هذا يعني أنّها تتصرّف كفئة منفصلة بدلاً من أن تكون مرتبطة بشكل كبير بـ NoteEditor. هذه طريقة أكثر وضوحًا لإنشاء فئات داخلية إذا لم تكن بحاجة إلى الوصول إلى الحالة من الفئة الخارجية. يحافظ ذلك على صغر حجم الفئة التي تم إنشاؤها ويسمح باستخدامها بسهولة من فئات أخرى

    LinedEditText توسّع EditText، وهي طريقة العرض التي يجب تخصيصها في هذه الحالة. عند الانتهاء، يمكن أن تحلّ الفئة الجديدة محلّ طريقة عرض عادية EditText.

  2. تهيئة الفئة

    كالعادة، يتم استدعاء الفئة الأساسية أولاً. هذه ليست دالة إنشاء تلقائية، ولكنّها دالة إنشاء تتضمّن مَعلمات. يتم إنشاء EditText باستخدام هذه المَعلمات عند توسيعها من ملف تنسيق XML. وبالتالي، يجب أن تأخذ دالة الإنشاء هذه المَعلمات وتمرِّرها إلى دالة إنشاء الفئة الأساسية أيضًا.

  3. الطرق التي تم إلغاؤها

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

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

  4. المكوّن المخصّص

    أصبح لديك الآن المكوّن المخصّص، ولكن كيف يمكنك استخدامه؟ في مثال NotePad، يتم استخدام المكوّن المخصّص مباشرةً من التنسيق التعريفي، لذا اطّلِع على note_editor.xml في المجلد res/layout:

    <view xmlns:android="http://schemas.android.com/apk/res/android"
        class="com.example.android.notepad.NoteEditor$LinedEditText"
        android:id="@+id/note"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/transparent"
        android:padding="5dp"
        android:scrollbars="vertical"
        android:fadingEdge="vertical"
        android:gravity="top"
        android:textSize="22sp"
        android:capitalize="sentences"
    />

    يتم إنشاء المكوّن المخصّص كطريقة عرض عامة في ملف XML، ويتم تحديد الفئة باستخدام الحزمة الكاملة. تتم الإشارة إلى الفئة الداخلية التي تحدّدها باستخدام الرمز NoteEditor$LinedEditText، وهي طريقة عادية للإشارة إلى الفئات الداخلية في لغة برمجة Java.

    إذا لم يتم تعريف مكوّن طريقة العرض المخصّصة كفئة داخلية، يمكنك تعريف مكوّن طريقة العرض باستخدام اسم عنصر XML واستبعاد السمة class. مثلاً:

    <com.example.android.notepad.LinedEditText
      id="@+id/note"
      ... />

    لاحظ أنّ الفئة LinedEditText أصبحت الآن ملف فئة منفصلاً. عندما يتم تضمين الفئة في الفئة NoteEditor، لا تنجح هذه التقنية.

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

إنشاء مكوّنات مخصّصة ليس معقدًا إلا بقدر ما تحتاج إليه.

يمكن أن يلغي مكوّن أكثر تطورًا طُرق on أكثر ويُدخل طُرقًا مساعِدة خاصة به، ما يؤدي إلى تخصيص خصائصه وسلوكه بشكل كبير. الحدود الوحيدة هي خيالك وما تحتاج إلى أن يفعله المكوّن.