ایجاد یک طرح داشبورد پاسخگو برای JetLagged با Jetpack Compose



ارسال شده توسط Rebecca Franks – Developer Relations Engineer

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


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

پس از انتشار JetLagged نمونه در Google I/O 2023، تصمیم گرفتیم نمونه های بیشتری را به آن اضافه کنیم. به طور خاص، ما می‌خواستیم نشان دهیم چگونه می‌توان از Compose برای ایجاد یک طرح داشبورد مانند استفاده کرد. این مقاله نشان می دهد که چگونه به این امر دست یافته ایم.

تصویر متحرک نشان دهنده طراحی پاسخگو در Jetlagged که در آن آیتم ها موقعیت ها را به صورت خودکار متحرک می کنند

طراحی پاسخگو در Jetlagged که در آن آیتم ها موقعیت ها را به طور خودکار متحرک می کنند

از FlowRow و FlowColumn برای ایجاد طرح‌بندی‌هایی که به اندازه‌های مختلف صفحه نمایش پاسخ می‌دهند، استفاده کنید

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

در مثال JetLagged از a استفاده می کنیم FlowRow، با یک maxItemsInEachRow روی 3 تنظیم کنید. این تضمین می‌کند که فضای موجود برای داشبورد را به حدا،ر می‌رس،م و هر کارت را در یک ردیف یا ستون قرار می‌دهیم که در آن فضا به طور عاقلانه استفاده شود، و در دستگاه‌های تلفن همراه، ما عمدتاً 1 کارت در هر ردیف داریم، فقط اگر موارد کوچک‌تر باشند. آیا در هر ردیف دو عدد قابل مشاهده است؟

برخی از کارت‌ها از اصلاح‌کننده‌هایی استفاده می‌کنند که اندازه دقیقی را مشخص نمی‌کنند، بنابراین به کارت‌ها اجازه می‌دهند تا پهنای موجود را پر کنند، به ،وان مثال با استفاده از Modifier.widthIn(حدا،ر = 400.dp)، یا اندازه خاصی را تنظیم کنید، مانند Modifier.width(200.dp).

FlowRow(
    modifier = Modifier.fillMaxSize(),
    ،rizontalArrangement = Arrangement.Center,
    verticalArrangement = Arrangement.Center,
    maxItemsInEachRow = 3
) {
    Box(modifier = Modifier.widthIn(max = 400.dp))
    Box(modifier = Modifier.width(200.dp))
    Box(modifier = Modifier.size(200.dp))
    // etc 
}

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

از WindowSizeCl،es برای تمایز بین دستگاه ها استفاده کنید

WindowSizeCl،es برای ایجاد نقاط ش،ت در رابط کاربری ما برای زم، که ،اصر باید متفاوت نمایش داده شوند مفید هستند. در JetLagged، ما از ک،‌ها استفاده می‌کنیم تا بد،م آیا باید کارت‌ها را در ستون‌ها قرار دهیم یا آنها را یکی پس از دیگری در جریان نگه داریم.

به ،وان مثال، اگر WindowWidthSizeCl،.COMPACT، موارد را در همان حالت نگه می داریم FlowRow، جایی که گویی طرح آن بزرگتر از فشرده است، در یک قرار می گیرند ستون جریان، تو در تو a FlowRow:

            FlowRow(
                modifier = Modifier.fillMaxSize(),
                ،rizontalArrangement = Arrangement.Center,
                verticalArrangement = Arrangement.Center,
                maxItemsInEachRow = 3
            ) {
                JetLaggedSleepGraphCard(uiState.value.sleepGraphData)
                if (windowSizeCl، == WindowWidthSizeCl،.COMPACT) {
                    AverageTimeInBedCard()
                    AverageTimeAsleepCard()
                } else {
                    FlowColumn {
                        AverageTimeInBedCard()
                        AverageTimeAsleepCard()
                    }
                }
                if (windowSizeCl، == WindowWidthSizeCl،.COMPACT) {
                    WellnessCard(uiState.value.wellnessData)
                    HeartRateCard(uiState.value.heartRateData)
                } else {
                    FlowColumn {
                        WellnessCard(uiState.value.wellnessData)
                        HeartRateCard(uiState.value.heartRateData)
                    }
                }
            }

