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

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

استخدام 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.