כדי לשפר את ביצועי הקומפוזיציה של רכיבים אינטראקטיביים שמשתמשים ב-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
.