با Jetpack Compose برنامه های تطبیقی ​​بسازید

۱. مقدمه

در این آزمایشگاه کدنویسی، نحوه ساخت برنامه‌های تطبیقی ​​برای تلفن‌ها، تبلت‌ها و دستگاه‌های تاشو و نحوه افزایش دسترسی‌پذیری آنها با Jetpack Compose را خواهید آموخت. همچنین بهترین شیوه‌ها برای استفاده از کامپوننت‌های Material 3 و قالب‌بندی را خواهید آموخت.

قبل از اینکه وارد بحث شویم، مهم است که منظورمان از سازگاری را بفهمیم.

سازگاری

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

برای کسب اطلاعات بیشتر، طراحی تطبیقی ​​را بررسی کنید.

در این آزمایشگاه کد، شما نحوه استفاده و تفکر در مورد سازگاری هنگام استفاده از Jetpack Compose را بررسی می‌کنید. شما یک برنامه به نام Reply می‌سازید که به شما نشان می‌دهد چگونه سازگاری را برای انواع صفحه نمایش پیاده‌سازی کنید و چگونه سازگاری و قابلیت دسترسی با هم کار می‌کنند تا به کاربران یک تجربه بهینه ارائه دهند.

آنچه یاد خواهید گرفت

  • چگونه برنامه خود را طوری طراحی کنیم که با Jetpack Compose همه اندازه‌های پنجره را هدف قرار دهد.
  • چگونه اپلیکیشن خود را برای گوشی‌های تاشوی مختلف هدف قرار دهید.
  • نحوه استفاده از انواع مختلف ناوبری برای دسترسی و دسترسی بهتر.
  • نحوه استفاده از اجزای Material 3 برای ارائه بهترین تجربه برای هر اندازه پنجره.

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

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

شبیه‌ساز با قابلیت تغییر اندازه با گزینه‌های گوشی، حالت باز، تبلت و دسکتاپ.

اگر با Compose آشنا نیستید، قبل از تکمیل این codelab، codelab مقدماتی Jetpack Compose را در نظر بگیرید.

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

  • یک اپلیکیشن ایمیل کلاینت تعاملی به نام Reply، که از بهترین شیوه‌ها برای طراحی‌های تطبیق‌پذیر، ناوبری‌های متریال مختلف و استفاده بهینه از فضای صفحه نمایش استفاده می‌کند.

پشتیبانی از چندین دستگاه، نمایشی است که در این آزمایشگاه کد به آن دست خواهید یافت.

۲. آماده شوید

برای دریافت کد این codelab، مخزن GitHub را از خط فرمان کلون کنید:

git clone https://github.com/android/codelab-android-compose.git
cd codelab-android-compose/AdaptiveUiCodelab

روش دیگر، دانلود مخزن به صورت فایل زیپ است:

توصیه می‌کنیم با کد موجود در شاخه اصلی شروع کنید و گام به گام با سرعت دلخواه خود، codelab را دنبال کنید.

پروژه را در اندروید استودیو باز کنید

  1. در پنجره Welcome to Android Studio ، گزینه c01826594f360d94.png باز کردن یک پروژه موجود.
  2. پوشه <Download Location>/AdaptiveUiCodelab را انتخاب کنید (مطمئن شوید که پوشه AdaptiveUiCodelab حاوی build.gradle انتخاب می‌کنید).
  3. وقتی اندروید استودیو پروژه را وارد کرد، بررسی کنید که آیا می‌توانید شاخه main را اجرا کنید یا خیر.

کد شروع را بررسی کنید

کد شاخه اصلی شامل بسته ui است. شما با فایل‌های زیر در آن بسته کار خواهید کرد:

  • MainActivity.kt - فعالیت نقطه ورود که در آن برنامه خود را شروع می‌کنید.
  • ReplyApp.kt - شامل کامپوننت‌های رابط کاربری صفحه اصلی است.
  • ReplyHomeViewModel.kt - داده‌ها و وضعیت رابط کاربری را برای محتوای برنامه فراهم می‌کند.
  • ReplyListContent.kt - شامل composableهایی برای ارائه لیست‌ها و صفحات جزئیات است.

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

صفحه اولیه روی گوشی

نمای کشیده اولیه روی تبلت

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

