ساختن میانای کاربر با Glance

این صفحه نحوه مدیریت اندازه‌ها و ارائه چیدمان‌های انعطاف‌پذیر و واکنش‌گرا با «نگاه سریع» را بااستفاده از عناصر موجود «نگاه سریع» شرح می‌دهد.

استفاده از Box،‏ Column، و Row

«نگاه سریع» سه چیدمان ترکیبی اصلی دارد:

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

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

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

«نگاه سریع» از Scaffold شیء پشتیبانی می‌کند. عناصر ترکیبی Column، Row، و Box را در شیء Scaffold معینی قرار دهید.

چیدمان ستونی، ردیفی، و جعبه‌ای.
شکل ۱. نمونه‌هایی از چیدمان با «ستون»، «ردیف»، و «چارگوش».

هریک از این عناصر ترکیبی به شما امکان می‌دهد تراز عمودی و افقی محتوای آن و محدودیت‌های پهنا، ارتفاع، وزن، یا حاشیه را بااستفاده از اصلاح‌گرها تعریف کنید. علاوه‌براین، هر فرزند می‌تواند اصلاح‌گر خود را برای تغییر فضا و جایگاه درون والد تعریف کند.

مثال زیر نشان می‌دهد چگونه Row ایجاد کنید که عناصر فرزندش را به‌صورت افقی و یکنواخت توزیع کند، همان‌طور که در «شکل ۱» می‌بینید:

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,
    itemId = { person -> person.id.hashCode().toLong() }) { person ->
    Text(person.name)
}

پیمایش سریع

پیمایش سریع پویانمایی‌ای است که به محتوای پیمایش‌پذیر امکان می‌دهد به بالای ظرف ویجت بچسبد.

ویدیو ۱. در سمت چپ، مورد فهرستی نشان داده می‌شود که هنگام پیمایش در جای خود ثابت نمی‌شود، اما در سمت راست، در جای خود ثابت می‌شود.


برای پیاده‌سازی پیمایش سریع، مطمئن شوید شرایط زیر را برآورده می‌کنید:

  • وابستگی Glance را به نسخه 1.3.0-alpha02 یا بالاتر به‌روز کنید.
  • compileSdk را روی ۳۷ یا بالاتر تنظیم کنید، زیرا پیمایش سریع در دستگاه‌های دارای Android 17 و بالاتر پشتیبانی می‌شود.
  • ‫LazyColumn را با VerticalScrollMode پیکربندی کنید. اگر دستگاه از پیمایش سریع پشتیبانی می‌کند، از SnapScrollMatchHeight استفاده کنید. درغیراین‌صورت، از Normal استفاده کنید.
را ببینید.

اگر از پیمایش سریع با تصاویر استفاده می‌کنید، «چیدمان کانونی» تصویر تمام‌رنگ را ببینید.

@Composable
fun SnapScrollLayout() {
    val height = LocalSize.current.height
    val items = listOf(
        ColorItem(Color.Red, "Red"),
        ColorItem(Color.Yellow, "Yellow"),
        ColorItem(Color.Blue, "Blue")
    )

    val scrollMode = if (Build.VERSION.SDK_INT >= 37) {
        VerticalScrollMode.SnapScrollMatchHeight(height)
    } else {
        VerticalScrollMode.Normal
    }

    LazyColumn(
        verticalScrollMode = scrollMode
    ) {
        items(items) { item ->
            ColorCard(item, height)
        }
    }
}

@Composable
private fun ColorCard(item: ColorItem, height: Dp) {
    Box(
        modifier = GlanceModifier
            .background(item.color)
            .fillMaxWidth()
            .height(height),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = item.name,
            modifier = GlanceModifier.background(Color.White)
        )
    }
}

تعریف 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 نشان می‌دهد:

اندازه دردسترس ‫۱۰۵ × ۱۱۰ ۱۱۲ × ۲۰۳ ۷۲ × ۷۲ ۱۵۰ × ۲۰۳
SizeMode.Single ‫۱۱۰ × ۱۱۰ ‫۱۱۰ × ۱۱۰ ‫۱۱۰ × ۱۱۰ ‫۱۱۰ × ۱۱۰
SizeMode.Exact ‫۱۰۵ × ۱۱۰ ۱۱۲ × ۲۰۳ ۷۲ × ۷۲ ۱۵۰ × ۲۰۳
SizeMode.Responsive ‫۸۰ × ۱۰۰ ‫۸۰ × ۱۰۰ ‫۸۰ × ۱۰۰ ۱۵۰ × ۱۲۰
* مقادیر دقیق فقط برای اهداف نمایشی است.

SizeMode.Exact

SizeMode.Exact معادل ارائه چیدمان‌های دقیق است که هر بار اندازه AppWidget موجود تغییر می‌کند (برای مثال، وقتی کاربر اندازه AppWidget را در صفحه اصلی تغییر می‌دهد)، محتوای GlanceAppWidget را درخواست می‌کند.

برای مثال، در ابزاره مقصد، اگر پهنای دردسترس از مقدار معینی بیشتر باشد، می‌توان دکمه‌ای اضافی اضافه کرد.

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

نام کاربری

‫Glance نسخه ۱.۱.۰ شامل میانای برنامه‌سازی کاربردی برای تنظیم سبک‌های نوشتاری است. سبک‌های نوشتار را بااستفاده از fontSize، fontWeight، یا fontFamily ویژگی‌های کلاس TextStyle تنظیم کنید.

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

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

افزودن دکمه‌های ترکیبی

دکمه‌های ترکیبی در Android 12 معرفی شدند. «نگاه سریع» از سازگاری با نسخه قدیمی برای انواع زیر از دکمه‌های ترکیبی پشتیبانی می‌کند:

هریک از این دکمه‌های ترکیبی نمای کلیک‌کردنی را نمایش می‌دهد که نشان‌دهنده حالت «علامت‌گذاری‌شده» است.

var isApplesChecked by remember { mutableStateOf(false) }
var isEnabledSwitched by remember { mutableStateOf(false) }
var isRadioChecked by remember { mutableIntStateOf(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)
    ),

    )

اجزای اضافی

«نگاه سریع» نسخه ۱.۱.۰ شامل انتشار اجزای اضافی است، همان‌طور که در جدول زیر توضیح داده شده است:

نام تصویر پیوند مرجع نکته‌های بیشتر
دکمه توپر alt_text عنصر
دکمه‌های طرح کلی alt_text عنصر
دکمه‌های نماد alt_text عنصر اصلی / ثانویه / فقط نماد
نوار عنوان alt_text عنصر
داربست «داربست» و «نوار عنوان» در یک نسخه نمایشی هستند.

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

برای کسب اطلاعات بیشتر درباره چیدمان‌های متعارف، به چیدمان‌های ابزارک متعارف مراجعه کنید.