در حالی که مهاجرت از Views به Compose صرفاً مربوط به UI است، برای انجام یک انتقال ایمن و تدریجی باید موارد زیادی را در نظر گرفت. این صفحه شامل برخی ملاحظات هنگام انتقال برنامه مبتنی بر View شما به Compose است.
در حال انتقال تم برنامه شما
Material Design سیستم طراحی پیشنهادی برای قالب بندی برنامه های اندروید است.
برای برنامههای مبتنی بر View، سه نسخه از Material موجود است:
- Material Design 1 با استفاده از کتابخانه AppCompat (یعنی
Theme.AppCompat.*
) - Material Design 2 با استفاده از کتابخانه MDC-Android (یعنی
Theme.MaterialComponents.*
) - Material Design 3 با استفاده از کتابخانه MDC-Android (یعنی
Theme.Material3.*
)
برای برنامه های Compose، دو نسخه از Material موجود است:
- Material Design 2 با استفاده از کتابخانه Compose Material (یعنی
androidx.compose.material.MaterialTheme
) - Material Design 3 با استفاده از کتابخانه Compose Material 3 (یعنی
androidx.compose.material3.MaterialTheme
)
اگر سیستم طراحی برنامه شما در موقعیت مناسبی است، توصیه می کنیم از آخرین نسخه (Material 3) استفاده کنید. راهنماهای انتقال هم برای Views و هم برای Compose وجود دارد:
هنگام ایجاد صفحههای جدید در Compose، صرف نظر از اینکه از کدام نسخه طراحی متریال استفاده میکنید، اطمینان حاصل کنید که یک MaterialTheme
قبل از هر ترکیبی که UI از کتابخانههای Compose Material منتشر میکند، اعمال کنید. اجزای Material ( Button
، Text
، و غیره) به یک MaterialTheme
در محل بستگی دارند و رفتار آنها بدون آن تعریف نشده است.
همه نمونههای Jetpack Compose از یک تم Compose سفارشی استفاده میکنند که بر روی MaterialTheme
ساخته شده است.
برای کسب اطلاعات بیشتر، طراحی سیستمها را در Compose and Migring themes XML to Compose ببینید.
ناوبری
اگر از مؤلفه ناوبری در برنامه خود استفاده می کنید، برای اطلاعات بیشتر به «پیمایش با نوشتن - قابلیت همکاری و انتقال ناوبری Jetpack به نگارش ناوبری» مراجعه کنید.
رابط کاربری ترکیبی Compose/Views خود را آزمایش کنید
پس از انتقال بخشهایی از برنامهتان به Compose، آزمایش برای اطمینان از اینکه چیزی را خراب نکردهاید بسیار مهم است.
وقتی یک فعالیت یا قطعه از Compose استفاده می کند، باید به جای استفاده از ActivityScenarioRule
از createAndroidComposeRule
استفاده کنید. createAndroidComposeRule
ActivityScenarioRule
با ComposeTestRule
ادغام می کند که به شما امکان می دهد کد Compose و View را همزمان تست کنید.
class MyActivityTest { @Rule @JvmField val composeTestRule = createAndroidComposeRule<MyActivity>() @Test fun testGreeting() { val greeting = InstrumentationRegistry.getInstrumentation() .targetContext.resources.getString(R.string.greeting) composeTestRule.onNodeWithText(greeting).assertIsDisplayed() } }
برای کسب اطلاعات بیشتر در مورد آزمایش، به آزمایش طرحبندی نوشتن خود مراجعه کنید. برای همکاری با چارچوبهای آزمایش UI، قابلیت همکاری با Espresso و قابلیت همکاری با UiAutomator را ببینید.
ادغام Compose با معماری برنامه موجود شما
الگوهای معماری جریان داده های یک جهته (UDF) با Compose یکپارچه کار می کنند. اگر برنامه به جای آن از انواع دیگری از الگوهای معماری استفاده میکند، مانند Model View Presenter (MVP)، توصیه میکنیم آن قسمت از UI را قبل یا در حین پذیرش Compose به UDF منتقل کنید.
استفاده از ViewModel
در Compose
اگر از کتابخانه ViewModel
کامپوننتهای معماری استفاده میکنید، میتوانید با فراخوانی تابع viewModel()
ViewModel
از هر composable دسترسی داشته باشید، همانطور که در Compose و کتابخانههای دیگر توضیح داده شده است.
هنگام استفاده از Compose، مراقب استفاده از نوع ViewModel
یکسان در Composable های مختلف باشید زیرا عناصر ViewModel
از محدوده View-lifecycle پیروی می کنند. اگر از کتابخانه ناوبری استفاده شود، دامنه یا فعالیت میزبان، قطعه، یا نمودار ناوبری خواهد بود.
به عنوان مثال، اگر composable ها در یک اکتیویتی میزبانی شوند، viewModel()
همیشه همان نمونه ای را برمی گرداند که تنها پس از پایان فعالیت پاک می شود. در مثال زیر، همان کاربر ("user1") دو بار مورد استقبال قرار می گیرد زیرا همان نمونه GreetingViewModel
در همه composable ها تحت فعالیت میزبان مجددا استفاده می شود. اولین نمونه ViewModel
ایجاد شده در دیگر composable ها مجددا استفاده می شود.
class GreetingActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Column { GreetingScreen("user1") GreetingScreen("user2") } } } } } @Composable fun GreetingScreen( userId: String, viewModel: GreetingViewModel = viewModel( factory = GreetingViewModelFactory(userId) ) ) { val messageUser by viewModel.message.observeAsState("") Text(messageUser) } class GreetingViewModel(private val userId: String) : ViewModel() { private val _message = MutableLiveData("Hi $userId") val message: LiveData<String> = _message } class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return GreetingViewModel(userId) as T } }
از آنجایی که نمودارهای ناوبری شامل عناصر ViewModel
نیز میشوند، کامپوزیشنهایی که مقصدی در یک نمودار ناوبری هستند، نمونههای متفاوتی از ViewModel
دارند. در این حالت، ViewModel
به چرخه حیات مقصد اختصاص داده میشود و زمانی که مقصد از پشت پشتی حذف میشود، پاک میشود. در مثال زیر، زمانی که کاربر به صفحه پروفایل میرود، نمونه جدیدی از GreetingViewModel
ایجاد میشود.
@Composable fun MyApp() { NavHost(rememberNavController(), startDestination = "profile/{userId}") { /* ... */ composable("profile/{userId}") { backStackEntry -> GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "") } } }
منبع حقیقت
وقتی Compose را در بخشی از UI استفاده می کنید، ممکن است Compose و کد سیستم View نیاز به اشتراک گذاری داده ها داشته باشند. در صورت امکان، توصیه می کنیم حالت مشترک را در کلاس دیگری که از بهترین شیوه های UDF استفاده شده توسط هر دو پلتفرم پیروی می کند، کپسوله کنید. به عنوان مثال، در یک ViewModel
که جریانی از داده های مشترک را برای انتشار به روز رسانی داده ها در معرض دید قرار می دهد.
با این حال، اگر دادههایی که قرار است به اشتراک گذاشته شوند قابل تغییر باشند یا به شدت به یک عنصر UI متصل شده باشند، همیشه ممکن نیست. در این صورت، یک سیستم باید منبع حقیقت باشد و آن سیستم باید هرگونه به روز رسانی داده را با سیستم دیگر به اشتراک بگذارد. به عنوان یک قاعده کلی، منبع حقیقت باید متعلق به هر عنصری باشد که به ریشه سلسله مراتب UI نزدیکتر است.
تالیف به عنوان منبع حقیقت
از SideEffect
composable برای انتشار حالت Compose به کد غیر Compose استفاده کنید. در این مورد، منبع حقیقت در یک composable نگهداری می شود که به روز رسانی های حالت را ارسال می کند.
به عنوان مثال، کتابخانه تجزیه و تحلیل شما ممکن است به شما اجازه دهد با پیوست کردن ابرداده های سفارشی ( ویژگی های کاربر در این مثال) به همه رویدادهای تجزیه و تحلیل بعدی، جمعیت کاربر خود را بخش بندی کنید. برای ارتباط نوع کاربری کاربر فعلی با کتابخانه تجزیه و تحلیل خود، از SideEffect
برای به روز رسانی مقدار آن استفاده کنید.
@Composable fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { FirebaseAnalytics() } // On every successful composition, update FirebaseAnalytics with // the userType from the current User, ensuring that future analytics // events have this metadata attached SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics }
برای اطلاعات بیشتر، به عوارض جانبی در نوشتن مراجعه کنید.
به سیستم به عنوان منبع حقیقت نگاه کنید
اگر سیستم View مالک حالت است و آن را با Compose به اشتراک میگذارد، توصیه میکنیم که حالت را در اشیاء mutableStateOf
بپیچید تا آن را به صورت رشتهای برای Compose ایمن کنید. اگر از این روش استفاده می کنید، توابع ترکیب پذیر ساده می شوند زیرا دیگر منبع حقیقت را ندارند، اما سیستم View باید حالت تغییرپذیر و View هایی را که از آن حالت استفاده می کنند به روز کند.
در مثال زیر، یک CustomViewGroup
شامل یک TextView
و یک ComposeView
با یک TextField
قابل ترکیب در داخل است. TextView
باید محتوای آنچه کاربر در TextField
تایپ می کند را نشان دهد.
class CustomViewGroup @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : LinearLayout(context, attrs, defStyle) { // Source of truth in the View system as mutableStateOf // to make it thread-safe for Compose private var text by mutableStateOf("") private val textView: TextView init { orientation = VERTICAL textView = TextView(context) val composeView = ComposeView(context).apply { setContent { MaterialTheme { TextField(value = text, onValueChange = { updateState(it) }) } } } addView(textView) addView(composeView) } // Update both the source of truth and the TextView private fun updateState(newValue: String) { text = newValue textView.text = newValue } }
در حال انتقال رابط کاربری مشترک
اگر به تدریج به Compose مهاجرت می کنید، ممکن است لازم باشد از عناصر رابط کاربری مشترک در سیستم Compose و View استفاده کنید. برای مثال، اگر برنامه شما یک جزء CallToActionButton
سفارشی دارد، ممکن است لازم باشد از آن در هر دو صفحه Compose و View-based استفاده کنید.
در Compose، عناصر UI به اشتراکگذاشتهشده به قابلیتهایی تبدیل میشوند که میتوانند مجدداً در سراسر برنامه استفاده شوند، صرف نظر از اینکه عنصر با استفاده از XML یا نمای سفارشی استایلبندی شده باشد. برای مثال، میتوانید یک CallToActionButton
ایجاد کنید که میتواند برای مؤلفه Button
تماس به اقدام سفارشی خود ایجاد کند.
برای استفاده از composable در صفحه نمایش مبتنی بر View، یک نمای بسته بندی سفارشی ایجاد کنید که از AbstractComposeView
گسترش می یابد. همانطور که در مثال زیر نشان داده شده است، در Content
composable لغو شده آن، قابل ترکیبی را که ایجاد کردید در قالب Compose خود قرار دهید:
@Composable fun CallToActionButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, ) { Button( colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.secondary ), onClick = onClick, modifier = modifier, ) { Text(text) } } class CallToActionViewButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AbstractComposeView(context, attrs, defStyle) { var text by mutableStateOf("") var onClick by mutableStateOf({}) @Composable override fun Content() { YourAppTheme { CallToActionButton(text, onClick) } } }
توجه داشته باشید که پارامترهای composable تبدیل به متغیرهای قابل تغییر در نمای سفارشی می شوند. این باعث می شود که نمای CallToActionViewButton
سفارشی مانند یک نمای سنتی قابل تورم و قابل استفاده باشد. نمونه ای از این را با View Binding در زیر ببینید:
class ViewBindingActivity : ComponentActivity() { private lateinit var binding: ActivityExampleBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityExampleBinding.inflate(layoutInflater) setContentView(binding.root) binding.callToAction.apply { text = getString(R.string.greeting) onClick = { /* Do something */ } } } }
اگر مؤلفه سفارشی حاوی حالت تغییرپذیر است، به State source of true مراجعه کنید.
اولویت بندی حالت تفکیک از ارائه
به طور سنتی، یک View
حالتی است. یک View
فیلدهایی را مدیریت می کند که علاوه بر نحوه نمایش آن، چه چیزی را نشان می دهد. هنگامی که یک View
به Compose تبدیل میکنید، به دنبال جداسازی دادههای ارائهشده برای دستیابی به یک جریان داده یک طرفه باشید، همانطور که در حالت hoisting توضیح داده شده است.
به عنوان مثال، یک View
دارای ویژگی visibility
است که نشان می دهد قابل مشاهده، نامرئی یا از بین رفته است. این ویژگی ذاتی View
است. در حالی که سایر قطعات کد ممکن است نمایان بودن یک View
را تغییر دهند، فقط خود View
واقعاً می داند که نمای فعلی آن چقدر است. منطق حصول اطمینان از قابل مشاهده بودن یک View
می تواند مستعد خطا باشد و اغلب به خود View
گره خورده است.
در مقابل، Compose با استفاده از منطق شرطی در Kotlin، نمایش اجزای کاملاً متفاوت را آسان میکند:
@Composable fun MyComposable(showCautionIcon: Boolean) { if (showCautionIcon) { CautionIcon(/* ... */) } }
با طراحی، CautionIcon
نیازی به دانستن یا اهمیتی ندارد که چرا نمایش داده می شود، و هیچ مفهومی از visibility
وجود ندارد: یا در ترکیب قرار دارد یا نیست.
با جداسازی دقیق مدیریت حالت و منطق ارائه، میتوانید آزادانهتر نحوه نمایش محتوا را به عنوان تبدیل حالت به UI تغییر دهید. توانایی بالا بردن حالت در صورت نیاز نیز باعث می شود که مواد ترکیبی قابل استفاده مجدد باشند، زیرا مالکیت دولتی انعطاف پذیرتر است.
اجزای محصور شده و قابل استفاده مجدد را تبلیغ کنید
عناصر View
اغلب تصوری از محل زندگی خود دارند: داخل یک Activity
، یک Dialog
، یک Fragment
یا جایی در داخل سلسله مراتب View
دیگر. از آنجا که آنها اغلب از فایل های طرح بندی ایستا پر می شوند، ساختار کلی یک View
تمایل دارد بسیار سفت و سخت باشد. این منجر به اتصال محکمتر میشود و تغییر یا استفاده مجدد یک View
را دشوارتر میکند.
به عنوان مثال، یک View
سفارشی ممکن است فرض کند که یک نمای فرزند از نوع خاصی با یک شناسه خاص دارد و خصوصیات آن را مستقیماً در پاسخ به برخی اقدامات تغییر دهد. این عناصر View
کاملاً با هم مرتبط میکند: اگر View
سفارشی نتواند فرزند را پیدا کند، ممکن است خراب شود یا خراب شود، و احتمالاً کودک نمیتواند بدون والد View
سفارشی دوباره استفاده شود.
این مشکل در Compose با قابلیت های قابل استفاده مجدد کمتر است. والدین میتوانند به راحتی وضعیت و تماسها را مشخص کنند، بنابراین میتوانید مواد قابل استفاده مجدد را بدون نیاز به دانستن مکان دقیق استفاده از آنها بنویسید.
@Composable fun AScreen() { var isEnabled by rememberSaveable { mutableStateOf(false) } Column { ImageWithEnabledOverlay(isEnabled) ControlPanelWithToggle( isEnabled = isEnabled, onEnabledChanged = { isEnabled = it } ) } }
در مثال بالا، هر سه قسمت بیشتر کپسوله شده و کمتر جفت شده اند:
ImageWithEnabledOverlay
فقط باید بداند که وضعیت فعلیisEnabled
چیست. نیازی به دانستن وجودControlPanelWithToggle
یا حتی نحوه کنترل آن نیست.ControlPanelWithToggle
نمی داند کهImageWithEnabledOverlay
وجود دارد. ممکن است صفر، یک یا چند راه وجود داشته باشد کهisEnabled
نمایش داده شود، وControlPanelWithToggle
نیازی به تغییر ندارد.برای والدین، اهمیتی ندارد که
ImageWithEnabledOverlay
یاControlPanelWithToggle
تا چه حد عمیق تو در تو باشد. این کودکان می توانند تغییرات را متحرک کنند، محتوا را مبادله کنند یا محتوا را به کودکان دیگر منتقل کنند.
این الگو به عنوان وارونگی کنترل شناخته می شود که می توانید اطلاعات بیشتری در مورد آن در مستندات CompositionLocal
بخوانید.
مدیریت تغییر اندازه صفحه نمایش
داشتن منابع مختلف برای اندازههای مختلف پنجره یکی از راههای اصلی ایجاد طرحبندی View
Responsive است. در حالی که منابع واجد شرایط هنوز گزینه ای برای تصمیم گیری در مورد طرح بندی در سطح صفحه نمایش هستند، Compose تغییر طرح بندی ها را به طور کامل در کد با منطق شرطی معمولی بسیار آسان تر می کند. برای کسب اطلاعات بیشتر به استفاده از کلاس های اندازه پنجره مراجعه کنید.
علاوه بر این، برای آشنایی با تکنیکهایی که Compose برای ایجاد رابطهای کاربری تطبیقی ارائه میدهد، به پشتیبانی از اندازههای مختلف صفحه مراجعه کنید.
پیمایش تو در تو با Views
برای اطلاعات بیشتر در مورد نحوه فعال کردن interop پیمایش تودرتو بین عناصر View قابل پیمایش و composableهای قابل پیمایش، تودرتو در هر دو جهت، از طریق Nested scrolling interop را بخوانید.
نوشتن در RecyclerView
Composable ها در RecyclerView
از RecyclerView
نسخه 1.3.0-alpha02 کارایی دارند. مطمئن شوید که حداقل از نسخه 1.3.0-alpha02 RecyclerView
استفاده می کنید تا این مزایا را ببینید.
WindowInsets
با Views تداخل دارد
هنگامی که صفحه نمایش شما دارای هر دو کد Views و Compose در یک سلسله مراتب است، ممکن است لازم باشد که ورودی های پیش فرض را لغو کنید. در این مورد، شما باید به صراحت بگویید که در کدام یک از اینست ها باید مصرف کرد و کدام یک باید آنها را نادیده گرفت.
به عنوان مثال، اگر بیرونیترین طرحبندی شما یک طرحبندی Android View است، باید ورودیهای موجود در سیستم View را مصرف کنید و آنها را برای Compose نادیده بگیرید. از طرف دیگر، اگر بیرونیترین چیدمان شما قابل ترکیب است، باید ورودیها را در Compose مصرف کنید و بر اساس آن، Composableهای AndroidView
را پاک کنید.
به طور پیشفرض، هر ComposeView
تمام ورودیها را در سطح مصرف WindowInsetsCompat
مصرف میکند. برای تغییر این رفتار پیشفرض، ComposeView.consumeWindowInsets
روی false
تنظیم کنید.
برای اطلاعات بیشتر، مستندات WindowInsets
در Compose را بخوانید.
برای شما توصیه می شود
- توجه: وقتی جاوا اسکریپت خاموش است، متن پیوند نمایش داده می شود
- نمایش ایموجی
- Material Design 2 در Compose
- ورودی های پنجره در Compose