إنشاء واجهة مستخدم من خلال ميزة "نظرة سريعة"

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

استخدِم Box وColumn وRow.

تتضمّن ميزة "نظرة سريعة" ثلاثة تنسيقات رئيسية قابلة للتجميع:

  • Box: تضع العناصر فوق بعضها. ويُترجم إلى RelativeLayout.

  • Column: تضع العناصر بعد بعضها في المحور العمودي. ويتم تحويله إلى LinearLayout باتجاه عمودي.

  • Row: تضع العناصر بعد بعضها في المحور الأفقي. ويتم تحويله إلى LinearLayout باتجاه أفقي.

تتيح ميزة "نظرة سريعة" عرض عناصر Scaffold. ضَع العناصر القابلة للتجميع Column وRow و Box داخل كائن Scaffold معيّن.

صورة لتنسيق عمود وصف ومربّع
الشكل 1. أمثلة على التنسيقات التي تتضمّن العمود والصف والصندوق

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

يوضّح لك المثال التالي كيفية إنشاء 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 حسب الجهاز أو اختيار المستخدم أو مشغّل التطبيقات، لذلك من المهم توفير تنسيقات مرنة كما هو موضّح في صفحة توفير 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 x ‏110 203 x ‏112 ‫72 x ‏72 203 x ‏150
SizeMode.Single 110 x ‏110 110 x ‏110 110 x ‏110 110 x ‏110
SizeMode.Exact 105 x ‏110 203 x ‏112 ‫72 x ‏72 203 x ‏150
SizeMode.Responsive ‎80 x 100 ‎80 x 100 ‎80 x 100 ‎150 x 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 { 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)
    ),

)

المكوّنات الإضافية

يتضمّن الإصدار 1.1.0 من Glance إصدار مكونات إضافية، كما هو موضّح في الجدول التالي:

الاسم صورة رابط المرجع ملاحظات إضافية
زر ملون alt_text المكوّن
الأزرار المحدَّدة الجوانب alt_text المكوّن
أزرار الرموز alt_text المكوّن أساسية / ثانوية / رمز فقط
شريط العنوان alt_text المكوّن
سقالة يتوفّر إطار العمل وشريط العنوان في العرض التجريبي نفسه.

لمزيد من المعلومات حول تفاصيل التصميم، اطّلِع على تصميمات المكوّنات في مجموعة التصميم هذه على Figma.

لمزيد من المعلومات حول التنسيقات الأساسية، يُرجى الانتقال إلى مقالة تنسيقات التطبيقات المصغّرة الأساسية.