على الرغم من أنّ الانتقال من طرق العرض إلى "إنشاء" مرتبط فقط بواجهة المستخدم، هناك الكثير من الأمور التي يجب أخذها في الاعتبار لإجراء نقل بيانات آمن وتدريجي. تحتوي هذه الصفحة على بعض الاعتبارات أثناء نقل تطبيقك المستند إلى طريقة العرض إلى ميزة "إنشاء".
نقل مظهر التطبيق
يُعدّ Material Design نظام التصميم المُقترَح لاستخدامه في تصميم تطبيقات Android.
بالنسبة إلى التطبيقات المستندة إلى View، تتوفّر ثلاثة إصدارات من Material:
- لغة تصميم Material Design 1 باستخدام مكتبة
AppCompat (أي
Theme.AppCompat.*
) - Material Design 2 باستخدام مكتبة
MDC-Android (أي
Theme.MaterialComponents.*
) - Material Design 3 باستخدام مكتبة
MDC-Android (أي
Theme.Material3.*
)
بالنسبة إلى تطبيقات Compose، يتوفّر إصداران من Material:
- Material Design 2 باستخدام مكتبة
Compose Material
(أي
androidx.compose.material.MaterialTheme
) - Material Design 3 باستخدام مكتبة
Compose Material 3
(أي
androidx.compose.material3.MaterialTheme
)
ننصحك باستخدام أحدث إصدار (Material 3) إذا كان نظام تصميم تطبيقك يسمح بذلك. تتوفّر أدلة نقل البيانات لكلٍّ من "العروض" و"الإنشاء":
- المادّة 1 إلى المادة 2 في "المشاهدات"
- المادة 2 إلى المادة 3 في طرق العرض
- المادّة 2 إلى المادة 3 في ميزة "الإنشاء"
عند إنشاء شاشات جديدة في Compose، بغض النظر عن إصدار Material
Design الذي تستخدمه، تأكَّد من تطبيق MaterialTheme
قبل أي
عناصر قابلة للتجميع تُنشئ واجهة مستخدم من مكتبات Compose Material. وتعتمد مكوّنات المواد (Button
وText
وما إلى ذلك) على توفُّر MaterialTheme
، ويكون سلوكها بدونها غير محدَّد.
تستخدِم كل عيّنات Jetpack Compose
مظهرًا مخصّصًا لتطبيق Compose تم إنشاؤه استنادًا إلى MaterialTheme
.
اطّلِع على أنظمة التصميم في Compose ونقل مظاهر XML إلى Compose لمزيد من المعلومات.
التنقّل
إذا كنت تستخدم مكوّن Navigation في تطبيقك، يمكنك الاطّلاع على التنقّل باستخدام Compose - إمكانية التشغيل التفاعلي ونقل بيانات Jetpack Navigation إلى Navigation Compose للحصول على مزيد من المعلومات.
اختبار واجهة المستخدم المختلطة/طريقة العرض
بعد نقل أجزاء من تطبيقك إلى Compose، من المهم اختبارها للتأكّد من عدم حدوث أي مشاكل.
عندما يستخدم نشاط أو جزء ميزة "إنشاء"، يجب استخدام createAndroidComposeRule
بدلاً من ActivityScenarioRule
. يدمج createAndroidComposeRule
ActivityScenarioRule
مع ComposeTestRule
يتيح لك اختبار ميزة "إنشاء" و
عرض الرمز في الوقت نفسه.
class MyActivityTest { @Rule @JvmField val composeTestRule = createAndroidComposeRule<MyActivity>() @Test fun testGreeting() { val greeting = InstrumentationRegistry.getInstrumentation() .targetContext.resources.getString(R.string.greeting) composeTestRule.onNodeWithText(greeting).assertIsDisplayed() } }
اطّلِع على اختبار تنسيق ميزة "الإنشاء" لمعرفة المزيد من المعلومات عن الاختبار. للاطّلاع على معلومات عن التوافق مع إطارات عمل اختبار واجهة المستخدم، يمكنك الاطّلاع على مقالتَي التوافق مع Espresso والتوافق مع UiAutomator.
دمج ميزة Compose مع بنية تطبيقك الحالية
تعمل نماذج بنية تدفق البيانات أحادي الاتجاه (UDF) بسلاسة مع Compose. إذا كان التطبيق يستخدم أنواعًا أخرى من أنماط التصميم بدلاً من ذلك، مثل Model View Presenter (MVP)، ننصحك بنقل هذا الجزء من واجهة المستخدم إلى UDF قبل استخدام Compose أو أثناء استخدامه.
استخدام ViewModel
في ميزة "الكتابة الذكية"
إذا كنت تستخدم مكتبة المكوّنات الأساسية
ViewModel
، يمكنك الوصول إلى
ViewModel
من أي عنصر قابل للإنشاء من خلال
استدعاء الدالة
viewModel()
كما هو موضّح في Compose والمكتبات الأخرى.
عند استخدام Compose، يجب الانتباه إلى استخدام نوع ViewModel
نفسه في
عناصر Compose المختلفة لأنّ عناصر ViewModel
تتّبع نطاقات دورة حياة View. سيكون
النطاق هو نشاط المضيف أو المقتطف أو الرسم البياني للتنقّل في حال استخدام
مكتبة التنقّل.
على سبيل المثال، إذا كانت العناصر القابلة للتجميع مستضافة في نشاط، viewModel()
يعرض دائمًا المثيل نفسه الذي لا يتم محوه إلا عند انتهاء النشاط.
في المثال التالي، يتمّ الترحيب بالمستخدم نفسه ("user1") مرّتين لأنّه تتمّ إعادة استخدام مثيل GreetingViewModel
نفسه في جميع العناصر القابلة للتجميع ضمن
نشاط المضيف. يُعاد استخدام مثيل ViewModel
الأول الذي تم إنشاؤه في عناصر أخرى قابلة للإنشاء.
class GreetingActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Column { GreetingScreen("user1") GreetingScreen("user2") } } } } } @Composable fun GreetingScreen( userId: String, viewModel: GreetingViewModel = viewModel( factory = GreetingViewModelFactory(userId) ) ) { val messageUser by viewModel.message.observeAsState("") Text(messageUser) } class GreetingViewModel(private val userId: String) : ViewModel() { private val _message = MutableLiveData("Hi $userId") val message: LiveData<String> = _message } class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return GreetingViewModel(userId) as T } }
بما أنّ الرسوم البيانية للتنقّل تشمل أيضًا عناصر ViewModel
، فإنّ العناصر القابلة للتجميع التي تشكل
وجهة في رسم بياني للتنقّل تحتوي على مثيل مختلف من ViewModel
.
في هذه الحالة، يتم ضبط نطاق ViewModel
على دورة حياة الوجهة، ويتم cleared عند إزالة الوجهة من الحزمة الخلفية. في المثال التالي، عندما ينتقل المستخدم إلى شاشة الملف الشخصي، يتم إنشاء مثيل جديد
لعنصر GreetingViewModel
.
@Composable fun MyApp() { NavHost(rememberNavController(), startDestination = "profile/{userId}") { /* ... */ composable("profile/{userId}") { backStackEntry -> GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "") } } }
مصدر بيانات الحالة
عند استخدام ميزة "الإنشاء" في جزء من واجهة المستخدم، من المحتمل أن تحتاج ميزة "الإنشاء" و
رمز نظام العرض إلى مشاركة البيانات. ننصحك، كلما أمكن،
بتغليف هذه الحالة المشتركة في فئة أخرى تلتزم بأفضل ممارسات الدوالّ المخصّصة
التي تستخدمها كلتا المنصّتَين، على سبيل المثال، في ViewModel
التي تعرض مصدرًا
للبيانات المشتركة لإصدار تعديلات البيانات.
ومع ذلك، قد لا يكون ذلك ممكنًا دائمًا إذا كانت البيانات التي ستتم مشاركتها قابلة للتغيير أو مرتبطة ارتباطًا وثيقًا بعنصر واجهة مستخدم. في هذه الحالة، يجب أن يكون أحد النظامَين مصدر الحقيقة، ويجب أن يشارك هذا النظام أي تعديلات على البيانات مع النظام الآخر. كقاعدة عامة، يجب أن يكون مصدر المعلومات مملوكًا للعنصر الأقرب إلى جذر التدرّج الهرمي لواجهة المستخدم.
إنشاء المحتوى كمصدر للحقائق
استخدِم العنصر
SideEffect
composable لنشر حالة Compose في رمز غير Compose. في هذه الحالة، يتم الاحتفاظ
بمصدر المعلومات في عنصر قابل للتركيب يُرسِل تحديثات الحالة.
على سبيل المثال، قد تسمح لك مكتبة الإحصاءات بتقسيم قاعدة مستخدمي
موقعك الإلكتروني من خلال إرفاق بيانات وصفية مخصّصة (سمات المستخدِمين في هذا المثال)
بجميع أحداث الإحصاءات اللاحقة. لإرسال نوع المستخدِم
الحالي إلى مكتبة الإحصاءات، استخدِم SideEffect
لتعديل قيمته.
@Composable fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { FirebaseAnalytics() } // On every successful composition, update FirebaseAnalytics with // the userType from the current User, ensuring that future analytics // events have this metadata attached SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics }
لمزيد من المعلومات، يُرجى الاطّلاع على التأثيرات الجانبية في ميزة "الإنشاء".
عرض النظام كمصدر المعلومات
إذا كان نظام العرض يملك الحالة ويشاركها مع Compose، ننصحك
بتغليف الحالة في كائنات mutableStateOf
لجعلها آمنة لسلسلة المهام في
Compose. في حال استخدام هذا النهج، يتم تبسيط الدوالّ القابلة للتجميع لأنّه
لم يعُد لديها مصدر المعلومات، ولكنّ نظام View يحتاج إلى تعديل الحالة
المتغيّرة وViews التي تستخدم هذه الحالة.
في المثال التالي، يحتوي العنصر CustomViewGroup
على TextView
و
ComposeView
مع عنصر TextField
قابل للتركيب. يجب أن يعرض TextView
محتوى ما يطلبه المستخدم في TextField
.
class CustomViewGroup @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : LinearLayout(context, attrs, defStyle) { // Source of truth in the View system as mutableStateOf // to make it thread-safe for Compose private var text by mutableStateOf("") private val textView: TextView init { orientation = VERTICAL textView = TextView(context) val composeView = ComposeView(context).apply { setContent { MaterialTheme { TextField(value = text, onValueChange = { updateState(it) }) } } } addView(textView) addView(composeView) } // Update both the source of truth and the TextView private fun updateState(newValue: String) { text = newValue textView.text = newValue } }
نقل واجهة المستخدم المشتركة
إذا كنت بصدد نقل البيانات تدريجيًا إلى ميزة "الإنشاء"، قد تحتاج إلى استخدام عناصر واجهة مستخدم مشتركة في كلّ من "الإنشاء" ونظام "العرض". على سبيل المثال، إذا كان تطبيقك يتضمّن CallToActionButton
مخصّصًا، قد تحتاج إلى استخدامه في كلّ من الشاشات المستندة إلى CallToActionButton
والشاشات المستندة إلى العرض.
في أداة "الإنشاء"، تصبح عناصر واجهة المستخدم المشترَكة عناصر قابلة للتجميع يمكن إعادة استخدامها في
التطبيق، بغض النظر عمّا إذا كان العنصر مصمّمًا باستخدام XML أو كان عرضًا مخصّصًا. على سبيل المثال، يمكنك إنشاء عنصر CallToActionButton
قابل للتجميع لمكوّن Button
الدعوة إلى
العمل المخصّص.
لاستخدام عنصر قابل للإنشاء في الشاشات المستندة إلى العرض، أنشِئ برنامج تضمين طريقة عرض مخصّصة يمتد من AbstractComposeView
. في العنصر القابل للإنشاء Content
الذي تم إلغاء تحديده،
ضَع العنصر القابل للإنشاء الذي أنشأته مُغلفًا في موضوع Compose، كما هو موضّح في المثال التالي:
@Composable fun CallToActionButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, ) { Button( colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.secondary ), onClick = onClick, modifier = modifier, ) { Text(text) } } class CallToActionViewButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AbstractComposeView(context, attrs, defStyle) { var text by mutableStateOf("") var onClick by mutableStateOf({}) @Composable override fun Content() { YourAppTheme { CallToActionButton(text, onClick) } } }
يُرجى العِلم أنّ المَعلمات القابلة للتجميع تصبح متغيّرات قابلة للتغيير داخل الجدول المخصّص
. وهذا يجعل طريقة العرض المخصّصة CallToActionViewButton
قابلة للنفخ والاستخدام،
مثل طريقة العرض التقليدية. انظر مثالاً على ذلك من خلال ربط طريقة العرض
أدناه:
class ViewBindingActivity : ComponentActivity() { private lateinit var binding: ActivityExampleBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityExampleBinding.inflate(layoutInflater) setContentView(binding.root) binding.callToAction.apply { text = getString(R.string.greeting) onClick = { /* Do something */ } } } }
إذا كان المكوّن المخصّص يحتوي على حالة قابلة للتغيير، اطّلِع على مصدر حالة الحقيقة.
منح الأولوية لحالة التقسيم من العرض التقديمي
عادةً ما يكون View
مرتبطًا بحالة. يدير View
الحقول التي تصِف ما يتم عرضه، بالإضافة إلى كيفية عرضه. عند
تحويل View
إلى Compose، احرص على فصل البيانات التي يتم عرضها ل
تحقيق تدفق بيانات أحادي الاتجاه، كما هو موضّح بالتفصيل في تصعيد الحالة.
على سبيل المثال، يحتوي View
على سمة visibility
تصف ما إذا كان
مرئيًا أو غير مرئي أو تمّت إزالته. هذه سمة أساسية في View
. وفي حين أنّ أجزاء الرموز الأخرى قد تغيّر مستوى ظهور View
، فإنّ View
نفسه هو الوحيد الذي يعرف حقًا مستوى ظهوره الحالي. منطق ضمان أنّ View
يمكن أن يكون عُرضة للخطأ، وغالبًا ما يرتبط بـ View
نفسه.
على النقيض من ذلك، يُسهل Compose عرض عناصر مختلفة تمامًا قابلة للإنشاء باستخدام المنطق الشرطي في Kotlin:
@Composable fun MyComposable(showCautionIcon: Boolean) { if (showCautionIcon) { CautionIcon(/* ... */) } }
من الناحية التصميمية، لا يحتاج CautionIcon
إلى معرفة أو الاهتمام بسبب عرض المحتوى،
ولا مفهوم visibility
: إنه موضوع في المقطوعة الموسيقية أو
ليس كذلك.
من خلال الفصل الواضح بين إدارة الحالة ومنطق العرض، يمكنك تغيير طريقة عرض المحتوى بشكل أكثر حرية كتحويل للحالة إلى واجهة المستخدم. إنّ التمكّن من تصعيد الحالة عند الحاجة يجعل العناصر القابلة للتجميع أكثر قابلية لإعادة الاستخدام، لأنّ ملكية الحالة أكثر مرونة.
الترويج للمكونات المُدمجة والقابلة لإعادة الاستخدام
غالبًا ما يكون لدى عناصر View
فكرة عن مكانها: داخل Activity
أو
Dialog
أو Fragment
أو في مكان ما داخل تسلسل هرمي آخر من View
. وبما أنّ البنية الأساسية لـ View
غالبًا ما يتم تضخيمها من ملفات التنسيق الثابتة، تكون صلبة للغاية. وينتج عن ذلك إقران أكثر دقة، ويصعّب
تغيير View
أو إعادة استخدامه.
على سبيل المثال، قد يفترض View
مخصّص أنّه يحتوي على عرض فرعي من نوع معيّن
بمعرّف معيّن، ويغيّر خصائصه مباشرةً استجابةً لبعض
الإجراءات. يؤدي ذلك إلى ربط عناصر View
معًا بشكلٍ وثيق: قد يتعطل العنصر المخصّص View
أو يتعطّل إذا لم يتمكّن من العثور على العنصر الفرعي، ومن المحتمل أنّه لا يمكن
إعادة استخدام العنصر الفرعي بدون العنصر الرئيسي View
المخصّص.
لا يشكّل ذلك مشكلة كبيرة في تطبيق Compose باستخدام العناصر القابلة لإعادة الاستخدام. يمكن للوالدَين تحديد الحالة وطلبات إعادة الاتصال بسهولة، ما يتيح لك كتابة مكونات قابلة لإعادة الاستخدام بدون الحاجة إلى معرفة المكان الدقيق الذي سيتم استخدامها فيه.
@Composable fun AScreen() { var isEnabled by rememberSaveable { mutableStateOf(false) } Column { ImageWithEnabledOverlay(isEnabled) ControlPanelWithToggle( isEnabled = isEnabled, onEnabledChanged = { isEnabled = it } ) } }
في المثال أعلاه، تكون الأجزاء الثلاثة أكثر تجميعًا وأقل ارتباطًا:
لا يحتاج
ImageWithEnabledOverlay
إلى معرفة سوى الحالة الحالية لـisEnabled
. ولا يحتاج إلى معرفة أنّControlPanelWithToggle
متوفّر، أو حتى كيفية التحكّم فيه.لا يعرف
ControlPanelWithToggle
أنّImageWithEnabledOverlay
متوفّر. يمكن أن يكون هناك طريقة واحدة أو أكثر لعرضisEnabled
، ولا يلزم أن يتغيّرControlPanelWithToggle
.لا يهمّ العنصر الرئيسي مدى تداخل
ImageWithEnabledOverlay
أوControlPanelWithToggle
. وقد يضيف هؤلاء الأطفال تغييرات متحركة أو يبدّلون المحتوى أو يرسلونه إلى أطفال آخرين.
يُعرف هذا النمط باسم عكس التحكّم، ويمكنك الاطّلاع على مزيد من المعلومات
عنه في مستندات CompositionLocal
.
التعامل مع تغييرات حجم الشاشة
إنّ توفُّر موارد مختلفة لمختلف أحجام النوافذ هو إحدى الطرق الرئيسية ل
إنشاء تصاميم View
سريعة الاستجابة. وفي حين أن الموارد المؤهلة لا تزال خيارًا لقرارات التخطيط على مستوى الشاشة، فإن Compose تجعل من السهل تغيير
التخطيطات بالكامل في التعليمات البرمجية باستخدام المنطق الشرطي العادي. راجِع استخدام فئات حجم النوافذ لمعرفة المزيد من المعلومات.
بالإضافة إلى ذلك، يمكنك الاطّلاع على مقالة إتاحة التطبيق لأحجام شاشات مختلفة للتعرّف على التقنيات التي يوفّرها Compose لإنشاء واجهات مستخدم قابلة للتكيّف.
التمرير المدمَج مع طرق العرض
لمزيد من المعلومات حول كيفية تفعيل إمكانية التشغيل التفاعلي للانتقال المتداخل بين عناصر العرض القابلة للانتقال والمكوّنات القابلة للانتقال، والتي تكون متداخلة في كلا الاتجاهين، اطّلِع على مقالة إمكانية التشغيل التفاعلي للانتقال المتداخل.
إنشاء الرسائل في RecyclerView
العناصر القابلة للإنشاء باللغة RecyclerView
تحقّق أداءً جيدًا منذ الإصدار
1.3.0-alpha02 من RecyclerView
. يُرجى التأكّد من استخدام الإصدار 1.3.0-alpha02 على الأقل من
RecyclerView
للاطّلاع على هذه المزايا.
WindowInsets
إمكانية التشغيل التفاعلي مع "الملف الشخصي على Google"
قد تحتاج إلى إلغاء الأجزاء المضمّنة التلقائية عندما تحتوي شاشتك على كلٍّ من "طرق العرض" و "رمز الإنشاء" في التسلسل الهرمي نفسه. في هذه الحالة، عليك تحديد بوضوح العنصر الذي يجب أن يستخدِم المكوّنات المضمّنة والعنصر الذي يجب أن يتجاهلها.
على سبيل المثال، إذا كان التنسيق الخارجي هو تنسيق Android View، يجب
استخدام الأجزاء المضمّنة في نظام View وتجاهلها في Compose.
بدلاً من ذلك، إذا كان التنسيق الخارجي هو عنصر قابل للتركيب، يجب استخدام
العناصر المضمّنة في أداة "الإنشاء"، وإضافة مساحة بين العناصر القابلة للتركيب AndroidView
وفقًا لذلك.
يستهلك كل ComposeView
تلقائيًا جميع المكوّنات المضمّنة على مستوى الاستهلاك
WindowInsetsCompat
. لتغيير هذا السلوك التلقائي، اضبط
ComposeView.consumeWindowInsets
على false
.
لمزيد من المعلومات، اطّلِع على مستندات WindowInsets
في Compose.
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون لغة JavaScript غير مفعّلة.
- عرض رموز الإيموجي
- تصميم Material Design 2 في ميزة "الإنشاء"
- أجزاء النافذة المضمّنة في ميزة "الإنشاء"