۱. مقدمه
اکوسیستم دستگاههای اندروید همیشه در حال تکامل است. از روزهای اولیه کیبوردهای سختافزاری داخلی گرفته تا چشمانداز مدرن دستگاههای تاشو، فلیپآپ، تبلتها و پنجرههای با اندازه دلخواه - برنامههای اندروید هرگز مانند امروز روی مجموعهای متنوع از دستگاهها اجرا نشدهاند.
اگرچه این خبر خوبی برای توسعهدهندگان است، اما بهینهسازیهای خاصی برای اپلیکیشن لازم است تا انتظارات مربوط به کاربردپذیری را برآورده کند و تجربه کاربری عالی را در اندازههای مختلف صفحه نمایش ایجاد کند. به جای هدف قرار دادن هر دستگاه جدید به صورت جداگانه، یک رابط کاربری واکنشگرا/تطبیقپذیر و معماری انعطافپذیر میتواند به اپلیکیشن شما کمک کند تا در هر مکانی که کاربران فعلی و آینده شما هستند - روی دستگاههایی با هر اندازه و شکلی - به خوبی ظاهر و کار کند!
معرفی محیطهای اندروید با قابلیت تغییر اندازه آزاد، راهی عالی برای آزمایش رابط کاربری واکنشگرا/انطباقی شما است تا آن را برای هر دستگاهی آماده کنید. این آزمایشگاه کد، شما را در درک پیامدهای تغییر اندازه و همچنین اجرای برخی از بهترین شیوهها برای تغییر اندازه پایدار و آسان یک برنامه راهنمایی میکند.
آنچه خواهید ساخت
شما پیامدهای تغییر اندازه به صورت آزاد را بررسی خواهید کرد و یک برنامه اندروید را بهینه خواهید کرد تا بهترین شیوههای تغییر اندازه را نشان دهید. برنامه شما:
یک مانیفست سازگار داشته باشید
- محدودیتهایی را که مانع از تغییر اندازه آزادانه یک برنامه میشوند، حذف کنید
حفظ حالت هنگام تغییر اندازه
- با استفاده از rememberSaveable، حالت رابط کاربری را هنگام تغییر اندازه حفظ میکند.
- از تکرار غیرضروری کارهای پسزمینه برای مقداردهی اولیه رابط کاربری خودداری کنید.
آنچه نیاز دارید
- آشنایی با ساخت اپلیکیشنهای پایه اندروید
- آشنایی با ViewModel و State در Compose
- یک دستگاه آزمایشی که از تغییر اندازه پنجره به صورت آزاد مانند یکی از موارد زیر پشتیبانی میکند:
- یک کرومبوک با تنظیمات ADB
- تبلتی که از حالت Samsung DeX یا حالت بهرهوری پشتیبانی میکند
- شبیهساز دستگاه مجازی اندروید دسکتاپ در اندروید استودیو
اگر در حین کار با این آزمایشگاه کد با هرگونه مشکلی (اشکال در کد، خطاهای دستوری، کلمات نامفهوم و غیره) مواجه شدید، لطفاً مشکل را از طریق لینک «گزارش اشتباه» در گوشه پایین سمت چپ آزمایشگاه کد گزارش دهید.
۲. شروع کار
مخزن را از GitHub کپی کنید.
git clone https://github.com/android/large-screen-codelabs/
... یا یک فایل زیپ از مخزن را دانلود کرده و آن را استخراج کنید
پروژه واردات
- اندروید استودیو را باز کنید
- انتخاب گزینهی وارد کردن پروژه یا File->New->Import Project
- به جایی که پروژه را کلون یا استخراج کردهاید بروید
- پوشه تغییر اندازه را باز کنید.
- پروژه را در پوشه شروع باز کنید. این پوشه شامل کد شروع کننده است.
برنامه را امتحان کنید
- ساخت و اجرای برنامه
- تغییر اندازه برنامه را امتحان کنید
نظر شما چیست؟
بسته به پشتیبانی سازگاری دستگاه آزمایشی شما، احتمالاً متوجه شدهاید که تجربه کاربری ایدهآل نیست. برنامه قادر به تغییر اندازه نیست و در نسبت ابعاد اولیه گیر کرده است. چه اتفاقی میافتد؟
محدودیتهای آشکار
اگر به فایل AndroidManifest.xml برنامه نگاه کنید، میتوانید ببینید که چند محدودیت اضافه شده است که مانع از عملکرد خوب برنامه ما در محیط تغییر اندازه پنجره با فرم آزاد میشود.
فایل AndroidManifest.xml
android:maxAspectRatio="1.4"
android:resizeableActivity="false"
android:screenOrientation="portrait">
سعی کنید این سه خط مشکلساز را از مانیفست خود حذف کنید، برنامه را دوباره بسازید و دوباره روی دستگاه آزمایشی خود امتحان کنید. متوجه خواهید شد که برنامه دیگر از تغییر اندازه آزاد محدود نیست. حذف محدودیتهایی مانند این از مانیفست، گامی مهم در بهینهسازی برنامه شما برای تغییر اندازه پنجره آزاد است.
۳. تغییرات پیکربندی تغییر اندازه
وقتی اندازه پنجره برنامه شما تغییر میکند، پیکربندی برنامه شما بهروزرسانی میشود. این بهروزرسانیها پیامدهایی برای برنامه شما دارند. درک و پیشبینی آنها میتواند به ارائه یک تجربه عالی به کاربران شما کمک کند. واضحترین تغییرات، عرض و ارتفاع پنجره برنامه شما هستند، اما این تغییرات پیامدهایی برای نسبت ابعاد و جهتگیری نیز دارند.
مشاهده تغییرات پیکربندی
برای مشاهدهی این تغییرات در برنامهای که با سیستم نمای اندروید ساخته شده است، میتوانید View.onConfigurationChanged را بازنویسی کنید. در Jetpack Compose، به LocalConfiguration.current دسترسی داریم که هر زمان View.onConfigurationChanged فراخوانی شود، بهطور خودکار بهروزرسانی میشود.
برای مشاهدهی این تغییرات پیکربندی در برنامهی نمونهی خود، یک composable به برنامهی خود اضافه کنید که مقادیر LocalConfiguration.current را نمایش دهد، یا یک پروژهی نمونهی جدید با چنین composable ایجاد کنید. یک رابط کاربری نمونه برای مشاهدهی این موارد چیزی شبیه به این خواهد بود:
val configuration = LocalConfiguration.current
val isPortrait = configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT
val screenLayoutSize =
when (configuration.screenLayout and
Configuration.SCREENLAYOUT_SIZE_MASK) {
SCREENLAYOUT_SIZE_SMALL -> "SCREENLAYOUT_SIZE_SMALL"
SCREENLAYOUT_SIZE_NORMAL -> "SCREENLAYOUT_SIZE_NORMAL"
SCREENLAYOUT_SIZE_LARGE -> "SCREENLAYOUT_SIZE_LARGE"
SCREENLAYOUT_SIZE_XLARGE -> "SCREENLAYOUT_SIZE_XLARGE"
else -> "undefined value"
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text("screenWidthDp: ${configuration.screenWidthDp}")
Text("screenHeightDp: ${configuration.screenHeightDp}")
Text("smallestScreenWidthDp: ${configuration.smallestScreenWidthDp}")
Text("orientation: ${if (isPortrait) "portrait" else "landscape"}")
Text("screenLayout SIZE: $screenLayoutSize")
}
میتوانید یک نمونه پیادهسازی را در پوشه پروژه observation-configuration-changes مشاهده کنید. سعی کنید این را به رابط کاربری برنامه خود اضافه کنید، آن را روی دستگاه آزمایشی خود اجرا کنید و بهروزرسانی رابط کاربری را همزمان با تغییر پیکربندی برنامه خود مشاهده کنید.

