تصف هذه الصفحة كيفية التعامل مع الأحجام وتوفير تخطيطات مرنة وسريعة الاستجابة باستخدام ميزة "نظرة سريعة".
استخدام Box
وColumn
وRow
تتضمّن ميزة Glance ثلاثة تنسيقات رئيسية قابلة للإنشاء:
Box
: يضع العناصر فوق عناصر أخرى تتم ترجمتها إلىRelativeLayout
.Column
: يضع العناصر بعد بعضها في المحور الرأسي تتم ترجمتها إلىLinearLayout
باتجاه عمودي.Row
: يضع العناصر بعد بعضها في المحور الأفقي. تتم ترجمتها إلىLinearLayout
باتجاه أفقي.
يتيح لك كل عنصر من هذه العناصر القابلة للإنشاء تحديد المحاذاة العمودية والأفقية للمحتوى وقيود العرض أو الارتفاع أو الوزن أو المساحة المتروكة باستخدام عوامل التعديل. بالإضافة إلى ذلك، يمكن لكل طفل تحديد المعدِّل لتغيير المسافة والموضع داخل العنصر الرئيسي.
يوضح لك المثال التالي كيفية إنشاء 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, key = { person -> person.id }) { person -> Text(person.name) }
تحديد SizeMode
قد تختلف أحجام AppWidget
بناءً على الجهاز أو اختيار المستخدم أو مشغّل التطبيقات،
لذلك من المهم توفير تنسيقات مرنة كما هو موضّح في صفحة توفير
تنسيقات مرنة للتطبيقات. وتعمل ميزة "نظرة سريعة" على تبسيط هذه العملية من خلال استخدام
تعريف 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 x 100 | 80 x 100 | 80 x 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", )
إضافة أزرار مركبة
تم إطلاق الأزرار المركّبة في Android 12. تدعم ميزة Glance التوافق مع الأنظمة العكسية للأنواع التالية من الأزرار المركّبة:
يعرض كل من هذه الأزرار المركّبة عرضًا قابلاً للنقر يمثل حالة "تم التحديد".
var isApplesChecked by remember { mutableStateOf(false) } var isEnabledSwitched by remember { mutableStateOf(false) } var isRadioChecked by remember { mutableStateOf(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) ), )