تغییر اندازه برنامه اندروید

1. مقدمه

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

در حالی که این خبر خوبی برای توسعه دهندگان است، بهینه سازی برنامه های خاصی برای برآورده کردن انتظارات قابلیت استفاده و ایجاد یک تجربه کاربری عالی در اندازه های مختلف صفحه مورد نیاز است. به جای هدف قرار دادن هر دستگاه جدید در یک زمان، یک رابط کاربری واکنش‌گرا/تطبیقی ​​و معماری انعطاف‌پذیر می‌تواند به برنامه شما کمک کند تا در هر جایی که کاربران فعلی و آینده شما هستند عالی به نظر برسد و کار کند - در دستگاه‌هایی با هر اندازه و شکل!

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

آنچه را که خواهید ساخت

شما پیامدهای تغییر اندازه به صورت رایگان را بررسی خواهید کرد و یک برنامه اندروید را برای نشان دادن بهترین شیوه ها برای تغییر اندازه بهینه سازی خواهید کرد. برنامه شما:

مانیفست سازگار داشته باشید

  • محدودیت‌هایی را که مانع از تغییر اندازه برنامه می‌شوند را حذف کنید

هنگام تغییر اندازه حالت را حفظ کنید

  • حالت رابط کاربری را هنگام تغییر اندازه با استفاده از memorySaveable حفظ می کند
  • از تکرار بی مورد کار پس زمینه برای مقداردهی اولیه UI خودداری کنید

آنچه شما نیاز دارید

  1. آشنایی با ساخت اپلیکیشن های پایه اندروید
  2. آشنایی با ViewModel و State در Compose
  3. یک دستگاه آزمایشی که از تغییر اندازه پنجره آزاد مانند یکی از موارد زیر پشتیبانی می کند:

اگر در حین کار با این کد با مشکلاتی (اشکالات کد، خطاهای دستوری، عبارت نامشخص و غیره) مواجه شدید، لطفاً مشکل را از طریق پیوند گزارش یک اشتباه در گوشه سمت چپ پایین صفحه کد گزارش کنید.

2. شروع به کار

مخزن را از GitHub کلون کنید.

git clone https://github.com/android/large-screen-codelabs/

... یا یک فایل زیپ از مخزن دانلود و استخراج کنید

پروژه واردات

  • اندروید استودیو را باز کنید
  • Import Project یا File->New->Import Project را انتخاب کنید
  • به جایی که پروژه را شبیه سازی یا استخراج کرده اید بروید
  • پوشه تغییر اندازه را باز کنید.
  • پروژه را در پوشه start باز کنید. این شامل کد شروع است.

برنامه را امتحان کنید

  • برنامه را بسازید و اجرا کنید
  • سعی کنید اندازه برنامه را تغییر دهید

نظر شما چیست؟

بسته به پشتیبانی سازگاری دستگاه آزمایشی شما، احتمالاً متوجه شده اید که تجربه کاربری ایده آل نیست. برنامه قابل تغییر اندازه نیست و در نسبت تصویر اولیه گیر کرده است. چه اتفاقی می افتد؟

محدودیت های آشکار

اگر به فایل AndroidManifest.xml برنامه نگاه کنید، می بینید که چند محدودیت اضافه شده است که مانع از عملکرد خوب برنامه ما در یک محیط تغییر اندازه پنجره آزاد می شود.

AndroidManifest.xml

            android:maxAspectRatio="1.4"
            android:resizeableActivity="false"
            android:screenOrientation="portrait">

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

3. تغییرات پیکربندی تغییر اندازه

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

مشاهده تغییرات پیکربندی

برای مشاهده این تغییرات که خودتان در یک برنامه ساخته شده با سیستم نمای Android رخ می دهد، می توانید View.onConfigurationChanged لغو کنید. در Jetpack Compose، ما به LocalConfiguration.current دسترسی داریم که هر زمان که View.onConfigurationChanged فراخوانی شود، به‌طور خودکار به‌روزرسانی می‌شود.