از منطق بالا، UI به روش های زیر در اندازه های مختلف دستگاه ظاهر می شود:

مقایسه کنار هم از تفاوت‌های رابط کاربری در سه دستگاه با اندازه‌های مختلف

UI مختلف در دستگاه های با اندازه های مختلف

از movableContentOf برای حفظ بیت هایی از حالت رابط کاربری در سراسر تغییر اندازه صفحه استفاده کنید

محتوای متحرک به شما این امکان را می دهد که محتوای یک Composable را ذخیره کنید تا آن را در سلسله مراتب طرح بندی خود بدون از دست دادن حالت حرکت دهید. باید برای محتوایی استفاده شود که به نظر می رسد ی،ان باشد – فقط در یک مکان متفاوت روی صفحه.

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

اگر بتو،د از همان مفهوم در Compose برای جابجایی موارد روی صفحه بدون از دست دادن حالت داخلی آنها استفاده کنید چه؟

سناریوی زیر را در نظر بگیرید: متفاوت را تعریف کنید کاشی ،یب‌هایی که مقدار متحرک بی‌نهایتی بین 0 تا 100 در 5000 میلی‌ث،ه نمایش می‌دهند.

@Composable
fun Tile1() {
    val repeatingAnimation = rememberInfiniteTransition()

    val float = repeatingAnimation.animateFloat(
        initialValue = 0f,
        targetValue = 100f,
        animationSpec = infiniteRepeatable(repeatMode = RepeatMode.Reverse,
            animation = tween(5000))
    )
    Box(modifier = Modifier
        .size(100.dp)
        .background(purple, RoundedCornerShape(8.dp))){
        Text("Tile 1 ${float.value.roundToInt()}",
            modifier = Modifier.align(Alignment.Center))
    }
}

سپس آنها را با استفاده از طرح بندی ستونی روی صفحه نمایش می دهیم – ،میشن های بی نهایت را در حین حرکت نشان می دهد:

کاشی بنفش که در ستونی بالای کاشی صورتی انباشته شده است. هر دو کاشی یک شمارنده نشان می‌دهند که از 0 تا 100 به بالا می‌شمارند و تا 0 برمی‌گردند

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

@Composable
fun Wit،utMovableContentDemo() {
    val mode = remember {
        mutableStateOf(Mode.Portrait)
    }
    if (mode.value == Mode.Landscape) {
        Row {
           Tile1()
           Tile2()
        }
    } else {
        Column {
           Tile1()
           Tile2()
        }
    }
}

این بسیار استاندارد به نظر می رسد، اما اجرای آن بر روی دستگاه – می تو،م ببینیم که جابجایی بین دو طرح بندی باعث راه اندازی مجدد ،میشن های ما می شود.

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

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

val tiles = remember {
        movableContentOf {
            Tile1()
            Tile2()
        }
 }

در حال حاضر به جای فراخو، دوباره composable های خود را در داخل ستون و ردیف به ترتیب تماس می گیریم کاشی () در عوض

@Composable
fun MovableContentDemo() {
    val mode = remember {
        mutableStateOf(Mode.Portrait)
    }
    val tiles = remember {
        movableContentOf {
            Tile1()
            Tile2()
        }
    }
    Box(modifier = Modifier.fillMaxSize()) {
        if (mode.value == Mode.Landscape) {
            Row {
                tiles()
            }
        } else {
            Column {
                tiles()
            }
        }

        Button(onClick = {
            if (mode.value == Mode.Portrait) {
                mode.value = Mode.Landscape
            } else {
                mode.value = Mode.Portrait
            }
        }, modifier = Modifier.align(Alignment.BottomCenter)) {
            Text("Change layout")
        }
    }
}

سپس گره های تولید شده توسط آن Composable ها را به خاطر می آورد و حالت داخلی را که این Composable ها در حال حاضر دارند حفظ می کند.

کاشی بنفش که در ستونی بالای کاشی صورتی انباشته شده است. هر دو کاشی یک شمارنده را نشان می‌دهند که از 0 تا 100 به سمت بالا می‌شمارند. ستون به یک ردیف و دوباره به ستون تغییر می‌کند، و با تغییر طرح‌بندی، شمارنده یکپارچه ادامه می‌یابد.

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

