ایجاد رابط کاربری با Glance

این صفحه نحوه مدیریت اندازه‌ها و ارائه طرح‌بندی‌های انعطاف‌پذیر و پاسخگو با Glance را با استفاده از مؤلفه‌های موجود Glance شرح می‌دهد.

از Box ، Column و Row استفاده کنید

Glance دارای سه طرح اصلی قابل ترکیب است:

  • Box : عناصر را روی دیگری قرار می دهد. به یک RelativeLayout ترجمه می شود.

  • Column : عناصر را پشت سر هم در محور عمودی قرار می دهد. این به یک LinearLayout با جهت عمودی ترجمه می شود.

  • Row : عناصر را پشت سر هم در محور افقی قرار می دهد. به یک LinearLayout با جهت افقی ترجمه می شود.

Glance از اشیاء 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 ممکن است بسته به دستگاه، انتخاب کاربر یا راه‌انداز متفاوت باشد، بنابراین مهم است که طرح‌بندی‌های انعطاف‌پذیر را همانطور که در صفحه ارائه طرح‌بندی ویجت انعطاف‌پذیر توضیح داده شده است، ارائه دهید. 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 x 112 72*72 203*150
SizeMode.Single 110*110 110*110 110*110 110*110
SizeMode.Exact 105*110 203 x 112 72*72 203*150
SizeMode.Responsive 80*100 80*100 80*100 150*120
* مقادیر دقیق فقط برای اهداف آزمایشی هستند.

SizeMode.Exact

SizeMode.Exact معادل ارائه طرح‌بندی‌های دقیق است که هر بار که اندازه AppWidget موجود تغییر می‌کند، محتوای GlanceAppWidget را درخواست می‌کند (به عنوان مثال، زمانی که کاربر اندازه 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 باید کاملاً بازسازی شود. این می تواند منجر به مشکلات عملکرد و جهش UI در زمانی که محتوا پیچیده است، شود.
  • اندازه موجود ممکن است بسته به اجرای راه‌انداز متفاوت باشد. به عنوان مثال، اگر لانچر لیست اندازه ها را ارائه ندهد، از حداقل اندازه ممکن استفاده می شود.
  • در دستگاه‌های پیش از اندروید 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",
)

مدیریت متن

Glance 1.1.0 شامل یک API برای تنظیم سبک های متن شما است. سبک های متن را با استفاده از ویژگی های fontSize ، fontWeight یا fontFamily کلاس TextStyle تنظیم کنید.

همانطور که در مثال زیر نشان داده شده است، fontFamily از تمام فونت های سیستم پشتیبانی می کند، اما فونت های سفارشی در برنامه ها پشتیبانی نمی شوند:

Text(
    style = TextStyle(
        fontWeight = FontWeight.Bold,
        fontSize = 18.sp,
        fontFamily = FontFamily.Monospace
    ),
    text = "Example Text"
)

دکمه های ترکیبی را اضافه کنید

دکمه های ترکیبی در اندروید 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"
)

هنگامی که وضعیت تغییر می کند، لامبدای ارائه شده فعال می شود. همانطور که در مثال زیر نشان داده شده است، می توانید وضعیت چک را ذخیره کنید:

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)
    ),

)

اجزای اضافی

Glance 1.1.0 شامل انتشار اجزای اضافی است که در جدول زیر توضیح داده شده است:

نام تصویر لینک مرجع یادداشت های اضافی
دکمه پر شده alt_text جزء
دکمه های طرح کلی alt_text جزء
دکمه های آیکون alt_text جزء اولیه / ثانویه / فقط نماد
نوار عنوان alt_text جزء
داربست Scaffold و Title Bar در یک نسخه نمایشی هستند.

برای اطلاعات بیشتر در مورد مشخصات طراحی، به طرح های اجزای این کیت طراحی در Figma مراجعه کنید.