برای مشاهده این تغییرات پیکربندی در برنامه نمونه خود، یک Composable را به برنامه خود اضافه کنید که مقادیر LocalConfiguration.current را نمایش می دهد، یا یک پروژه نمونه جدید با چنین قابلیتی ایجاد کنید. یک مثال UI برای دیدن اینها چیزی شبیه به این خواهد بود:

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

می توانید یک نمونه از پیاده سازی را در پوشه پروژه observing-configuration-changes مشاهده کنید. سعی کنید این را به رابط کاربری برنامه خود اضافه کنید، آن را در دستگاه آزمایشی خود اجرا کنید و با تغییر پیکربندی برنامه، به روز رسانی رابط کاربری را تماشا کنید.

با تغییر اندازه برنامه، اطلاعات پیکربندی در حال تغییر در رابط برنامه در زمان واقعی نمایش داده می شود

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

4. ثبت رویدادهای چرخه حیات فعالیت

یکی دیگر از پیامدهای تغییر اندازه پنجره آزاد برای برنامه شما، تغییرات مختلف چرخه حیات Activity است که برای برنامه شما رخ می دهد. برای مشاهده این تغییرات در زمان واقعی، یک ناظر چرخه حیات را به متد onCreate خود اضافه کنید و هر رویداد چرخه حیات جدید را با نادیده گرفتن onStateChanged ثبت کنید.

lifecycle.addObserver(object : LifecycleEventObserver {
        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        Log.d("resizing-codelab-lifecycle", "$event was called")
    }
})

با این ورود به سیستم، دوباره برنامه خود را روی دستگاه آزمایشی خود اجرا کنید و در حالی که سعی می کنید برنامه خود را کوچک کنید و دوباره آن را در پیش زمینه قرار دهید، به logcat نگاه کنید.

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

logcat نشان می دهد که روش های چرخه حیات فعالیت در هنگام تغییر اندازه فراخوانی می شوند

اکنون به Logcat نگاه کنید تا ببینید زمانی که برنامه خود را از کوچک‌ترین اندازه ممکن به بزرگ‌ترین اندازه ممکن تغییر اندازه می‌دهید، فراخوانی چرخه حیات کدام فعالیت فراخوانی می‌شود.

بسته به دستگاه آزمایشی خود، ممکن است رفتارهای مختلفی را مشاهده کنید، اما احتمالاً متوجه شده اید که وقتی اندازه پنجره برنامه شما به طور قابل توجهی تغییر می کند، فعالیت شما از بین می رود و دوباره ایجاد می شود، اما نه زمانی که کمی تغییر می کند. این به این دلیل است که در API 24+، تنها تغییرات قابل توجه در اندازه منجر به Activity تفریحی می شود .

برخی از تغییرات پیکربندی رایجی را که می‌توانید در یک محیط پنجره‌سازی آزاد انتظار داشته باشید، مشاهده کرده‌اید، اما تغییرات دیگری نیز وجود دارد که باید از آنها آگاه باشید. به عنوان مثال، اگر یک مانیتور خارجی به دستگاه آزمایشی خود وصل شده‌اید ، می‌توانید ببینید که Activity شما از بین می‌رود و دوباره ایجاد می‌شود تا تغییرات پیکربندی مانند تراکم نمایشگر را در نظر بگیرد.

برای انتزاع برخی از پیچیدگی های مرتبط با تغییرات پیکربندی، از API های سطح بالاتر مانند WindowSizeClass برای پیاده سازی رابط کاربری تطبیقی ​​خود استفاده کنید. (همچنین به پشتیبانی از اندازه های مختلف صفحه نمایش مراجعه کنید.)

5. تداوم - حفظ حالت داخلی اجزای سازنده هنگام تغییر اندازه

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

کار را با توسعه تابع 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)
                ),


            )
        }
    }
}

وقتی هدر قابل ارتقا را به برنامه خود اضافه کردید،

  1. برنامه را روی دستگاه آزمایشی خود اجرا کنید
  2. روی هدر ضربه بزنید تا بزرگ شود
  3. سعی کنید اندازه پنجره را تغییر دهید

خواهید دید که هدر با تغییر اندازه قابل توجه حالت خود را از دست می دهد.

هدر در کشوی پیمایش برنامه ضربه زده و بزرگ می شود، اما پس از تغییر اندازه برنامه جمع می شود