این تغییرات در پیکربندی برنامه شما به شما امکان میدهد تا به سرعت از حالت دوبخشی صفحه نمایش در یک گوشی کوچک به حالت تمام صفحه در یک تبلت یا دسکتاپ، حرکت کنید. این روش نه تنها روش خوبی برای آزمایش طرحبندی برنامه شما در صفحات مختلف است، بلکه به شما امکان میدهد تا ببینید برنامه شما چقدر میتواند رویدادهای تغییر سریع پیکربندی را مدیریت کند.
۴. ثبت رویدادهای چرخه حیات فعالیت
یکی دیگر از پیامدهای تغییر اندازه پنجره فرم آزاد برای برنامه شما، تغییرات مختلف چرخه حیات Activity است که برای برنامه شما رخ خواهد داد. برای مشاهده این تغییرات به صورت بلادرنگ، یک ناظر چرخه حیات به متد onCreate خود اضافه کنید و با override کردن onStateChanged هر رویداد چرخه حیات جدید را ثبت کنید.
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
Log.d("resizing-codelab-lifecycle", "$event was called")
}
})
با این لاگین، برنامه خود را دوباره روی دستگاه آزمایشی خود اجرا کنید و در حالی که سعی میکنید برنامه خود را کوچک کنید و دوباره آن را به پیشزمینه بیاورید، به logcat نگاه کنید.
توجه داشته باشید که برنامه شما هنگام کوچک شدن (مینیمایز) متوقف میشود و هنگامی که به پیشزمینه (foreground) آورده میشود، دوباره از سر گرفته میشود. این موضوع پیامدهایی برای برنامه شما دارد که در بخش بعدی این آزمایشگاه کد که بر پیوستگی تمرکز دارد، بررسی خواهید کرد.