۳. برنامه‌ها را تطبیق‌پذیر کنید

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

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

اندازه پنجره‌ها

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

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

کلاس WindowWidthSize برای عرض فشرده، متوسط ​​و گسترده.

کلاس WindowHeightSize برای ارتفاع جمع و جور، متوسط ​​و باز.

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

حالت‌های تاخوردگی

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

حالت‌های تاشو، صاف و نیمه باز

علاوه بر این، کاربر می‌تواند در حالی که لولا تا حدی باز است، به نمایشگر داخلی نگاه کند، که منجر به حالت‌های فیزیکی مختلفی بر اساس جهت تا شدن می‌شود: حالت رومیزی (تا شدن افقی، که در تصویر بالا در سمت راست نشان داده شده است) و حالت کتابی (تا شدن عمودی).

درباره حالت‌های تا شدن و لولاها بیشتر بخوانید.

همه اینها مواردی هستند که باید هنگام اجرای طرح‌بندی‌های تطبیقی ​​که از گوشی‌های تاشو پشتیبانی می‌کنند، در نظر گرفته شوند.

اطلاعات تطبیقی ​​​​دریافت کنید

کتابخانه adaptive ​​Material3 دسترسی راحت به اطلاعات مربوط به پنجره‌ای که برنامه شما در آن اجرا می‌شود را فراهم می‌کند.

  1. ورودی‌های مربوط به این مصنوع و نسخه آن را به فایل کاتالوگ نسخه اضافه کنید:

gradle/libs.versions.toml

[versions]
material3Adaptive = "1.0.0"

[libraries]
androidx-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
  1. در فایل ساخت ماژول app، وابستگی کتابخانه جدید را اضافه کنید و سپس همگام‌سازی Gradle را انجام دهید:

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive)
}

اکنون، در هر محدوده‌ی ترکیبی، می‌توانید از currentWindowAdaptiveInfo() برای دریافت یک شیء WindowAdaptiveInfo که حاوی اطلاعاتی مانند کلاس اندازه پنجره فعلی و اینکه آیا دستگاه در حالت تاشو مانند حالت رومیزی قرار دارد یا خیر، استفاده کنید.

اکنون می‌توانید این را در MainActivity امتحان کنید.

  1. در onCreate() درون بلوک ReplyTheme ، اطلاعات تطبیقی ​​پنجره را دریافت کرده و کلاس‌های اندازه را در یک Text composable نمایش دهید. می‌توانید این را بعد از عنصر ReplyApp() اضافه کنید:

فعالیت اصلی.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ReplyTheme {
            val uiState by viewModel.uiState.collectAsStateWithLifecycle()
            ReplyApp(
                replyHomeUIState = uiState,
                onEmailClick = viewModel::setSelectedEmail
            )

            val adaptiveInfo = currentWindowAdaptiveInfo()
            val sizeClassText =
                "${adaptiveInfo.windowSizeClass.windowWidthSizeClass}\n" +
                "${adaptiveInfo.windowSizeClass.windowHeightSizeClass}"
            Text(
                text = sizeClassText,
                color = Color.Magenta,
                modifier = Modifier.padding(
                    WindowInsets.safeDrawing.asPaddingValues()
                )
            )
        }
    }
}

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

۴. ناوبری پویا

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

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

همانطور که برنامه خود را طراحی می‌کنید و تصمیم می‌گیرید که عناصر رابط کاربری تعاملی را در کجای طرح خود قرار دهید، پیامدهای ارگونومیک مناطق مختلف صفحه را در نظر بگیرید.

  • هنگام نگه داشتن دستگاه، دسترسی به کدام نواحی راحت است؟
  • به کدام نواحی فقط با دراز کردن انگشتان می‌توان دسترسی پیدا کرد، که ممکن است ناخوشایند باشد؟
  • دسترسی به کدام قسمت‌ها چالش‌برانگیز است یا از جایی که کاربر دستگاه را در دست می‌گیرد، دور هستند؟

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

ناوبری پایین

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

نوار ناوبری پایین با آیتم‌ها

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

ریل ناوبری با اقلام

