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

تجربة طريقة الإنشاء
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. استخدِم فئة الإضافة الجديدة. وبعد اكتمالها، يمكنك استخدام فئة الإضافة الجديدة بدلاً من الملف الشخصي الذي استندت إليه.

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

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

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

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

  • العرض الأكثر عمومية الذي يمكنك تمديده هو View، لذلك تبدأ عادةً بتوسيعه لإنشاء المكوّن المميز الجديد.
  • يمكنك توفير دالة إنشائية يمكن أن تأخذ سمات ومعلَمات من ملف XML، ويمكنك استهلاك السمات والمَعلمات الخاصة بك، مثل لون ونطاق مقياس VU أو عرض الإبرة وترطيبها.
  • قد تحتاج إلى إنشاء أدوات خاصة بك لمعالجة الأحداث وموصّلات المواقع وأدوات التعديل، بالإضافة إلى سلوك أكثر تعقيدًا في فئة المكوّنات.
  • أنت بالتأكيد تريد إلغاء onMeasure() وربما تحتاج أيضًا إلى إلغاء onDraw() إذا كنت تريد أن يعرض المكوِّن شيئًا. على الرغم من أنّ لكل منهما سلوكًا تلقائيًا، لا تفعل السمة onDraw() التلقائية أي إجراء، أمّا القيمة التلقائية onMeasure() فيتم ضبطها دائمًا على حجم 100x100، وهو ما قد لا تريده.
  • يمكنك أيضًا إلغاء طُرق 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 Studio أو الاطّلاع على المصدر باستخدام الرابط المتوفر. وعلى وجه الخصوص، يمكنك الاطّلاع على تعريف 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 وتقديم أساليب مساعِدة خاصة به، ما يؤدي إلى تخصيص خصائصه وسلوكه بشكل كبير. الحد الوحيد هو خيالك وما تحتاج إلى المكون للقيام به.