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

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

استخدام 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 بدءًا من الإصدار 12 من نظام التشغيل Android والإصدارات الأحدث (على سبيل المثال، عند إضافة عناصر أو إزالتها من القائمة). يوضّح المثال التالي كيفية تحديد 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 × 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"
)

إضافة أزرار مركبة

تم تقديم الأزرار المركّبة في الإصدار 12 من نظام التشغيل Android. تدعم ميزة "نظرة سريعة" التوافق مع الإصدارات العكسية للأنواع التالية من الأزرار المركبة:

يعرض كل من هذه الأزرار المركّبة عرضًا قابلاً للنقر يمثل الحالة "محدد".

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 إصدار مكونات إضافية، كما هو موضح في الجدول التالي:

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

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