توضّح هذه الصفحة كيفية التعامل مع الأحجام وتوفير تصاميم مرنة ومتجاوبة باستخدام مكوّنات Glance الحالية.
استخدِم Box وColumn وRow
تتضمّن Glance ثلاثة تنسيقات رئيسية للدوال المركّبة:
Box: يضع العناصر فوق بعضها. وهذا يعني أنّك ستحصل علىRelativeLayout.
Column: يضع العناصر بعد بعضها في المحور العمودي. ويعادل ذلكLinearLayoutباتجاه عمودي.
Row: يضع العناصر بعد بعضها في المحور الأفقي. ويتم ترجمتها إلىLinearLayoutباتجاه أفقي.
يتيح Glance استخدام عناصر Scaffold. ضَع الدوال المركّبة Column وRow وBox ضمن عنصر Scaffold معيّن.
تتيح لك كل دالة من هذه الدوال المركّبة تحديد المحاذاة الرأسية والأفقية لمحتواها وقيود العرض أو الارتفاع أو الوزن أو المساحة المتروكة باستخدام المعدِّلات. بالإضافة إلى ذلك، يمكن لكل عنصر فرعي تحديد المُعدِّل الخاص به لتغيير المساحة والموضع داخل العنصر الرئيسي.
يوضّح المثال التالي كيفية إنشاء Row يوزّع عناصره الفرعية بشكل متساوٍ أفقيًا، كما هو موضّح في الشكل 1:
Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) { val modifier = GlanceModifier.defaultWeight() Text("first", modifier) Text("second", modifier) Text("third", modifier) }
يملأ العنصر Row الحد الأقصى للعرض المتاح، وبما أنّ كل عنصر فرعي له الوزن نفسه،
يتم تقسيم المساحة المتاحة بينها بالتساوي. يمكنك تحديد أوزان أو أحجام أو هوامش أو محاذاة مختلفة لتكييف التنسيقات مع احتياجاتك.
استخدام التنسيقات القابلة للتمرير
هناك طريقة أخرى لتوفير محتوى متجاوب وهي جعله قابلاً للتمرير. ويمكنك إجراء ذلك باستخدام الدالة المركّبة LazyColumn. تتيح لك هذه الدالة المركّبة تحديد مجموعة
من العناصر التي سيتم عرضها داخل حاوية قابلة للتمرير في تطبيق مصغّر.
تعرض المقتطفات التالية طرقًا مختلفة لتحديد العناصر داخل
LazyColumn.
يمكنك تقديم عدد السلع:
// Remember to import Glance Composables // import androidx.glance.appwidget.layout.LazyColumn LazyColumn { items(10) { index: Int -> Text( text = "Item $index", modifier = GlanceModifier.fillMaxWidth() ) } }
تقديم عناصر فردية:
LazyColumn { item { Text("First Item") } item { Text("Second Item") } }
قدِّم قائمة أو مصفوفة من العناصر:
LazyColumn { items(peopleNameList) { name -> Text(name) } }
يمكنك أيضًا استخدام مجموعة من الأمثلة السابقة:
LazyColumn { item { Text("Names:") } items(peopleNameList) { name -> Text(name) } // or in case you need the index: itemsIndexed(peopleNameList) { index, person -> Text("$person at index $index") } }
يُرجى العِلم أنّ المقتطف السابق لا يحدّد itemId. يساعد تحديد
itemId في تحسين الأداء والحفاظ على موضع الانتقال
من خلال تحديثات القائمة وappWidget من Android 12 فصاعدًا (على سبيل المثال،
عند إضافة عناصر إلى القائمة أو إزالتها منها). يوضّح المثال التالي كيفية تحديد itemId:
items( items = peopleList, itemId = { person -> person.id.hashCode().toLong() }) { person -> Text(person.name) }
التمرير السريع
التمرير السريع هو رسم متحرك يسمح للمحتوى القابل للتمرير بالانتقال إلى أعلى حاوية التطبيق المصغّر.
لتنفيذ ميزة "التمرير السريع"، تأكَّد من استيفاء الشروط التالية:
- حدِّث تبعية Glance إلى الإصدار 1.3.0-alpha02 أو إصدار أحدث.
- اضبط
compileSdkعلى 37 أو أعلى، لأنّ ميزة "التمرير السريع" متاحة على الأجهزة التي تعمل بنظام التشغيل Android 17 والإصدارات الأحدث. - اضبط إعدادات "
LazyColumn" باستخدام "VerticalScrollMode". إذا كان الجهاز يتيح ميزة التمرير السريع، استخدِمSnapScrollMatchHeight. ويمكنك بدلاً من ذلك استخدامNormal.
إذا كنت تستخدم ميزة "التمرير السريع" مع الصور، يمكنك الاطّلاع على التنسيق الأساسي صورة كاملة.
@Composable fun SnapScrollLayout() { val height = LocalSize.current.height val items = listOf( ColorItem(Color.Red, "Red"), ColorItem(Color.Yellow, "Yellow"), ColorItem(Color.Blue, "Blue") ) val scrollMode = if (Build.VERSION.SDK_INT >= 37) { VerticalScrollMode.SnapScrollMatchHeight(height) } else { VerticalScrollMode.Normal } LazyColumn( verticalScrollMode = scrollMode ) { items(items) { item -> ColorCard(item, height) } } } @Composable private fun ColorCard(item: ColorItem, height: Dp) { Box( modifier = GlanceModifier .background(item.color) .fillMaxWidth() .height(height), contentAlignment = Alignment.Center ) { Text( text = item.name, modifier = GlanceModifier.background(Color.White) ) } }
تحديد SizeMode
AppWidget قد تختلف الأحجام حسب الجهاز أو خيار المستخدم أو مشغّل التطبيقات، لذا من المهم توفير تنسيقات مرنة كما هو موضّح في صفحة توفير تنسيقات مرنة للأدوات. تسهّل Glance هذه العملية من خلال تعريف SizeMode وقيمة LocalSize. توضّح الأقسام التالية الأوضاع الثلاثة.
SizeMode.Single
SizeMode.Single هو الوضع التلقائي. ويشير ذلك إلى أنّه يتم توفير نوع واحد فقط من المحتوى، أي أنّه حتى إذا تغيّر حجم AppWidget المتاح، لن يتغيّر حجم المحتوى.
class MyAppWidget : GlanceAppWidget() { override val sizeMode = SizeMode.Single override suspend fun provideGlance(context: Context, id: GlanceId) { // ... provideContent { MyContent() } } @Composable private fun MyContent() { // Size will be the minimum size or resizable // size defined in the App Widget metadata val size = LocalSize.current // ... } }
عند استخدام هذا الوضع، تأكَّد مما يلي:
- يتم تحديد قيم البيانات الوصفية للحد الأدنى والحد الأقصى للحجم بشكل صحيح استنادًا إلى حجم المحتوى.
- المحتوى مرن بما يكفي ضمن نطاق الحجم المتوقّع.
بشكل عام، يجب استخدام هذا الوضع في الحالات التالية:
أ) أن يكون حجم AppWidget ثابتًا، أو
ب) ألا يتغيّر محتواه عند تغيير حجمه.
SizeMode.Responsive
هذا الوضع يعادل توفير تخطيطات سريعة الاستجابة، ما يسمح
لـ GlanceAppWidget بتحديد مجموعة من التخطيطات سريعة الاستجابة التي تقتصر على أحجام
محددة. لكل حجم محدّد، يتم إنشاء المحتوى وربطه بالحجم المحدّد عند إنشاء AppWidget أو تعديله. يختار النظام بعد ذلك أفضل إعلان مناسب استنادًا إلى الحجم المتاح.
على سبيل المثال، في وجهتنا AppWidget، يمكنك تحديد ثلاثة أحجام
ومحتواها:
class MyAppWidget : GlanceAppWidget() { companion object { private val SMALL_SQUARE = DpSize(100.dp, 100.dp) private val HORIZONTAL_RECTANGLE = DpSize(250.dp, 100.dp) private val BIG_SQUARE = DpSize(250.dp, 250.dp) } override val sizeMode = SizeMode.Responsive( setOf( SMALL_SQUARE, HORIZONTAL_RECTANGLE, BIG_SQUARE ) ) override suspend fun provideGlance(context: Context, id: GlanceId) { // ... provideContent { MyContent() } } @Composable private fun MyContent() { // Size will be one of the sizes defined above. val size = LocalSize.current Column { if (size.height >= BIG_SQUARE.height) { Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp)) } Row(horizontalAlignment = Alignment.CenterHorizontally) { Button() Button() if (size.width >= HORIZONTAL_RECTANGLE.width) { Button("School") } } if (size.height >= BIG_SQUARE.height) { Text(text = "provided by X") } } } }
في المثال السابق، يتم استدعاء الطريقة provideContent ثلاث مرات ويتم ربطها بالحجم المحدّد.
- في الاستدعاء الأول، يتم تقييم الحجم على أنّه
100x100. لا يتضمّن المحتوى الزر الإضافي، ولا النصوص العلوية والسفلية. - في الاستدعاء الثاني، يتم تقييم الحجم إلى
250x100. يتضمن المحتوى الزر الإضافي، ولكن ليس النصوص العلوية والسفلية. - في الاستدعاء الثالث، يتم تقييم الحجم على أنّه
250x250. يتضمن المحتوى الزر الإضافي وكلا النصين.
يجمع وضع SizeMode.Responsive بين الوضعَين الآخرَين، ويتيح لك
تحديد محتوى متجاوب ضمن حدود محدّدة مسبقًا. بشكل عام، يقدّم هذا الوضع أداءً أفضل ويتيح إجراء انتقالات أكثر سلاسة عند تغيير حجم "AppWidget".
يوضّح الجدول التالي قيمة الحجم، استنادًا إلى SizeMode وAppWidget الحجم المتاح:
| المقاس المتوفّر | 105 × 110 | 203 × 112 | 72 × 72 | 203 × 150 |
|---|---|---|---|---|
SizeMode.Single |
110 × 110 | 110 × 110 | 110 × 110 | 110 × 110 |
SizeMode.Exact |
105 × 110 | 203 × 112 | 72 × 72 | 203 × 150 |
SizeMode.Responsive |
80 × 100 | 80 × 100 | 80 × 100 | 150 × 120 |
| * القيم الدقيقة هي لأغراض العرض التوضيحي فقط. |
SizeMode.Exact
SizeMode.Exact يعادل توفير تنسيقات دقيقة، ما يؤدي إلى طلب محتوى GlanceAppWidget في كل مرة يتغيّر فيها حجم AppWidget المتاح (على سبيل المثال، عندما يغيّر المستخدم حجم AppWidget في الشاشة الرئيسية).
على سبيل المثال، في تطبيق الوجهة المصغّر، يمكن إضافة زر إضافي إذا كان العرض المتاح أكبر من قيمة معيّنة.
class MyAppWidget : GlanceAppWidget() { override val sizeMode = SizeMode.Exact override suspend fun provideGlance(context: Context, id: GlanceId) { // ... provideContent { MyContent() } } @Composable private fun MyContent() { // Size will be the size of the AppWidget val size = LocalSize.current Column { Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp)) Row(horizontalAlignment = Alignment.CenterHorizontally) { Button() Button() if (size.width > 250.dp) { Button("School") } } } } }
يوفّر هذا الوضع مرونة أكبر من الأوضاع الأخرى، ولكنّه يتضمّن بعض التحذيرات:
- يجب إعادة إنشاء
AppWidgetبالكامل في كل مرة يتغيّر فيها الحجم. وقد يؤدي ذلك إلى حدوث مشاكل في الأداء وقفزات في واجهة المستخدم عندما يكون المحتوى معقدًا. - وقد يختلف الحجم المتاح حسب طريقة تنفيذ مشغّل التطبيقات. على سبيل المثال، إذا لم يوفّر مشغّل التطبيقات قائمة بالأحجام، يتم استخدام الحد الأدنى للحجم الممكن.
- في الأجهزة التي تعمل بإصدارات أقدم من Android 12، قد لا يعمل منطق احتساب الحجم في جميع الحالات.
بشكل عام، يجب استخدام هذا الوضع إذا تعذّر استخدام SizeMode.Responsive (أي إذا لم تكن مجموعة صغيرة من التنسيقات المتجاوبة ممكنة).
الوصول إلى المراجع
استخدِم LocalContext.current للوصول إلى أي مورد Android، كما هو موضّح في المثال التالي:
LocalContext.current.getString(R.string.glance_title)
ننصحك بتقديم معرّفات الموارد مباشرةً لتقليل حجم عنصر RemoteViews النهائي وتفعيل الموارد الديناميكية، مثل الألوان الديناميكية.
تقبل الدوال المركّبة والطرق الموارد باستخدام "موفّر"، مثل
ImageProvider، أو باستخدام طريقة تحميل زائد مثل
GlanceModifier.background(R.color.blue). على سبيل المثال:
Column( modifier = GlanceModifier.background(R.color.default_widget_background) ) { /**...*/ } Image( provider = ImageProvider(R.drawable.ic_logo), contentDescription = "My image", )
نص الاسم المعرِّف
يتضمّن الإصدار 1.1.0 من Glance واجهة برمجة تطبيقات لضبط أنماط النص. يمكنك ضبط أنماط النص باستخدام سمات fontSize أو fontWeight أو fontFamily لفئة TextStyle.
يتيح fontFamily استخدام جميع خطوط النظام، كما هو موضّح في المثال التالي، ولكن لا يمكن استخدام الخطوط المخصّصة في التطبيقات:
Text( style = TextStyle( fontWeight = FontWeight.Bold, fontSize = 18.sp, fontFamily = FontFamily.Monospace ), text = "Example Text" )
إضافة أزرار مركّبة
تم طرح الأزرار المركّبة في Android 12. يتيح Glance التوافق مع الأنظمة القديمة لأنواع الأزرار المركّبة التالية:
يعرض كل زر من هذه الأزرار المركّبة طريقة عرض قابلة للنقر تمثّل الحالة "محدَّد".
var isApplesChecked by remember { mutableStateOf(false) } var isEnabledSwitched by remember { mutableStateOf(false) } var isRadioChecked by remember { mutableIntStateOf(0) } CheckBox( checked = isApplesChecked, onCheckedChange = { isApplesChecked = !isApplesChecked }, text = "Apples" ) Switch( checked = isEnabledSwitched, onCheckedChange = { isEnabledSwitched = !isEnabledSwitched }, text = "Enabled" ) RadioButton( checked = isRadioChecked == 1, onClick = { isRadioChecked = 1 }, text = "Checked" )
عندما تتغيّر الحالة، يتم تشغيل دالة Lambda المقدَّمة. يمكنك تخزين حالة الاختيار، كما هو موضّح في المثال التالي:
class MyAppWidget : GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { val myRepository = MyRepository.getInstance() provideContent { val scope = rememberCoroutineScope() val saveApple: (Boolean) -> Unit = { scope.launch { myRepository.saveApple(it) } } MyContent(saveApple) } } @Composable private fun MyContent(saveApple: (Boolean) -> Unit) { var isAppleChecked by remember { mutableStateOf(false) } Button( text = "Save", onClick = { saveApple(isAppleChecked) } ) } }
يمكنك أيضًا تقديم السمة colors إلى CheckBox وSwitch وRadioButton لتخصيص ألوانها:
CheckBox( // ... colors = CheckboxDefaults.colors( checkedColor = ColorProvider(day = colorAccentDay, night = colorAccentNight), uncheckedColor = ColorProvider(day = Color.DarkGray, night = Color.LightGray) ), checked = isChecked, onCheckedChange = { isChecked = !isChecked } ) Switch( // ... colors = SwitchDefaults.colors( checkedThumbColor = ColorProvider(day = Color.Red, night = Color.Cyan), uncheckedThumbColor = ColorProvider(day = Color.Green, night = Color.Magenta), checkedTrackColor = ColorProvider(day = Color.Blue, night = Color.Yellow), uncheckedTrackColor = ColorProvider(day = Color.Magenta, night = Color.Green) ), checked = isChecked, onCheckedChange = { isChecked = !isChecked }, text = "Enabled" ) RadioButton( // ... colors = RadioButtonDefaults.colors( checkedColor = ColorProvider(day = Color.Cyan, night = Color.Yellow), uncheckedColor = ColorProvider(day = Color.Red, night = Color.Blue) ), )
المكوّنات الإضافية
يتضمّن الإصدار 1.1.0 من Glance مكوّنات إضافية، كما هو موضّح في الجدول التالي:
| الاسم | صورة | رابط مرجعي | ملاحظات إضافية |
|---|---|---|---|
| زر معبأ |
|
المكوّن | |
| أزرار المخطط |
|
المكوّن | |
| أزرار الرموز |
|
المكوّن | أساسي / ثانوي / رمز فقط |
| شريط العنوان |
|
المكوّن | |
| Scaffold | يظهر Scaffold وشريط العنوان في العرض التوضيحي نفسه. |
لمزيد من المعلومات حول تفاصيل التصميم، يمكنك الاطّلاع على تصاميم المكوّنات في مجموعة التصميم هذه على Figma.
لمزيد من المعلومات حول التنسيقات الأساسية، يُرجى الانتقال إلى تنسيقات التطبيقات المصغّرة الأساسية.