حالت UI به دلیل این واقعیت از بین می رود که remember به شما کمک می کند حالت را در بین ترکیبات مجدد حفظ کنید، اما نه در سراسر فعالیت یا سرگرمی فرآیند. معمول است که از حالت hoisting ، انتقال حالت به فراخوان کننده composable برای بی حالت کردن composable ها استفاده کنید، که می تواند به طور کامل از این مشکل جلوگیری کند. با این اوصاف، ممکن است در مکان‌هایی که حالت عنصر UI را درون توابع قابل ترکیب نگه می‌دارید، از remember استفاده کنید.

برای حل این مشکلات، remember با rememberSaveable جایگزین کنید. این کار به این دلیل کار می کند که rememberSaveable مقدار به خاطر سپرده شده را در savedInstanceState ذخیره و بازیابی می کند. remember به rememberSaveable تغییر دهید، برنامه خود را در دستگاه آزمایشی اجرا کنید و دوباره اندازه برنامه را تغییر دهید. متوجه خواهید شد که وضعیت هدر قابل گسترش در طول تغییر اندازه همانطور که در نظر گرفته شده است حفظ می شود.

6. پرهیز از تکرار بی مورد کارهای پس زمینه

دیده‌اید که چگونه می‌توانید از rememberSaveable برای حفظ حالت رابط کاربری داخلی قطعات composable از طریق تغییرات پیکربندی استفاده کنید که ممکن است اغلب در نتیجه تغییر اندازه پنجره به شکل آزاد اتفاق بیفتد. با این حال، یک برنامه اغلب باید حالت رابط کاربری و منطق را از قابلیت های composable دور کند . انتقال مالکیت حالت به ViewModel یکی از بهترین راه‌ها برای حفظ حالت در هنگام تغییر اندازه است. همانطور که وضعیت خود را در 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 نگاه می کنید، متوجه می شوید که برنامه شما نشان می دهد که روش اولیه چندین بار اجرا شده است. این می تواند برای کارهایی که می خواهید فقط یک بار اجرا کنید تا UI خود را مقداردهی کنید، مشکل ساز باشد. تماس‌های شبکه اضافی، ورودی/خروجی فایل یا کارهای دیگر می‌تواند عملکرد دستگاه را مختل کند و باعث مشکلات ناخواسته دیگری شود.

برای جلوگیری از کار غیرضروری در پس‌زمینه، فراخوانی initializeUIState() را از متد onCreate() فعالیت خود حذف کنید. در عوض، داده ها را در روش init ViewModel مقداردهی اولیه کنید. این تضمین می کند که روش مقداردهی اولیه فقط یک بار اجرا شود، زمانی که ReplyViewModel برای اولین بار نمونه سازی می شود:

init {
    initializeUIState()
}

دوباره برنامه را اجرا کنید، و بدون توجه به اینکه چند بار پنجره برنامه خود را تغییر اندازه می دهید، می توانید ببینید که وظیفه اولیه سازی شبیه سازی شده غیر ضروری فقط یک بار اجرا می شود. این به این دلیل است که ViewModel ها فراتر از چرخه حیات Activity باقی می مانند. با اجرای کد مقداردهی اولیه فقط یک بار در ایجاد ViewModel ، آن را از هر گونه Activity بازآفرینی جدا می کنیم و از کارهای غیر ضروری جلوگیری می کنیم. اگر این در واقع یک تماس سرور گران قیمت یا یک عملیات ورودی/خروجی فایل سنگین برای مقداردهی اولیه UI شما بود، در منابع قابل توجهی صرفه جویی می کنید و تجربه کاربری خود را بهبود می بخشید.

7. تبریک!

تو انجامش دادی! کار عالی! اکنون برخی از بهترین روش‌ها را برای فعال کردن برنامه‌های Android برای تغییر اندازه خوب در ChromeOS و سایر محیط‌های چند پنجره‌ای و چند صفحه‌ای اجرا کرده‌اید.

نمونه کد منبع

مخزن را از GitHub کلون کنید

git clone https://github.com/android/large-screen-codelabs/

... یا یک فایل زیپ از مخزن دانلود و استخراج کنید