כדי לשפר את ביצועי הקומפוזיציה של רכיבים אינטראקטיביים שמשתמשים ב-Modifier.clickable, הוספנו ממשקי API חדשים. ממשקי ה-API האלה מאפשרים הטמעות יעילות יותר של Indication, כמו ripples.
androidx.compose.foundation:foundation:1.7.0+ ו-androidx.compose.material:material-ripple:1.7.0+ כוללים את השינויים הבאים ב-API:
הוצא משימוש |
החלפה |
|---|---|
|
|
|
במקום זאת, ממשקי API חדשים של הערה: בהקשר הזה, 'ספריות חומרים' מתייחסות אל |
|
למשל:
|
בדף הזה מתואר השינוי בהתנהגות, ומופיעות הוראות להעברה לממשקי ה-API החדשים.
שינוי בהתנהגות
גרסאות הספריות הבאות כוללות שינוי בהתנהגות של אפקט הגל:
androidx.compose.material:material:1.7.0+androidx.compose.material3:material3:1.3.0+androidx.wear.compose:compose-material:1.4.0+
בגרסאות האלה של ספריות Material כבר לא נעשה שימוש ב-rememberRipple(), אלא בממשקי ה-API החדשים של אפקט האדווה. כתוצאה מכך, הם לא שולחים שאילתות אל LocalRippleTheme.
לכן, אם תגדירו את LocalRippleTheme באפליקציה, רכיבי Material לא ישתמשו בערכים האלה.
בקטעים הבאים מוסבר איך לבצע מיגרציה לממשקי ה-API החדשים.
העברה מ-rememberRipple אל ripple
שימוש בספריית חומרים
אם אתם משתמשים בספריית Material, מחליפים את rememberRipple() ישירות בקריאה ל-ripple() מהספרייה המתאימה. ה-API הזה יוצר אפקט אדווה באמצעות ערכים שנגזרים מממשקי ה-API של ערכת העיצוב Material. לאחר מכן, מעבירים את האובייקט שמוחזר אל Modifier.clickable או אל רכיבים אחרים.
לדוגמה, בקטע הקוד הבא נעשה שימוש בממשקי API שיצאו משימוש:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
צריך לשנות את קטע הקוד שלמעלה כך שייראה כך:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
שימו לב: ripple() היא כבר לא פונקציה שאפשר להרכיב, ואין צורך לזכור אותה. אפשר גם להשתמש בו מחדש בכמה רכיבים, בדומה לשימוש במאפייני שינוי. לכן, כדאי להעביר את יצירת האפקט של האדווה לערך ברמה העליונה כדי לחסוך בהקצאות.
הטמעה של מערכת עיצוב בהתאמה אישית
אם אתם מטמיעים מערכת עיצוב משלכם, ובעבר השתמשתם ב-rememberRipple() יחד עם RippleTheme מותאם אישית כדי להגדיר את האפקט של הגל, במקום זאת אתם צריכים לספק API משלכם לאפקט הגל, שמקצה ל-API של צומת הגל את הפונקציות שמוצגות ב-material-ripple. לאחר מכן, הרכיבים יכולים להשתמש באפקט האדווה שלכם, שמשתמש ישירות בערכי העיצוב. מידע נוסף זמין במאמר בנושא מעבר מ-RippleTheme.
העברה מ-RippleTheme
שימוש ב-RippleTheme כדי להשבית את אפקט הגל ברכיב מסוים
הספריות material ו-material3 חושפות את RippleConfiguration ו-LocalRippleConfiguration, שמאפשרות להגדיר את המראה של האדוות בתוך עץ משנה. שימו לב שהתכונות RippleConfiguration ו-LocalRippleConfiguration הן ניסיוניות ומיועדות להתאמה אישית של כל רכיב בנפרד. התאמה אישית גלובלית או התאמה אישית של כל הנושא לא נתמכות בממשקי ה-API האלה. מידע נוסף על תרחיש השימוש הזה זמין במאמר שימוש ב-RippleTheme כדי לשנות באופן גלובלי את כל האפקטים של הגלים באפליקציה.
לדוגמה, בקטע הקוד הבא נעשה שימוש בממשקי API שיצאו משימוש:
private object DisabledRippleTheme : RippleTheme { @Composable override fun defaultColor(): Color = Color.Transparent @Composable override fun rippleAlpha(): RippleAlpha = RippleAlpha(0f, 0f, 0f, 0f) } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleTheme) { Button { // ... } }
צריך לשנות את קטע הקוד שלמעלה כך שייראה כך:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
שימוש ב-RippleTheme כדי לשנות את הצבע או את ערך האלפא של אפקט האדווה ברכיב נתון
כפי שמתואר בסעיף הקודם, RippleConfiguration ו-LocalRippleConfiguration הם ממשקי API ניסיוניים שמיועדים רק להתאמה אישית של כל רכיב בנפרד.
לדוגמה, בקטע הקוד הבא נעשה שימוש בממשקי API שיצאו משימוש:
private object DisabledRippleThemeColorAndAlpha : RippleTheme { @Composable override fun defaultColor(): Color = Color.Red @Composable override fun rippleAlpha(): RippleAlpha = MyRippleAlpha } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) { Button { // ... } }
צריך לשנות את קטע הקוד שלמעלה כך שייראה כך:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
שימוש ב-RippleTheme כדי לשנות באופן גלובלי את כל האדוות באפליקציה
בעבר, יכולתם להשתמש ב-LocalRippleTheme כדי להגדיר את התנהגות האפקט ברמה של כל הנושא. הנקודה הזו הייתה למעשה נקודת שילוב בין קבצים מקומיים של מערכת עיצוב מותאמת אישית לבין Ripple. במקום להציג פרימיטיב כללי של עיצוב, material-ripple מציג עכשיו פונקציה של createRippleModifierNode(). הפונקציה הזו מאפשרת לספריות של מערכת העיצוב ליצור הטמעה של wrapper מסדר גבוה יותר, שמבצעת שאילתה על ערכי הנושא שלה ואז מעבירה את ההטמעה של אפקט האדווה לצומת שנוצר על ידי הפונקציה הזו.
כך מערכות עיצוב יכולות לשלוח שאילתות ישירות לגבי מה שהן צריכות, ולחשוף את כל שכבות העיצוב הנדרשות שניתנות להגדרה על ידי המשתמשים, בלי להתאים את עצמן למה שמופיע בשכבת material-ripple. בנוסף, השינוי הזה הופך את ההתאמה של האפקט לערכת העיצוב או למפרט לברורה יותר, כי ה-API של האפקט הוא זה שמגדיר את החוזה, ולא נגזר באופן מרומז מערכת העיצוב.
הנחיות אפשר למצוא בהטמעה של ripple API בספריות Material. צריך להחליף את הקריאות ל-Material composition locals לפי הצורך במערכת העיצוב שלכם.
העברה מ-Indication אל IndicationNodeFactory
עוברים ליד Indication
אם אתם רק יוצרים Indication כדי להעביר אותו, למשל כדי ליצור אפקט של גל שמעבירים ל-Modifier.clickable או ל-Modifier.indication, אתם לא צריכים לבצע שינויים. IndicationNodeFactory עובר בירושה מ-Indication,
כך שהכול ימשיך להתקמפל ולפעול.
יצירת Indication
אם אתם יוצרים הטמעה משלכם של Indication, ברוב המקרים ההעברה אמורה להיות פשוטה. לדוגמה, נניח שיש Indication שמחיל אפקט של שינוי גודל בלחיצה:
object ScaleIndication : Indication { @Composable override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { // key the remember against interactionSource, so if it changes we create a new instance val instance = remember(interactionSource) { ScaleIndicationInstance() } LaunchedEffect(interactionSource) { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition) is PressInteraction.Release -> instance.animateToResting() is PressInteraction.Cancel -> instance.animateToResting() } } } return instance } } private class ScaleIndicationInstance : IndicationInstance { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun ContentDrawScope.drawIndication() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@drawIndication.drawContent() } } }
אפשר להעביר את זה בשני שלבים:
העברה של
ScaleIndicationInstanceכך שיהיהDrawModifierNode. ממשק ה-API שלDrawModifierNodeדומה מאוד לזה שלIndicationInstance: הוא חושף פונקציהContentDrawScope#draw()ששווה מבחינת הפונקציונליות לפונקציהIndicationInstance#drawContent(). צריך לשנות את הפונקציה הזו, ואז להטמיע את הלוגיקה שלcollectLatestישירות בתוך הצומת, במקום ב-Indication.לדוגמה, בקטע הקוד הבא נעשה שימוש בממשקי API שיצאו משימוש:
private class ScaleIndicationInstance : IndicationInstance { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun ContentDrawScope.drawIndication() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@drawIndication.drawContent() } } }
צריך לשנות את קטע הקוד שלמעלה כך שייראה כך:
private class ScaleIndicationNode( private val interactionSource: InteractionSource ) : Modifier.Node(), DrawModifierNode { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) private suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } private suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun onAttach() { coroutineScope.launch { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> animateToPressed(interaction.pressPosition) is PressInteraction.Release -> animateToResting() is PressInteraction.Cancel -> animateToResting() } } } } override fun ContentDrawScope.draw() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@draw.drawContent() } } }
העברה של
ScaleIndicationכדי להטמיע אתIndicationNodeFactory. הלוגיקה של האוסף הועברה עכשיו לצומת, ולכן מדובר באובייקט פשוט מאוד של factory, שהאחריות היחידה שלו היא ליצור מופע של צומת.לדוגמה, בקטע הקוד הבא נעשה שימוש בממשקי API שיצאו משימוש:
object ScaleIndication : Indication { @Composable override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { // key the remember against interactionSource, so if it changes we create a new instance val instance = remember(interactionSource) { ScaleIndicationInstance() } LaunchedEffect(interactionSource) { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition) is PressInteraction.Release -> instance.animateToResting() is PressInteraction.Cancel -> instance.animateToResting() } } } return instance } }
צריך לשנות את קטע הקוד שלמעלה כך שייראה כך:
object ScaleIndicationNodeFactory : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleIndicationNode(interactionSource) } override fun hashCode(): Int = -1 override fun equals(other: Any?) = other === this }
שימוש ב-Indication ליצירת IndicationInstance
ברוב המקרים, כדאי להשתמש ב-Modifier.indication כדי להציג את Indication עבור רכיב. עם זאת, במקרים נדירים שבהם אתם יוצרים באופן ידני IndicationInstance באמצעות rememberUpdatedInstance, אתם צריכים לעדכן את ההטמעה כדי לבדוק אם Indication הוא IndicationNodeFactory, וכך להשתמש בהטמעה קלה יותר. לדוגמה, Modifier.indication יבצע הקצאה פנימית לצומת שנוצר אם הוא IndicationNodeFactory. אם לא, המערכת תשתמש ב-Modifier.composed כדי להתקשר אל rememberUpdatedInstance.