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

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

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

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

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

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

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

Glance از اشیاء Scaffold پشتیبانی می‌کند. کامپوننت‌های Column ، Row و Box خود را درون یک شیء Scaffold مشخص قرار دهید.

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

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

مثال زیر به شما نشان می‌دهد که چگونه یک 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 از اندروید ۱۲ به بعد کمک می‌کند (برای مثال، هنگام اضافه کردن یا حذف موارد از لیست). مثال زیر نحوه مشخص کردن itemId را نشان می‌دهد:

items(items = peopleList, itemId = { person -> person.id.hashCode().toLong() }) { person ->
    Text(person.name)
}

پیمایش سریع

پیمایش سریع (Snap scrolling) انیمیشنی است که به محتوای قابل پیمایش اجازه می‌دهد تا به بالای ظرف ویجت (widget container) بچسبد.

ویدیوی ۱. سمت چپ یک آیتم از لیست را نشان می‌دهد که هنگام اسکرول کردن در جای خود قرار نمی‌گیرد، سمت راست در جای خود قرار می‌گیرد.


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

  • وابستگی Glance خود را به نسخه ۱.۳.۰-alpha02 یا بالاتر به‌روزرسانی کنید.
  • compileSdk خود را روی ۳۷ یا بالاتر تنظیم کنید، زیرا پیمایش اسنپ در دستگاه‌هایی که اندروید ۱۷ و بالاتر دارند پشتیبانی می‌شود.
  • 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 باید کاملاً از نو ساخته شود. این امر می‌تواند منجر به مشکلات عملکردی و پرش‌های رابط کاربری در هنگام پیچیدگی محتوا شود.
  • اندازه موجود ممکن است بسته به پیاده‌سازی لانچر متفاوت باشد. برای مثال، اگر لانچر لیست اندازه‌ها را ارائه ندهد، از حداقل اندازه ممکن استفاده می‌شود.
  • در دستگاه‌های قبل از اندروید ۱۲، منطق محاسبه اندازه ممکن است در همه شرایط کار نکند.

به طور کلی، اگر SizeMode.Responsive قابل استفاده نباشد (یعنی، مجموعه کوچکی از طرح‌بندی‌های واکنش‌گرا امکان‌پذیر نباشد)، باید از این حالت استفاده کنید.

دسترسی به منابع

برای دسترسی به هر منبع اندروید، LocalContext.current استفاده کنید، همانطور که در مثال زیر نشان داده شده است:

LocalContext.current.getString(R.string.glance_title)

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

Composableها و متدها منابع را با استفاده از یک "provider" مانند ImageProvider یا با استفاده از یک متد overload مانند 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"
)

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

دکمه‌های ترکیبی در اندروید ۱۲ معرفی شدند. Glance از سازگاری معکوس برای انواع دکمه‌های ترکیبی زیر پشتیبانی می‌کند:

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

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

)

اجزای اضافی

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

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

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

برای اطلاعات بیشتر در مورد طرح‌بندی‌های متعارف، به طرح‌بندی‌های ویجت متعارف مراجعه کنید.