حالا به Logcat نگاه کنید تا ببینید کدام فراخوانیهای چرخه حیات اکتیویتی هنگام تغییر اندازه برنامه از کوچکترین اندازه ممکن به بزرگترین اندازه ممکن فراخوانی میشوند.
بسته به دستگاه آزمایشی شما، ممکن است رفتارهای متفاوتی را مشاهده کنید، اما احتمالاً متوجه شدهاید که وقتی اندازه پنجره برنامه شما به طور قابل توجهی تغییر میکند، activity شما از بین میرود و دوباره ایجاد میشود، اما وقتی کمی تغییر میکند، این اتفاق نمیافتد. دلیل این امر این است که در API 24+، فقط تغییرات اندازه قابل توجه منجر به بازسازی Activity میشود .
شما برخی از تغییرات پیکربندی رایج مورد انتظار در یک محیط پنجرهبندی فرم آزاد را دیدهاید، اما تغییرات دیگری نیز وجود دارد که باید از آنها آگاه باشید. به عنوان مثال، اگر یک مانیتور خارجی به دستگاه آزمایشی خود متصل دارید، میتوانید ببینید که Activity شما برای در نظر گرفتن تغییرات پیکربندی مانند تراکم نمایشگر، از بین رفته و دوباره ایجاد میشود.
برای خلاصهسازی برخی از پیچیدگیهای مرتبط با تغییرات پیکربندی، از APIهای سطح بالاتر مانند WindowSizeClass برای پیادهسازی رابط کاربری تطبیقی خود استفاده کنید. (همچنین به بخش «پشتیبانی از اندازههای مختلف صفحه نمایش » مراجعه کنید.)
۵. پیوستگی - حفظ وضعیت داخلی composableها هنگام تغییر اندازه
در بخش قبلی، برخی از تغییرات پیکربندی که برنامه شما میتواند در یک محیط تغییر اندازه پنجره با فرم آزاد انتظار داشته باشد را مشاهده کردید. در این بخش، وضعیت رابط کاربری برنامه خود را در طول این تغییرات پیوسته نگه خواهید داشت.
با بسط دادن تابع قابل ترکیب NavigationDrawerHeader (که در ReplyHomeScreen.kt یافت میشود) شروع کنید تا آدرس ایمیل را هنگام کلیک نشان دهد.
@Composable
private fun NavigationDrawerHeader(
modifier: Modifier = Modifier
) {
var showDetails by remember { mutableStateOf(false) }
Column(
modifier = modifier.clickable {
showDetails = !showDetails
}
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
ReplyLogo(
modifier = Modifier
.size(dimensionResource(R.dimen.reply_logo_size))
)
ReplyProfileImage(
drawableResource = LocalAccountsDataProvider
.userAccount.avatar,
description = stringResource(id = R.string.profile),
modifier = Modifier
.size(dimensionResource(R.dimen.profile_image_size))
)
}
AnimatedVisibility (showDetails) {
Text(
text = stringResource(id = LocalAccountsDataProvider
.userAccount.email),
style = MaterialTheme.typography.labelMedium,
modifier = Modifier
.padding(
start = dimensionResource(
R.dimen.drawer_padding_header),
end = dimensionResource(
R.dimen.drawer_padding_header),
bottom = dimensionResource(
R.dimen.drawer_padding_header)
),
)
}
}
}
وقتی هدر قابل گسترش را به برنامه خود اضافه کردید،
- برنامه را روی دستگاه آزمایشی خود اجرا کنید
- برای باز کردن سربرگ، روی آن ضربه بزنید
- سعی کنید اندازه پنجره را تغییر دهید
خواهید دید که هدر با تغییر اندازه قابل توجه، حالت خود را از دست میدهد.

