لایهبندی معماری Jetpack Compose
این صفحه یک نمای کلی سطح بالا از لایههای معماری تشکیلدهنده Jetpack Compose و اصول اصلیای که طراحی آن را شکل میدهند ارائه میکند.
Jetpack Compose یک پروژه یکپارچه و تکبخشی نیست؛ بلکه از چندین ماژول ساخته شده که کنار هم قرار میگیرند تا یک stack کامل را تشکیل دهند. درک ماژولهای مختلفی که Jetpack Compose را میسازند به شما کمک میکند:
- از سطح abstraction مناسب برای ساخت اپلیکیشن یا کتابخانه خود استفاده کنید.
- بفهمید چه زمانی میتوانید برای کنترل یا سفارشیسازی بیشتر به یک سطح پایینتر بروید.
- وابستگیهای خود را کاهش دهید.
لایهها
لایههای اصلی Jetpack Compose عبارتاند از:
هر لایه روی لایههای پایینتر ساخته شده است و با ترکیب قابلیتها، componentهای سطح بالاتری ایجاد میکند. هر لایه بر اساس APIهای عمومی لایههای پایینتر ساخته میشود تا مرزهای ماژولها حفظ شود و در صورت نیاز بتوانید هر لایه را جایگزین کنید.
بیایید این لایهها را از پایین به بالا بررسی کنیم.
Runtime
این ماژول پایههای اصلی runtime مربوط به Compose را فراهم میکند؛ مانند remember، mutableStateOf، annotation به نام @Composable و SideEffect.
اگر فقط به قابلیتهای مدیریت درخت Compose نیاز دارید و نه UI آن، میتوانید مستقیماً روی این لایه چیزی بسازید.
UI
لایه UI از چندین ماژول تشکیل شده است؛ مانند ui-text، ui-graphics، ui-tooling و موارد دیگر.
این ماژولها پایههای toolkit رابط کاربری را پیادهسازی میکنند؛ مانند LayoutNode، Modifier، input handlerها، layoutهای سفارشی و drawing.
اگر فقط به مفاهیم پایهای یک UI toolkit نیاز دارید، میتوانید روی این لایه کار کنید.
Foundation
این ماژول building blockهای مستقل از design system را برای Compose UI فراهم میکند؛ مانند Row، Column، LazyColumn، تشخیص gestureهای خاص و موارد دیگر.
اگر میخواهید design system مخصوص خودتان را بسازید، میتوانید روی لایه Foundation کار کنید.
Material
این ماژول پیادهسازی سیستم Material Design را برای Compose UI فراهم میکند؛ از جمله سیستم theming، componentهای style شده، ripple indicationها و iconها.
اگر در اپلیکیشن خود از Material Design استفاده میکنید، روی این لایه کار کنید.
اصول طراحی
یکی از اصول راهنمای Jetpack Compose این است که بهجای چند component بزرگ و یکپارچه، قابلیتهای کوچک و متمرکز ارائه دهد که بتوان آنها را کنار هم قرار داد یا compose کرد.
این رویکرد چند مزیت دارد.
کنترل
Componentهای سطح بالاتر معمولاً کارهای بیشتری را برای شما انجام میدهند، اما میزان کنترل مستقیم شما را محدود میکنند. اگر به کنترل بیشتری نیاز داشته باشید، میتوانید به سطح پایینتر بروید و از componentهای سطح پایینتر استفاده کنید.
برای مثال، اگر بخواهید رنگ یک component را animate کنید، ممکن است از API به نام animateColorAsState استفاده کنید:
val color = animateColorAsState(if (condition) Color.Green else Color.Red)
اما اگر لازم باشد component همیشه از رنگ خاکستری شروع شود، نمیتوانید این کار را با این API انجام دهید. در عوض، میتوانید به سطح پایینتر بروید و از API به نام Animatable استفاده کنید:
val color = remember { Animatable(Color.Gray) }
LaunchedEffect(condition) {
color.animateTo(if (condition) Color.Green else Color.Red)
}
API سطح بالاتر یعنی animateColorAsState خودش بر اساس API سطح پایینتر یعنی Animatable ساخته شده است. استفاده از API سطح پایینتر پیچیدهتر است، اما کنترل بیشتری ارائه میدهد.
سطح abstractionای را انتخاب کنید که بهترین تناسب را با نیازهای شما دارد.
سفارشیسازی
ساختن componentهای سطح بالاتر از building blockهای کوچکتر باعث میشود در صورت نیاز، سفارشیسازی componentها بسیار آسانتر شود.
برای مثال، پیادهسازی Button را در نظر بگیرید که توسط لایه Material ارائه شده است:
@Composable
fun Button(
// …
content: @Composable RowScope.() -> Unit
) {
Surface(/* … */) {
CompositionLocalProvider(/* … */) { // تنظیم LocalContentAlpha
ProvideTextStyle(MaterialTheme.typography.button) {
Row(
// …
content = content
)
}
}
}
}
یک Button از ۴ component ساخته شده است:
- یک
Surfaceمربوط به Material که پسزمینه، شکل، مدیریت کلیک و موارد دیگر را فراهم میکند. - یک
CompositionLocalProviderکه alpha مربوط به content را هنگام فعال یا غیرفعال بودن دکمه تغییر میدهد. - یک
ProvideTextStyleکه style پیشفرض متن را مشخص میکند. - یک
Rowکه سیاست layout پیشفرض را برای محتوای دکمه فراهم میکند.
برخی پارامترها و commentها حذف شدهاند تا ساختار واضحتر باشد، اما کل component فقط حدود ۴۰ خط کد است، چون فقط همین ۴ component را کنار هم قرار میدهد تا دکمه را پیادهسازی کند.
Componentهایی مانند Button درباره اینکه چه پارامترهایی را در معرض استفاده قرار دهند، دیدگاه مشخصی دارند. آنها بین امکان سفارشیسازیهای رایج و جلوگیری از زیاد شدن بیش از حد پارامترها تعادل برقرار میکنند؛ چون تعداد زیاد پارامترها میتواند استفاده از component را سختتر کند.
برای مثال، componentهای Material، سفارشیسازیهایی را ارائه میدهند که در سیستم Material Design مشخص شدهاند و این کار پیروی از اصول Material Design را آسان میکند.
اما اگر بخواهید سفارشیسازیای انجام دهید که فراتر از پارامترهای یک component است، میتوانید یک سطح پایینتر بروید و component را fork کنید.
برای مثال، Material Design مشخص میکند که دکمهها باید پسزمینهای با رنگ یکدست داشته باشند. اگر به پسزمینه gradient نیاز داشته باشید، این گزینه توسط پارامترهای Button پشتیبانی نمیشود. در این حالت میتوانید از پیادهسازی Button در Material بهعنوان مرجع استفاده کنید و component خودتان را بسازید:
@Composable
fun GradientButton(
// …
background: List<Color>,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
Row(
// …
modifier = modifier
.clickable(onClick = {})
.background(
Brush.horizontalGradient(background)
)
) {
CompositionLocalProvider(/* … */) { // تنظیم LocalContentAlpha مربوط به Material
ProvideTextStyle(MaterialTheme.typography.button) {
content()
}
}
}
}
پیادهسازی بالا همچنان از componentهای لایه Material استفاده میکند؛ مانند مفهوم current content alpha در Material و text style فعلی. اما Surface مربوط به Material را با یک Row جایگزین میکند و آن را style میدهد تا ظاهر مورد نظر ایجاد شود.
احتیاط:
وقتی برای سفارشیسازی یک component به لایه پایینتر میروید، مطمئن شوید هیچ قابلیتی را تضعیف نمیکنید؛ برای مثال، پشتیبانی accessibility را نادیده نگیرید. از componentای که fork میکنید بهعنوان راهنما استفاده کنید.
اگر اصلاً نمیخواهید از مفاهیم Material استفاده کنید، مثلاً اگر design system اختصاصی خودتان را میسازید، میتوانید به سطح Foundation بروید و فقط از componentهای لایه Foundation استفاده کنید:
@Composable
fun BespokeButton(
// …
backgroundColor: Color,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
Row(
// …
modifier = modifier
.clickable(onClick = {})
.background(backgroundColor)
) {
// هیچ componentای از Material استفاده نشده است
content()
}
}
Jetpack Compose سادهترین نامها را برای componentهای سطح بالاتر نگه میدارد. برای مثال، androidx.compose.material.Text بر اساس androidx.compose.foundation.text.BasicText ساخته شده است. این کار باعث میشود اگر بخواهید لایههای بالاتر را جایگزین کنید، بتوانید پیادهسازی خودتان را با نامی قابل کشفتر ارائه دهید.
احتیاط:
Fork کردن یک component یعنی دیگر از اضافه شدن قابلیتهای آینده یا bug fixهای component اصلی بهرهمند نمیشوید.
انتخاب abstraction مناسب
فلسفه Compose در ساخت componentهای لایهبندیشده و قابل استفاده مجدد به این معنا نیست که همیشه باید به سراغ پایینترین building blockها بروید. بسیاری از componentهای سطح بالاتر نهتنها قابلیتهای بیشتری ارائه میدهند، بلکه اغلب بهترین روشها، مانند پشتیبانی از accessibility، را نیز پیادهسازی میکنند.
برای مثال، اگر بخواهید به component سفارشی خود gesture support اضافه کنید، میتوانید آن را از ابتدا با استفاده از Modifier.pointerInput بسازید. اما componentهای سطح بالاتری نیز وجود دارند که روی آن ساخته شدهاند و ممکن است نقطه شروع بهتری باشند؛ برای مثال:
Modifier.draggableModifier.scrollableModifier.swipeable
بهعنوان یک قانون کلی، بهتر است روی بالاترین سطح componentای بسازید که قابلیت مورد نیاز شما را ارائه میدهد، تا بتوانید از بهترین روشهایی که در آن componentها وجود دارد بهرهمند شوید.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: منبع مستندات