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

1. مقدمه

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

قبل از غواصی، مهم است که منظورمان از سازگاری را درک کنیم.

سازگاری

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

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

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

چیزی که یاد خواهید گرفت

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

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

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

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

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

چیزی که خواهی ساخت

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

ویترین پشتیبانی از چندین دستگاه که در این کد لبه به دست خواهید آورد

2. راه اندازی شوید

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

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

همچنین، می توانید مخزن را به عنوان یک فایل ZIP دانلود کنید:

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

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

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

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

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

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

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

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

نمای کشیده اولیه در رایانه لوحی

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

3. برنامه ها را سازگار کنید

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

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

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

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

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

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

WindowHeightSizeClass برای ارتفاع فشرده، متوسط ​​و منبسط شده.

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

حالت های فولد

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

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

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

در مورد وضعیت های چین دار و لولا بیشتر بخوانید.

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

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

کتابخانه 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. در فایل ساخت ماژول برنامه، وابستگی کتابخانه جدید را اضافه کنید و سپس یک همگام سازی Gradle را انجام دهید:

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive)
}

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

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

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

MainActivity.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 حذف کنید زیرا محتوای برنامه را پوشش می دهد و برای مراحل بعدی لازم نیست.

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

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

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

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

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

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

ناوبری پایین

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

نوار پیمایش پایین با موارد

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

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

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

کشوی ناوبری معین

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

کشوی ناوبری معین با اقلام

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

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

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

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

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

در حال حاضر، برنامه همیشه بدون توجه به وضعیت دستگاه 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. تابع composable ReplyNavigationWrapper() در ReplyApp.kt پیدا کنید و Column و محتویات آن را با NavigationSuiteScaffold جایگزین کنید:

ReplyApp.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 اضافه کنید:

ReplyApp.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 استفاده می کند. در غیر این صورت، به محاسبه پیش فرض برمی گردد.

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

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

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

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

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

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

متریال 3 سه طرح بندی متعارف را تعریف می کند که هر کدام دارای پیکربندی برای کلاس های اندازه پنجره فشرده، متوسط ​​و گسترش یافته هستند. طرح بندی متعارف 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. تابع composable ReplyAppContent() را در ReplyApp.kt پیدا کنید، که در حال حاضر تنها با فراخوانی ReplyListPane() صفحه لیست را نشان می دهد. با قرار دادن کد زیر، این پیاده سازی را با ListDetailPaneScaffold جایگزین کنید. از آنجایی که این یک API آزمایشی است، حاشیه نویسی @OptIn را نیز در تابع ReplyAppContent() اضافه خواهید کرد:

ReplyApp.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 هنگامی که کلاس اندازه عرض پنجره گسترش می یابد، دو پنجره را نشان می دهد. در غیر این صورت، یک صفحه یا صفحه دیگر را بر اساس مقادیر ارائه شده برای دو پارامتر نشان می دهد: دستور داربست و مقدار داربست. برای دریافت رفتار پیش‌فرض، این کد از دستور داربست و مقدار اسکافولد ارائه شده توسط ناوبر استفاده می‌کند.

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

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

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

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

ReplyHomeViewModel.kt

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

ReplyHomeViewModel.kt

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

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

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

ReplyHomeViewModel.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 بازگردید و از ایمیل انتخابی، در صورت موجود بودن، برای پر کردن محتوای صفحه جزئیات استفاده کنید:

ReplyApp.kt

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

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

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

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

ReplyApp.kt

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

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

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

  1. با افزودن کد زیر از ناوبری برگشت پشتیبانی کنید.

ReplyApp.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() پیچیده می‌شود.

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

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

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

6. تبریک می گویم

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

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

بعدش چی؟

سایر کدها را در مسیر Compose بررسی کنید.

نمونه برنامه ها

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

اسناد مرجع