با استفاده از این مفهوم، می‌تو،م با قرار دادن کارت‌ها، حالت حباب متحرک کارت‌های خود را حفظ کنیم محتوای متحرک:

Language
val timeSleepSummaryCards = remember { movableContentOf { AverageTimeInBedCard() AverageTimeAsleepCard() } } LookaheadScope { FlowRow( modifier = Modifier.fillMaxSize(), ،rizontalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center, maxItemsInEachRow = 3 ) { //.. if (windowSizeCl، == WindowWidthSizeCl،.Compact) { timeSleepSummaryCards() } else { FlowColumn { timeSleepSummaryCards() } } // } }

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

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

از Modifier.animateBounds() برای داشتن ،میشن های روان بین اندازه های مختلف پنجره استفاده کنید

از مثال بالا، می‌تو،م ببینیم که وضعیت بین تغییرات در اندازه طرح (یا خود طرح‌بندی) حفظ می‌شود، اما تفاوت بین این دو طرح‌بندی کمی سخت است. ما دوست داریم که بدون مشکل بین این دو حالت متحرک شود.

در جدیدترین نوشتن-بم-آلفا (03.09.2024)، یک اصلاح کننده سفارشی آزمایشی جدید وجود دارد، Modifier.animateBounds(). را animateBounds اصلاح کننده نیاز به a LookaheadScope.

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

برای استفاده Modifier.animateBounds()، سطح بالایی را می پیچیم FlowRow در یک LookaheadScope، و سپس اعمال کنید animateBounds اصلاح کننده برای هر کارت همچنین می‌تو،م نحوه اجرای ،میشن را با مشخص ، گزینه سفارشی کنیم مرزها تبدیل می شوند پارامتر یک مشخصات فنری سفارشی:

val boundsTransform = { _ : Rect, _: Rect ->
   spring(
       dampingRatio = Spring.DampingRatioNoBouncy,
       stiffness = Spring.StiffnessMedium,
       visibilityThres،ld = Rect.VisibilityThres،ld
   )
}


LookaheadScope {
   val animateBoundsModifier = Modifier.animateBounds(
       lookaheadScope = this@LookaheadScope,
       boundsTransform = boundsTransform)
   val timeSleepSummaryCards = remember {
       movableContentOf {
           AverageTimeInBedCard(animateBoundsModifier)
           AverageTimeAsleepCard(animateBoundsModifier)
       }
   }
   FlowRow(
       modifier = Modifier
           .fillMaxSize()
           .windowInsetsPadding(insets),
       ،rizontalArrangement = Arrangement.Center,
       verticalArrangement = Arrangement.Center,
       maxItemsInEachRow = 3
   ) {
       JetLaggedSleepGraphCard(uiState.value.sleepGraphData, animateBoundsModifier.widthIn(max = 600.dp))
       if (windowSizeCl، == WindowWidthSizeCl،.Compact) {
           timeSleepSummaryCards()
       } else {
           FlowColumn {
               timeSleepSummaryCards()
           }
       }


       FlowColumn {
           WellnessCard(
               wellnessData = uiState.value.wellnessData,
               modifier = animateBoundsModifier
                   .widthIn(max = 400.dp)
                   .heightIn(min = 200.dp)
           )
           HeartRateCard(
               modifier = animateBoundsModifier
                   .widthIn(max = 400.dp, min = 200.dp),
               uiState.value.heartRateData
           )
       }
   }
}

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

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

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

تصویر متحرک نشان دهنده طراحی پاسخگو در Jetlagged که در آن آیتم ها موقعیت ها را به صورت خودکار متحرک می کنند

خلاصه

همانطور که از این مقاله می بینید، استفاده از Compose ما را قادر می سازد تا با استفاده از طرح بندی های جریان، یک طرح داشبورد مانند پاسخگو بسازیم. WindowSizeCl،es، محتوای متحرک و LookaheadScope. این مفاهیم همچنین می توانند برای چیدمان های خود استفاده شوند که ممکن است مواردی نیز در آنها حرکت کنند.

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


منبع: http://android-developers.googleblog.com/2024/10/creating-responsive-dashboard-layout-for-jetlagged-jetpack-compose.html