وضعیت رابط کاربری به این دلیل از بین میرود که remember به شما کمک میکند وضعیت را در طول ترکیبهای مجدد حفظ کنید، اما نه در طول بازآفرینی فعالیت یا فرآیند. استفاده از state hoisting ، انتقال وضعیت به فراخوانیکنندهی composable برای بیحالت کردن composableها رایج است، که میتواند از این مشکل به طور کامل جلوگیری کند. با این اوصاف، میتوانید هنگام نگه داشتن وضعیت عنصر رابط کاربری در توابع composable، remember در جاهایی استفاده کنید.
برای حل این مشکلات، remember با rememberSaveable جایگزین کنید. این روش کار میکند زیرا rememberSaveable مقدار به خاطر سپرده شده را در savedInstanceState ذخیره و بازیابی میکند. remember را به rememberSaveable تغییر دهید، برنامه خود را روی دستگاه آزمایشی اجرا کنید و دوباره سعی کنید اندازه برنامه را تغییر دهید. متوجه خواهید شد که وضعیت هدر expandable در طول تغییر اندازه، همانطور که در نظر گرفته شده بود، حفظ میشود.
۶. اجتناب از تکرار غیرضروری کارهای پسزمینه
شما دیدهاید که چگونه میتوانید rememberSaveable برای حفظ وضعیت رابط کاربری داخلی composableها در طول تغییرات پیکربندی استفاده کنید که میتواند به طور مکرر در نتیجه تغییر اندازه پنجره free-form رخ دهد. با این حال، یک برنامه اغلب باید وضعیت رابط کاربری و منطق را از composableها جدا کند . انتقال مالکیت state به ViewModel یکی از بهترین راهها برای حفظ state در حین تغییر اندازه است. همانطور که state خود را به ViewModel منتقل میکنید، ممکن است با مشکلاتی در کارهای پسزمینه طولانی مدت مانند دسترسی سنگین به سیستم فایل یا فراخوانیهای شبکه که برای مقداردهی اولیه صفحه نمایش شما ضروری هستند، مواجه شوید.
برای دیدن نمونهای از انواع مشکلاتی که ممکن است با آنها مواجه شوید، یک دستور log به متد initializeUIState در ReplyViewModel اضافه کنید.
fun initializeUIState() {
Log.d("resizing-codelab", "initializeUIState() called in the viewmodel")
val mailboxes: Map<MailboxType, List<Email>> =
LocalEmailsDataProvider.allEmails.groupBy { it.mailbox }
_uiState.value =
ReplyUiState(
mailboxes = mailboxes,
currentSelectedEmail = mailboxes[MailboxType.Inbox]?.get(0)
?: LocalEmailsDataProvider.defaultEmail
)
}
اکنون برنامه را روی دستگاه آزمایشی خود اجرا کنید و چندین بار تغییر اندازه پنجره برنامه را امتحان کنید.
وقتی به Logcat نگاه میکنید، متوجه خواهید شد که برنامه شما نشان میدهد که متد مقداردهی اولیه چندین بار اجرا شده است. این میتواند برای کاری که فقط میخواهید یک بار برای مقداردهی اولیه رابط کاربری خود اجرا کنید، مشکلساز باشد. فراخوانیهای اضافی شبکه، ورودی/خروجی فایل یا سایر کارها میتواند عملکرد دستگاه را مختل کند و باعث مشکلات ناخواسته دیگری شود.
برای جلوگیری از کارهای پسزمینهای غیرضروری، فراخوانی تابع initializeUIState() را از متد onCreate() اکتیویتی خود حذف کنید. در عوض، دادهها را در متد init از ViewModel مقداردهی اولیه کنید. این کار تضمین میکند که متد مقداردهی اولیه فقط یک بار اجرا میشود، زمانی که ReplyViewModel برای اولین بار نمونهسازی میشود:
init {
initializeUIState()
}
دوباره برنامه را اجرا کنید، و میبینید که وظیفه مقداردهی اولیه شبیهسازیشده غیرضروری، صرف نظر از تعداد دفعاتی که اندازه پنجره برنامه خود را تغییر میدهید، فقط یک بار اجرا میشود. دلیل این امر این است که ViewModelها فراتر از چرخه حیات Activity باقی میمانند. با اجرای کد مقداردهی اولیه فقط یک بار در هنگام ایجاد ViewModel ، آن را از هرگونه بازتولید Activity جدا میکنیم و از کار غیرضروری جلوگیری میکنیم. اگر این در واقع یک فراخوانی پرهزینه سرور یا یک عملیات ورودی/خروجی سنگین فایل برای مقداردهی اولیه رابط کاربری شما بود، منابع قابل توجهی را ذخیره میکردید و تجربه کاربری خود را بهبود میبخشیدید.
۷. تبریک میگویم!
شما موفق شدید! کار عالی! اکنون شما برخی از بهترین شیوهها را برای فعال کردن تغییر اندازه مناسب برنامههای اندروید در ChromeOS و سایر محیطهای چند پنجرهای و چند صفحهای پیادهسازی کردهاید.
نمونه کد منبع
مخزن را از گیتهاب کپی کنید
git clone https://github.com/android/large-screen-codelabs/
... یا یک فایل زیپ از مخزن را دانلود کرده و آن را استخراج کنید