Jetpack Compose طراحی و ساخت رابط کاربری اپلیکیشن را بسیار سادهتر میکند. Compose، State را از طریق مراحل زیر به عناصر UI تبدیل میکند:
- Composition عناصر
- Layout عناصر
- Drawing عناصر
این سند روی مرحله Layout عناصر تمرکز دارد و برخی از building blockهایی را توضیح میدهد که Compose برای چیدمان عناصر UI در اختیار شما قرار میدهد.
اهداف Layout در Compose
پیادهسازی سیستم Layout در Jetpack Compose دو هدف اصلی دارد:
- عملکرد بالا
- امکان ساخت آسان Layoutهای سفارشی
نکته: در سیستم قدیمی Android View، هنگام تو در تو کردن برخی Viewها مانند RelativeLayout ممکن بود با مشکلات Performance مواجه شوید. اما Compose از چندبار اندازهگیری جلوگیری میکند، بنابراین میتوانید Layoutها را با عمق زیاد تو در تو کنید بدون اینکه عملکرد بهطور جدی تحت تأثیر قرار بگیرد.
مبانی توابع Composable
توابع Composable، building block اصلی Compose هستند. یک تابع Composable تابعی است که Unit برمیگرداند و بخشی از UI شما را توصیف میکند. این تابع مقداری ورودی دریافت میکند و چیزی را که روی صفحه نمایش داده میشود تولید میکند.
یک تابع Composable ممکن است چندین عنصر UI تولید کند. با این حال، اگر مشخص نکنید این عناصر چگونه باید چیده شوند، Compose ممکن است آنها را به شکلی قرار دهد که مطلوب شما نباشد.
برای مثال، کد زیر دو عنصر Text ایجاد میکند:
@Composable
fun ArtistCard() {
Text("Alfred Sisley")
Text("3 minutes ago")
}
اگر مشخص نکنید این دو عنصر چگونه باید چیده شوند، Compose ممکن است Textها را روی یکدیگر قرار دهد و باعث شود خواندن آنها دشوار شود.
Compose مجموعهای از Layoutهای آماده را فراهم میکند تا بتوانید عناصر UI را بهدرستی مرتب کنید. همچنین ساخت Layoutهای سفارشی و تخصصیتر را نیز آسان میکند.
Componentهای استاندارد Layout
در بسیاری از موارد میتوانید فقط از عناصر استاندارد Layout در Compose استفاده کنید.
برای قرار دادن عناصر بهصورت عمودی در صفحه، از Column استفاده کنید:
@Composable
fun ArtistCardColumn() {
Column {
Text("Alfred Sisley")
Text("3 minutes ago")
}
}
به همین شکل، برای قرار دادن عناصر بهصورت افقی در صفحه از Row استفاده کنید.
هر دو Column و Row امکان تنظیم Alignment عناصر داخل خود را فراهم میکنند.
@Composable
fun ArtistCardRow(artist: Artist) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(bitmap = artist.image, contentDescription = "Artist image")
Column {
Text(artist.name)
Text(artist.lastSeenOnline)
}
}
}
Box استفاده کنید. Box همچنین امکان تنظیم Alignment مشخص برای عناصر داخل خود را فراهم میکند.@Composable
fun ArtistAvatar(artist: Artist) {
Box {
Image(bitmap = artist.image, contentDescription = "Artist image")
Icon(Icons.Filled.Check, contentDescription = "Check mark")
}
}
در بسیاری از مواقع همین building blockها کافی هستند. میتوانید تابع Composable مخصوص خود را بنویسید و این Layoutها را با هم ترکیب کنید تا Layout پیچیدهتری متناسب با اپلیکیشن خود ایجاد کنید.
نکته: Compose، Layoutهای تو در تو را بهصورت کارآمد مدیریت میکند و به همین دلیل این روش برای ساخت UIهای پیچیده مناسب است. این موضوع نسبت به Android Views یک بهبود محسوب میشود، چون در Android Views معمولاً برای جلوگیری از مشکلات عملکردی باید از Layoutهای تو در توی زیاد اجتناب میکردید.
برای تعیین موقعیت فرزندان داخل Row، آرگومانهای horizontalArrangement و verticalAlignment را تنظیم کنید.
برای Column، آرگومانهای verticalArrangement و horizontalAlignment را تنظیم کنید:
@Composable
fun ArtistCardArrangement(artist: Artist) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
Image(bitmap = artist.image, contentDescription = "Artist image")
Column { /*...*/ }
}
}
مدل Layout
در مدل Layout، درخت UI در یک مرحله Layout میشود. ابتدا از هر Node خواسته میشود خودش را اندازهگیری کند، سپس بهصورت بازگشتی فرزندانش را اندازهگیری کند و محدودیتهای اندازه را به سمت پایین درخت منتقل کند.
سپس Leaf nodeها اندازهگذاری و در جای خود قرار داده میشوند و اندازههای نهایی و دستورهای Placement دوباره به سمت بالا درخت فرستاده میشوند.
بهصورت خلاصه:
- Parentها قبل از فرزندان خود Measure میشوند.
- اما بعد از فرزندان خود Size و Place میشوند.
تابع زیر را در نظر بگیرید:
@Composable
fun SearchResult() {
Row {
Image(
// ...
)
Column {
Text(
// ...
)
Text(
// ...
)
}
}
}
این تابع درخت UI زیر را ایجاد میکند:
SearchResult
Row
Image
Column
Text
Text
در مثال SearchResult، Layout درخت UI با ترتیب زیر انجام میشود:
- از Node اصلی
Rowخواسته میشود خودش را Measure کند. Rowاز اولین فرزند خود یعنیImageمیخواهد Measure شود.Imageیک Leaf node است، یعنی فرزندی ندارد، بنابراین اندازه خود را گزارش میدهد و دستورهای Placement را برمیگرداند.Rowاز دومین فرزند خود یعنیColumnمیخواهد Measure شود.Columnاز اولین فرزندTextخود میخواهد Measure شود.- اولین
Textیک Leaf node است، بنابراین اندازه خود را گزارش میدهد و دستورهای Placement را برمیگرداند. Columnاز دومین فرزندTextخود میخواهد Measure شود.- دومین
Textنیز یک Leaf node است و اندازه و دستورهای Placement را برمیگرداند. - حالا که
Columnفرزندان خود را Measure، Size و Place کرده است، میتواند اندازه و موقعیت خودش را تعیین کند. - در نهایت، چون
Rowفرزندان خود را Measure، Size و Place کرده است، میتواند اندازه و موقعیت خودش را مشخص کند.
Performance
Compose با Measure کردن هر Child فقط یک بار، عملکرد بالایی ایجاد میکند.
Single-pass measurement برای Performance مناسب است و به Compose اجازه میدهد درختهای UI عمیق را بهصورت کارآمد مدیریت کند.
اگر یک عنصر Child خود را دو بار Measure کند و آن Child نیز هرکدام از فرزندانش را دو بار Measure کند و همین روند ادامه پیدا کند، یک بار Layout کردن کل UI نیاز به مقدار زیادی پردازش خواهد داشت و حفظ عملکرد مناسب اپلیکیشن سخت میشود.
اگر Layout شما به هر دلیلی به چند بار Measurement نیاز داشته باشد، Compose یک سیستم مخصوص به نام Intrinsic measurements ارائه میدهد.
از آنجا که Measurement و Placement دو زیرمرحله جداگانه از Layout pass هستند، تغییراتی که فقط روی Placement عناصر اثر میگذارند و Measurement را تغییر نمیدهند میتوانند بهصورت جداگانه اجرا شوند.
استفاده از Modifierها در Layout
همانطور که در بخش Compose modifiers توضیح داده میشود، میتوانید از Modifierها برای تزئین یا گسترش قابلیتهای Composableها استفاده کنید.
Modifierها برای سفارشیسازی Layout بسیار مهم هستند.
برای مثال، در کد زیر چند Modifier بهصورت زنجیرهای برای سفارشیسازی ArtistCard استفاده شدهاند:
@Composable
fun ArtistCardModifiers(
artist: Artist,
onClick: () -> Unit
) {
val padding = 16.dp
Column(
Modifier
.clickable(onClick = onClick)
.padding(padding)
.fillMaxWidth()
) {
Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
Spacer(Modifier.size(padding))
Card(
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
) { /*...*/ }
}
}
در کد بالا چند تابع Modifier با هم استفاده شدهاند:
clickableباعث میشود یک Composable به ورودی کاربر واکنش نشان دهد و Ripple نمایش دهد.paddingفضای اطراف یک عنصر را مشخص میکند.fillMaxWidthباعث میشود Composable حداکثر عرضی را که Parent در اختیار آن قرار داده پر کند.size()عرض و ارتفاع ترجیحی یک عنصر را مشخص میکند.
نکته: Modifierها از بعضی جهات نقشی مشابه Layout parameterها در Layoutهای مبتنی بر View دارند. با این حال، چون برخی Modifierها مخصوص Scope مشخصی هستند، Type safety بیشتری ارائه میدهند و همچنین کمک میکنند راحتتر متوجه شوید چه قابلیتهایی برای یک Layout خاص قابل استفاده هستند.
در Layoutهای XML گاهی تشخیص اینکه یک Attribute مشخص برای یک View خاص قابل استفاده است یا نه دشوار است.
Layoutهای Scrollable
برای یادگیری بیشتر درباره Layoutهای قابل اسکرول، مستندات Gesture در Compose را بررسی کنید.
برای Listها و Lazy Listها نیز میتوانید مستندات List در Compose را ببینید.
Layoutهای Responsive
یک Layout باید با توجه به جهتهای مختلف صفحه و اندازههای مختلف Form factor طراحی شود.
Compose چند مکانیزم آماده برای سازگار کردن Layoutهای Composable با Configurationهای مختلف صفحه فراهم میکند.
Constraints
برای اینکه محدودیتهایی را که از Parent دریافت میکنید بدانید و Layout را بر اساس آن طراحی کنید، میتوانید از BoxWithConstraints استفاده کنید.
Measurement constraintها داخل Scope مربوط به Content lambda در دسترس هستند. میتوانید از این Constraintها برای ساخت Layoutهای مختلف در Configurationهای مختلف صفحه استفاده کنید:
@Composable
fun WithConstraintsComposable() {
BoxWithConstraints {
Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
}
}
Layoutهای مبتنی بر Slot
Compose با dependency مربوط به Material، مجموعه بزرگی از Composableهای مبتنی بر Material Design ارائه میدهد تا ساخت UI آسانتر شود.
عناصری مانند موارد زیر بهصورت آماده ارائه میشوند:
DrawerFloatingActionButtonTopAppBar
Componentهای Material بهطور گسترده از Slot API استفاده میکنند. این یک الگو در Compose است که لایهای از سفارشیسازی را روی Composableها فراهم میکند.
این رویکرد Componentها را انعطافپذیرتر میکند، چون آنها یک عنصر Child دریافت میکنند که میتواند خودش را پیکربندی کند، بهجای اینکه Component مجبور باشد برای تمام تنظیمات ممکن Child یک پارامتر جداگانه ارائه دهد.
Slotها یک فضای خالی در UI ایجاد میکنند تا توسعهدهنده آن را به شکل دلخواه پر کند.
برای مثال، در TopAppBar میتوانید Slotهای مختلفی را سفارشی کنید.
Composableها معمولاً یک Composable lambda به نام content دریافت میکنند:
content: @Composable () -> Unit
Slot APIها چندین پارامتر content برای کاربردهای مشخص ارائه میکنند.
برای مثال، TopAppBar به شما اجازه میدهد محتواهای زیر را تعیین کنید:
titlenavigationIconactions
بهعنوان مثال، Scaffold به شما امکان میدهد یک UI را با ساختار اصلی Layout در Material Design پیادهسازی کنید.
Scaffold برای رایجترین Componentهای سطح بالای Material، Slotهایی ارائه میکند؛ مانند:
TopAppBarBottomAppBarFloatingActionButtonDrawer
با استفاده از Scaffold، راحتتر میتوانید مطمئن شوید این Componentها در محل مناسب قرار گرفتهاند و بهدرستی با یکدیگر کار میکنند.
@Composable
fun HomeScreen(/*...*/) {
ModalNavigationDrawer(drawerContent = { /* ... */ }) {
Scaffold(
topBar = { /*...*/ }
) { contentPadding ->
// ...
}
}
}
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: منبع مستندات