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