کشوی ناوبری راهی آسان برای مشاهده اطلاعات دقیق برای تب‌های ناوبری فراهم می‌کند و هنگام استفاده از تبلت یا دستگاه‌های بزرگتر ، به راحتی قابل دسترسی است. دو نوع کشوی ناوبری موجود است: یک کشوی ناوبری مدال و یک کشوی ناوبری دائمی.

کشوی ناوبری مودال

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

کشوی ناوبری مودال با آیتم‌ها

کشوی ناوبری دائمی

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

کشوی ناوبری دائمی با موارد

پیاده‌سازی ناوبری پویا

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

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

  1. با به‌روزرسانی کاتالوگ نسخه و اسکریپت ساخت برنامه، وابستگی Gradle را برای دریافت این کامپوننت اضافه کنید، سپس همگام‌سازی Gradle را انجام دهید:

gradle/libs.versions.toml

[versions]
material3AdaptiveNavSuite = "1.3.0"

[libraries]
androidx-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3AdaptiveNavSuite" }

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.navigation.suite)
}
  1. تابع قابل ترکیب ReplyNavigationWrapper() را در ReplyApp.kt پیدا کنید و Column و محتویات آن را با NavigationSuiteScaffold جایگزین کنید:

پاسخApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    NavigationSuiteScaffold(
        navigationSuiteItems = {
            ReplyDestination.entries.forEach {
                item(
                    selected = it == selectedDestination,
                    onClick = { /*TODO update selection*/ },
                    icon = {
                        Icon(
                            imageVector = it.icon,
                            contentDescription = stringResource(it.labelRes)
                        )
                    },
                    label = {
                        Text(text = stringResource(it.labelRes))
                    },
                )
            }
        }
    ) {
        content()
    }
}

آرگومان navigationSuiteItems بلوکی است که به شما امکان می‌دهد با استفاده از تابع item() موارد را اضافه کنید، مشابه اضافه کردن موارد در LazyColumn . درون لامبدا انتهایی، این کد content() را که به عنوان آرگومان به ReplyNavigationWrapperUI() ارسال شده است، فراخوانی می‌کند.

برنامه را روی شبیه‌ساز اجرا کنید و سعی کنید اندازه‌ها را بین گوشی، تاشو و تبلت تغییر دهید، خواهید دید که نوار ناوبری به یک ریل ناوبری و برعکس تغییر می‌کند.

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

  1. کد زیر را درست قبل از فراخوانی NavigationSuiteScaffold اضافه کنید:

پاسخApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    val windowSize = with(LocalDensity.current) {
        currentWindowSize().toSize().toDpSize()
    }
    val layoutType = if (windowSize.width >= 1200.dp) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(
            currentWindowAdaptiveInfo()
        )
    }

    NavigationSuiteScaffold(
        layoutType = layoutType,
        ...
    ) {
        content()
    }
}

این کد ابتدا اندازه پنجره را دریافت کرده و با استفاده از currentWindowSize() و LocalDensity.current آن را به واحدهای DP تبدیل می‌کند و سپس عرض پنجره را برای تعیین نوع طرح‌بندی رابط کاربری ناوبری مقایسه می‌کند. اگر عرض پنجره حداقل 1200.dp باشد، از NavigationSuiteType.NavigationDrawer استفاده می‌کند. در غیر این صورت، به محاسبه پیش‌فرض برمی‌گردد.

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

نمایش تغییرات سازگاری برای اندازه‌های مختلف دستگاه‌ها.

تبریک می‌گویم، شما انواع مختلف ناوبری را برای پشتیبانی از انواع مختلف اندازه‌ها و حالت‌های پنجره یاد گرفته‌اید!

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

۵. استفاده از فضای صفحه نمایش

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

متریال ۳ سه طرح‌بندی متعارف تعریف می‌کند که هر کدام پیکربندی‌هایی برای کلاس‌های اندازه پنجره فشرده، متوسط ​​و گسترده دارند. طرح‌بندی متعارف List Detail برای این مورد استفاده عالی است و در حالت نوشتن با نام ListDetailPaneScaffold در دسترس است.

  1. با اضافه کردن وابستگی‌های زیر و انجام همگام‌سازی Gradle، این کامپوننت را دریافت کنید:

gradle/libs.versions.toml

