כדי לשפר את ביצועי הקומפוזיציה של רכיבים אינטראקטיביים שמשתמשים ב-Modifier.clickable, הוספנו ממשקי API חדשים. ממשקי ה-API האלה מאפשרים הטמעות יעילות יותר של Indication, כמו אפקט אדווה.
androidx.compose.foundation:foundation:1.7.0+ ו-androidx.compose.material:material-ripple:1.7.0+ כוללים את השינויים הבאים ב-API:
הוצא משימוש |
החלפה |
|---|---|
|
|
|
במקום זאת, ממשקי API חדשים של הערה: בהקשר הזה, "ספריות Material" מתייחסות ל- |
|
שתי האפשרויות הן:
|
בדף הזה מתוארת ההשפעה של השינוי בהתנהגות, ומופיעות הוראות למעבר לממשקי ה-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
אם אתם משתמשים בספריית Material, החליפו ישירות את rememberRipple() בקריאה ל-ripple() מהספרייה המתאימה. ה-API הזה יוצר אפקט אדווה באמצעות ערכים שנגזרים מממשקי ה-API של התאמת העיצוב של Google Material Design. לאחר מכן, מעבירים את האובייקט שהוחזר אל 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 שהוצאו משימוש:
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 כדי להגדיר את ההתנהגות של האפקט ברמת העיצוב. זו הייתה בעצם נקודת שילוב בין הרכיבים המקומיים של מערכת העיצוב בהתאמה אישית לבין האפקט. במקום לחשוף פרימיטיב כללי של עיצוב, material-ripple חושף עכשיו פונקציה של createRippleModifierNode(). הפונקציה הזו מאפשרת לספריות של מערכת העיצוב ליצור הטמעה של wrapper בסדר גבוה יותר, ששולחת שאילתות לגבי ערכי העיצוב שלהן ואז מקצה את הטמעת האפקט לצומת שנוצר על ידי הפונקציה הזו.
כך מערכות העיצוב יכולות לשלוח שאילתות ישירות לגבי מה שהן צריכות, ולחשוף את כל שכבות העיצוב הנדרשות שניתנות להגדרה על ידי המשתמש, בלי צורך להתאים את עצמן למה שמופיע בשכבת material-ripple. השינוי הזה גם מבהיר לאיזה עיצוב או מפרט האפקט תואם, כי ה-API של האפקט עצמו מגדיר את החוזה הזה, במקום שהוא ייגזר באופן משתמע מהעיצוב.
לקבלת הדרכה, עיינו בהטמעה של Ripple API בספריות Material, והחליפו את הקריאות ל-CompositionLocal של Material לפי הצורך במערכת העיצוב שלכם.
העברה מ-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. מכיוון שלוגיקת האיסוף הועברה לצומת, זהו אובייקט פשוט מאוד של מפעל, שהאחריות היחידה שלו היא ליצור מופע של צומת.לדוגמה, קטע הקוד הבא משתמש בממשקי ה-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.