המעבר מ'תצוגות' ל'כתיבה' קשור רק לממשק המשתמש, אבל יש הרבה שצריך לקחת בחשבון כדי לבצע מיגרציה בטוחה ומצטברת. הזה מכילים שיקולים מסוימים, אפליקציה מבוססת-צפייה לכתיבה.
העברת העיצוב של האפליקציה
Material Design היא מערכת העיצוב המומלצת בשביל אפליקציות ל-Android.
לאפליקציות שמבוססות על צפייה יש שלוש גרסאות של Material:
- Material Design 1 באמצעות
ספריית AppCompat (כלומר
Theme.AppCompat.*
) - Material Design 2 באמצעות
MDC-Android
ספרייה (כלומר
Theme.MaterialComponents.*
) - Material Design 3 באמצעות
MDC-Android
ספרייה (כלומר
Theme.Material3.*
)
לאפליקציות פיתוח נייטיב יש שתי גרסאות של Material:
- Material Design 2 באמצעות
הספרייה יצירת חומרי לימוד
(לדוגמה:
androidx.compose.material.MaterialTheme
) - Material Design 3 באמצעות
הספרייה כתיבת חומר לימוד 3
(לדוגמה:
androidx.compose.material3.MaterialTheme
)
מומלץ להשתמש בגרסה העדכנית ביותר (Material 3) אם מערכת העיצוב של האפליקציה מסוגלת לעשות זאת. יש מדריכים להעברת נתונים לגבי שתי התצוגות וכתיבה:
כשיוצרים מסכים חדשים ב'כתיבה', ללא קשר לגרסת Material
העיצוב שבו בחרת להשתמש, חשוב להקפיד להחיל MaterialTheme
לפני
תכנים קומפוזביליים שפולטים ממשק משתמש מהספריות 'הוספת חומרים'. החומר
רכיבים (Button
, Text
וכו') תלויים בקיומו של MaterialTheme
וההתנהגות שלהם לא מוגדרת בלעדיו.
הכול
דוגמאות של Jetpack Compose
להשתמש בעיצוב מותאם אישית של 'כתיבה' שמבוסס על MaterialTheme
.
למידע נוסף, ראו עיצוב מערכות בכתיבה והעברת עיצובי XML לכתיבה.
ניווט
אם אתם משתמשים ברכיב הניווט באפליקציה: בתפריט ניווט באמצעות 'כתיבה' – יכולת פעולה הדדית לקבלת מידע נוסף, אפשר להעביר את הניווט ב-Jetpack ל'פיתוח נייטיב'.
בדיקה של ממשק המשתמש המעורב של הכתיבה/התצוגות
אחרי ההעברה של חלקים מהאפליקציה לכתיבה, חשוב מאוד לבצע את הבדיקה כדי לוודא לא שברתם שום דבר.
כאשר פעילות או מקטע משתמשים ב'כתיבה', עליך להשתמש ב-
createAndroidComposeRule
במקום להשתמש ב-ActivityScenarioRule
. createAndroidComposeRule
משולב
ActivityScenarioRule
עם ComposeTestRule
שמאפשר לבדוק את הכתיבה
הצגת הקוד בו-זמנית.
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() } }
למידע נוסף על בדיקות, אפשר לעיין במאמר בנושא בדיקת פריסת הכתיבה. עבור של יכולת פעולה הדדית עם frameworks של בדיקות ממשק משתמש, יכולת פעולה הדדית עם Espresso יכולת פעולה הדדית עם UiAutomator.
שילוב של פיתוח נייטיב עם ארכיטקטורת האפליקציה הקיימת
ארכיטקטורה של זרימת נתונים חד-כיוונית (UDF) דפוסים, עובדים בצורה חלקה עם 'פיתוח נייטיב'. אם האפליקציה משתמשת בסוגים אחרים של של ארכיטקטורת 'מציג מודלים' (MVP), להעביר את החלק הזה בממשק המשתמש ל-UDF לפני או תוך כדי המעבר לכתיבה.
שימוש ב-ViewModel
בכתיבה
אם אתם משתמשים ברכיבי הארכיטקטורה
ViewModel
, יש לך אפשרות לגשת
ViewModel
מכל תוכן קומפוזבילי מאת
קוראים לפונקציה
viewModel()
כפי שמוסבר בכתיבה וספריות אחרות.
כשמתחילים לכתוב, חשוב לשים לב לשימוש באותו סוג ViewModel
תכנים קומפוזביליים שונים כי רכיבי ViewModel
עוקבים אחרי היקפים של מחזור החיים בתצוגה מפורטת.
היקף ההרשאות הזה יהיה פעילות המארח, מקטע או תרשים ניווט אם
נעשה שימוש בספריית הניווט.
לדוגמה, אם התכנים הקומפוזביליים מתארחים בפעילות, viewModel()
תמיד
מחזירה את אותו מופע שמתבטל רק כשהפעילות מסתיימת.
בדוגמה הבאה, אותו משתמש ("user1") מקבל את פניו פעמיים כי
בכל התכנים הקומפוזביליים נעשה שימוש חוזר באותה מכונה של GreetingViewModel
פעילות מארח. במכונה הראשונה (ViewModel
) שנוצרה נעשה שימוש חוזר במכונה אחרת
של תכנים קומפוזביליים.
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
בהיקף של מחזור החיים של היעד, וגם
הוא נמחק כשהיעד מוסר מהמקבץ האחורי. ב
בדוגמה הבאה, כשהמשתמש מנווט למסך Profile,
המופע של GreetingViewModel
נוצר.
@Composable fun MyApp() { NavHost(rememberNavController(), startDestination = "profile/{userId}") { /* ... */ composable("profile/{userId}") { backStackEntry -> GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "") } } }
מצב מקור האמת
כאשר משתמשים בתכונה 'כתיבה' בחלק אחד של ממשק המשתמש, ייתכן שהאפשרות 'כתיבה' וגם
קוד המערכת של View צריך לשתף נתונים. כשהדבר אפשרי, מומלץ
כוללים את המצב המשותף במחלקה אחרת שפועלת לפי השיטות המומלצות של UDF
בשימוש בשתי הפלטפורמות; לדוגמה, ב-ViewModel
שחושף זרם של
נתונים משותפים כדי להפיץ עדכוני נתונים.
עם זאת, זה לא תמיד אפשרי אם הנתונים שישותפו ניתנים לשינוי או שמקושרת חזק לרכיב בממשק המשתמש. במקרה זה, מערכת אחת חייבת להיות המקור של האמת, והמערכת צריכה לשתף עדכוני נתונים עם המערכת השנייה. בתור ככלל, מקור האמת צריך להיות בבעלות של כל רכיב הוא קרוב יותר לרמה הבסיסית (root) של היררכיית ממשק המשתמש.
כתוב כמקור האמת
משתמשים ב
SideEffect
קומפוזבילי לפרסום מצב הכתיבה לקוד שאינו כתיבה. במקרה הזה, הפרמטר
מקור האמת נשמר בתוכן קומפוזבילי, ששולח עדכוני מצב.
לדוגמה, ספריית ניתוח הנתונים עשויה לאפשר לכם לפלח את המשתמשים
אוכלוסייה על ידי צירוף מטא נתונים מותאמים אישית (מאפייני משתמש בדוגמה הזו)
לכל האירועים הבאים של ניתוח נתונים. כדי לציין את סוג המשתמש של
המשתמש הנוכחי בספריית ניתוח הנתונים, צריך להשתמש בפרמטר 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 }
למידע נוסף, ראו תופעות לוואי ב'כתיבה'.
צפייה במערכת כמקור האמת
אם המערכת של התצוגה היא הבעלים של המצב והיא משתפת אותו עם 'כתיבה', מומלץ
צריך לתחום את המצב ב-mutableStateOf
אובייקטים כדי שהוא יהיה בטוח לשרשורים
פיתוח נייטיב. אם משתמשים בגישה הזו, פונקציות קומפוזביליות פשוטות יותר כי
כבר אין להם את מקור האמת, אבל מערכת 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 } }
מתבצעת העברה של ממשק המשתמש המשותף
אם אתם עוברים בהדרגה לכתיבה, יכול להיות שתצטרכו להשתמש בממשק משתמש משותף
רכיבים גם במערכת הכתיבה וגם במערכת התצוגה. לדוגמה, אם באפליקציה שלך יש
רכיב CallToActionButton
מותאם אישית, ייתכן שיהיה עליך להשתמש בו גם בכתיבה
ומסכים המבוססים על תצוגה.
בפיתוח של רכיבים משותפים בממשק המשתמש, הם הופכים לתוכן קומפוזבילי. אפשר לעשות בהם שימוש חוזר
ללא קשר לרכיב שהסגנון שלו מוצג באמצעות XML או שהוא תצוגה מותאמת אישית. עבור
לדוגמה, אפשר ליצור תוכן קומפוזבילי מסוג CallToActionButton
לקריאה המותאמת אישית של
רכיב Button
של הפעולה.
כדי להשתמש בתוכן הקומפוזבילי במסכים המבוססים על תצוגה, צריך ליצור wrapper של תצוגה מותאמת אישית
נמשך מ-AbstractComposeView
. בתוכן הקומפוזבילי Content
שהוחלף,
למקם את התוכן הקומפוזבילי שיצרתם עטוף בעיצוב של הכתיבה, כמו שמוצג
דוגמה:
@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) } } }
שימו לב שהפרמטרים הקומפוזביליים הופכים למשתנים שניתנים לשינוי
צפייה. כך תצוגת ה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 */ } } } }
אם הרכיב המותאם אישית מכיל מצב ניתן לשינוי, אפשר לעיין בקטע מצב מקור של האמת.
קביעת סדר עדיפויות לפיצול מצב מהמצגת
בדרך כלל, View
הוא מאפיין שמירת מצב. View
מנהל שדות ש
לתאר מה להציג, בנוסף לאופן להציג אותו. אחרי ש
להמיר View
לכתיבה, צריך להפריד את הנתונים שמעובדים
יצירת זרימת נתונים חד-כיוונית, כפי שמוסבר בפירוט בקטע העלאת מצב של מדינה.
לדוגמה, ל-View
יש מאפיין visibility
שמתאר אם הוא
גלוי, לא נראה או נעלם. זהו מאפיין מובנה של View
. בזמן
קטעי קוד אחרים עשויים לשנות את החשיפה של View
, אלא רק של View
עצמו יודע באמת מהי החשיפה הנוכחית שלו. הלוגיקה שצריך להבטיח
View
גלוי יכול להיות מועד לשגיאות, והוא קשור בדרך כלל לView
עצמו.
לעומת זאת, התכונה 'פיתוח נייטיב' מאפשרת להציג בקלות תכנים קומפוזביליים שונים לחלוטין באמצעות לוגיקה מותנית ב-Kotlin:
@Composable fun MyComposable(showCautionIcon: Boolean) { if (showCautionIcon) { CautionIcon(/* ... */) } }
בזכות העיצוב, CautionIcon
לא צריך לדעת למה הוא מוצג או שלא אכפת לו ממנו,
ואין קונספט של visibility
: או שזה ביצירה, או
היא לא.
הפרדה ברורה בין ניהול המצבים לבין לוגיקת המצגת, מאפשרת לכם לשנות באופן חופשי את האופן שבו אתם מציגים תוכן כהמרה של מצב לממשק משתמש. להיות היכולת להעלות את המצב במקרה הצורך הופכת תכנים קומפוזביליים לשימוש חוזר, בעלות על המדינה גמישה יותר.
קידום רכיבים מורכבים לשימוש חוזר
לרוב, לרכיבי View
יש מושג כלשהו איפה הם נמצאים: בתוך Activity
,
Dialog
, Fragment
או במקום כלשהו בתוך היררכיה אחרת של View
. כי
הם מתנפחים לעיתים קרובות מקובצי פריסה סטטית, המבנה הכולל של
View
הוא בדרך כלל נוקש מאוד. כך נוצר צימוד קטן יותר
כדי שיהיה קשה יותר לשנות View
או להשתמש בו שוב.
לדוגמה, View
בהתאמה אישית עשוי להניח שיש לו תצוגת צאצא של
להקליד עם מזהה מסוים, ולשנות את המאפיינים שלו ישירות בתגובה
פעולה. הוא מצמיד יחד את רכיבי View
האלה: View
בהתאמה אישית
עלולה לקרוס או לקרוס אם הילדה לא מצליחה למצוא את הילד, והילד, ככל הנראה לא יצליח
נעשה בו שימוש חוזר ללא ההורה View
בהתאמה אישית.
זו פחות בעיה בכתיבה עם תכנים קומפוזביליים לשימוש חוזר. ההורים יכולים להגדיר בקלות מצב וקריאות חוזרות (callback), כך שתוכלו לכתוב תכנים קומפוזביליים לשימוש חוזר בלי לדעת את המקום המדויק שבו יופיעו המודעות.
@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
רספונסיביות. מקורות מידע שעומדים בדרישות עדיין זמינים
להחלטות בנוגע לפריסה ברמת המסך, הרבה יותר קל לשנות בעזרת 'כתיבה'
לגמרי בקוד עם לוגיקה מותנית רגילה. למידע נוסף, ראו סיווגים של גודל חלון.
בנוסף, כדאי לעיין במאמר תמיכה בגודלי מסכים שונים ללמוד על הטכניקות שכותבים הצעות לפיתוח של ממשקי משתמש מותאמים.
גלילה בתוך עץ עם 'תצוגות'
למידע נוסף על האופן שבו מפעילים יכולת פעולה הדדית של גלילה מקוננת בין רכיבי תצוגה שניתנים לגלילה ותכנים קומפוזביליים שניתנים לגלילה, המוצבים בשני הכיוונים, הקראה יכולת פעולה הדדית של גלילה בתוך גלילה.
כתיבת הודעה בRecyclerView
תכנים קומפוזביליים ב-RecyclerView
יש ביצועים טובים החל מגרסה RecyclerView
1.3.0-alpha02. צריך לוודא שאתם משתמשים בגרסה 1.3.0-alpha02 לפחות של
RecyclerView
כדי לראות את ההטבות האלה.
יכולת פעולה הדדית של WindowInsets
עם תצוגות
ייתכן שיהיה עליך לעקוף רכיבי inset שמוגדרים כברירת מחדל כשבמסך יש גם את האפשרות 'תצוגות' וגם את כתיבת הקוד באותה היררכיה. במקרה הזה צריך לציין טקסט באופן מפורש איזה מהם צריך לצרוך את הרכיבים הפנימיים, ומאיזה מהם צריך להתעלם.
לדוגמה, אם הפריסה החיצונית ביותר שלך היא פריסת Android View, עליך
צורכים את הרכיבים הפנימיים במערכת התצוגה ומתעלמים מהם לצורך פיתוח נייטיב.
לחלופין, אם הפריסה החיצונית ביותר שלכם היא קומפוזבילית, צריך לצרוך את
inset ב-Compose (כתיבה), ומרפד את התכנים הקומפוזביליים של AndroidView
בהתאם.
כברירת מחדל, כל ComposeView
צורך את כל הרכיבים הפנימיים
רמת הצריכה של WindowInsetsCompat
. כדי לשנות את התנהגות ברירת המחדל, צריך להגדיר
ComposeView.consumeWindowInsets
אל false
.
לקבלת מידע נוסף, אפשר לעיין במסמכי התיעוד של WindowInsets
בקטע 'כתיבה'.
מומלץ עבורך
- הערה: טקסט הקישור מוצג כאשר JavaScript מושבת
- הצגת סמלי אמוג'י
- עיצוב Material 2 ב-Compose
- כניסות חלון לכתיבה