[libraries]
androidx-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "material3Adaptive" }
androidx-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "material3Adaptive" }

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.layout)
    implementation(libs.androidx.material3.adaptive.navigation)
}
  1. تابع قابل ترکیب ReplyAppContent() را در ReplyApp.kt پیدا کنید، که در حال حاضر فقط با فراخوانی ReplyListPane() صفحه لیست را نشان می‌دهد. این پیاده‌سازی را با ListDetailPaneScaffold با وارد کردن کد زیر جایگزین کنید. از آنجایی که این یک API آزمایشی است، حاشیه‌نویسی @OptIn را نیز به تابع ReplyAppContent() اضافه خواهید کرد:

پاسخApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            ReplyListPane(replyHomeUIState, onEmailClick)
        },
        detailPane = {
            ReplyDetailPane(replyHomeUIState.emails.first())
        }
    )
}

این کد ابتدا با استفاده از rememberListDetailPaneNavigator () یک ناوبر ایجاد می‌کند. rememberListDetailPaneNavigator () . ناوبر تا حدودی کنترل می‌کند که کدام پنجره نمایش داده شود و چه محتوایی باید در آن پنجره نمایش داده شود، که بعداً نشان داده خواهد شد.

ListDetailPaneScaffold وقتی کلاس اندازه عرض پنجره باز می‌شود، دو پنل را نشان می‌دهد. در غیر این صورت، بر اساس مقادیر ارائه شده برای دو پارامتر، یک پنل یا پنل دیگر را نشان می‌دهد: دستورالعمل scaffold و مقدار scaffold. برای دریافت رفتار پیش‌فرض، این کد از دستورالعمل scaffold و مقدار scaffold ارائه شده توسط navigator استفاده می‌کند.

پارامترهای مورد نیاز باقی‌مانده، لامبداهای قابل ترکیب برای پنل‌ها هستند. ReplyListPane() و ReplyDetailPane() (که در ReplyListContent.kt یافت می‌شوند) به ترتیب برای پر کردن نقش‌های پنل‌های لیست و جزئیات استفاده می‌شوند. ReplyDetailPane() یک آرگومان ایمیل دریافت می‌کند، بنابراین فعلاً این کد از اولین ایمیل از لیست ایمیل‌های موجود در ReplyHomeUIState استفاده می‌کند.

برنامه را اجرا کنید و نمای شبیه‌ساز را به حالت تاشو یا تبلت تغییر دهید (همچنین ممکن است مجبور شوید جهت را تغییر دهید) تا طرح دو قسمتی را ببینید. این طرح از قبل خیلی بهتر به نظر می‌رسد!

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

  1. ReplyHomeViewModel.kt را باز کنید و کلاس داده ReplyHomeUIState را پیدا کنید. یک ویژگی برای ایمیل انتخاب شده با مقدار پیش‌فرض null اضافه کنید:

پاسخHomeViewModel.kt

data class ReplyHomeUIState(
    val emails : List<Email> = emptyList(),
    val selectedEmail: Email? = null,
    val loading: Boolean = false,
    val error: String? = null
)
  1. در همان فایل، ReplyHomeViewModel یک تابع setSelectedEmail() دارد که وقتی کاربر روی یک آیتم لیست ضربه می‌زند، فراخوانی می‌شود. این تابع را طوری تغییر دهید که حالت رابط کاربری را کپی کرده و ایمیل انتخاب شده را ثبت کند:

پاسخHomeViewModel.kt

fun setSelectedEmail(email: Email) {
    _uiState.update {
        it.copy(selectedEmail = email)
    }
}

نکته‌ای که باید در نظر بگیرید این است که قبل از اینکه کاربر روی هر آیتمی ضربه بزند و ایمیل انتخاب شده null باشد، چه اتفاقی می‌افتد. چه چیزی باید در پنل جزئیات نمایش داده شود؟ روش‌های مختلفی برای مدیریت این مورد وجود دارد، مانند نمایش اولین آیتم در لیست به صورت پیش‌فرض.

  1. در همان فایل، تابع observeEmails() را تغییر دهید. وقتی لیست ایمیل‌ها بارگذاری شد، اگر در حالت قبلی رابط کاربری، ایمیل انتخابی وجود نداشت، آن را روی اولین مورد تنظیم کنید:

پاسخHomeViewModel.kt

private fun observeEmails() {
    viewModelScope.launch {
        emailsRepository.getAllEmails()
            .catch { ex ->
                _uiState.value = ReplyHomeUIState(error = ex.message)
            }
            .collect { emails ->
                val currentSelection = _uiState.value.selectedEmail
                _uiState.value = ReplyHomeUIState(
                    emails = emails,
                    selectedEmail = currentSelection ?: emails.first()
                )
            }
    }
}
  1. به ReplyApp.kt برگردید و در صورت موجود بودن، از ایمیل انتخاب شده برای پر کردن محتوای پنل جزئیات استفاده کنید:

پاسخApp.kt

ListDetailPaneScaffold(
    // ...
    detailPane = {
        if (replyHomeUIState.selectedEmail != null) {
            ReplyDetailPane(replyHomeUIState.selectedEmail)
        }
    }
)

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

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

  1. برای رفع این مشکل، کد زیر را به عنوان لامبدا ارسالی به ReplyListPane وارد کنید:

پاسخApp.kt

ListDetailPaneScaffold(
    // ...
    listPane = {
        ReplyListPane(
            replyHomeUIState = replyHomeUIState,
            onEmailClick = { email ->
                onEmailClick(email)
                navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
            }
        )
    },
    // ...
)

این لامبدا از ناویگاتوری که قبلاً ایجاد شده است برای افزودن رفتار اضافی هنگام کلیک روی یک آیتم استفاده می‌کند. لامبدا اصلی ارسال شده به این تابع را فراخوانی می‌کند و سپس تابع navigator.navigateTo() را نیز فراخوانی می‌کند تا مشخص کند کدام صفحه باید نمایش داده شود. هر صفحه در scaffold نقشی مرتبط با آن دارد و برای صفحه جزئیات، این نقش ListDetailPaneScaffoldRole.Detail است. در پنجره‌های کوچک‌تر، این ظاهر را ایجاد می‌کند که برنامه به جلو حرکت کرده است.

این برنامه همچنین باید اتفاقاتی را که هنگام فشردن دکمه بازگشت از پنل جزئیات توسط کاربر رخ می‌دهد، مدیریت کند و این رفتار بسته به اینکه یک پنل یا دو پنل قابل مشاهده باشد، متفاوت خواهد بود.

  1. با اضافه کردن کد زیر، از پیمایش به عقب پشتیبانی کنید.

پاسخApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    BackHandler(navigator.canNavigateBack()) {
        navigator.navigateBack()
    }

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            AnimatedPane {
                ReplyListPane(
                    replyHomeUIState = replyHomeUIState,
                    onEmailClick = { email ->
                        onEmailClick(email)
                        navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
                    }
                )
            }
        },
        detailPane = {
            AnimatedPane {
                if (replyHomeUIState.selectedEmail != null) {
                    ReplyDetailPane(replyHomeUIState.selectedEmail)
                }
            }
        }
    )
}

ناویگاتور وضعیت کامل ListDetailPaneScaffold ، امکان بازگشت به عقب و نحوه‌ی انجام کار در تمام این سناریوها را می‌داند. این کد یک BackHandler ایجاد می‌کند که هر زمان ناویگاتور بتواند به عقب برگردد، فعال می‌شود و درون لامبدا، تابع navigateBack() فراخوانی می‌کند. همچنین، برای روان‌تر شدن انتقال بین پنل‌ها، هر پنل در یک AnimatedPane() قابل ترکیب قرار می‌گیرد.

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

نمایش تغییرات سازگاری برای اندازه‌های مختلف دستگاه‌ها.

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

۶. تبریک

تبریک! شما این آزمایشگاه کد را با موفقیت به پایان رساندید و یاد گرفتید که چگونه برنامه‌ها را با Jetpack Compose تطبیق‌پذیر کنید.

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

بعدش چی؟

سایر آزمایشگاه‌های کد را در مسیر Compose بررسی کنید.

برنامه‌های نمونه

  • نمونه‌های نوشتن، مجموعه‌ای از برنامه‌های بسیاری هستند که بهترین شیوه‌های توضیح داده شده در codelabs را در خود جای داده‌اند.

اسناد مرجع