רשימות עם 'כתיבה' ל-Wear OS


רשימות מאפשרות למשתמשים לבחור פריט מתוך קבוצת אפשרויות במכשירי WearOS.

בהרבה מכשירי Wear OS יש מסכים עגולים, ולכן קשה יותר לראות פריטים ברשימה שמופיעים בחלק העליון והתחתון של המסך. לכן, ב-Compose for Wear OS יש גרסה של המחלקה LazyColumn שנקראת TransformingLazyColumn, שתומכת בהנפשות של שינוי גודל ושינוי צורה. כשהפריטים מגיעים לקצוות, הם קטנים יותר ודהויים.

כדי להחיל את אפקטי ההזזה והשינוי המומלצים:

  1. משתמשים ב-Modifier.transformedHeight כדי לאפשר ל-Compose לחשב את שינוי הגובה בזמן שהפריט עובר במסך.
  2. משתמשים ב-transformation = SurfaceTransformation(transformationSpec) כדי להחיל את האפקטים הוויזואליים, כולל הקטנת התוכן של הפריט.
  3. משתמשים בTransformationSpec מותאם אישית לרכיבים שלא מקבלים את transformation כפרמטר, כמו Text.

באנימציה הבאה אפשר לראות איך רכיב ברשימה משנה את הגודל והצורה שלו כשהוא מתקרב לחלק העליון והתחתון של המסך:

בקטע הקוד הבא אפשר לראות איך ליצור רשימה באמצעות פריסת TransformingLazyColumn כדי ליצור תוכן שנראה מצוין במגוון גדלים של מסכי Wear OS.

בקטע הקוד מוצג גם השימוש בשינוי minimumVerticalContentPadding, שצריך להגדיר אותו בפריטים ברשימה כדי להחיל את הריווח הנכון בחלק העליון והתחתון של הרשימה.

כדי להציג את אינדיקטור הגלילה, משתפים את columnState בין ScreenScaffold לבין TransformingLazyColumn:

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(
    scrollState = columnState
) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding
    ) {
        item {
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text(text = "Header")
            }
        }
        // ... other items
        item {
            Button(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec),
                onClick = { /* ... */ },
                icon = {
                    Icon(
                        imageVector = Icons.Default.Build,
                        contentDescription = "build",
                    )
                },
            ) {
                Text(
                    text = "Build",
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                )
            }
        }
    }
}

הוספת אפקט של הצמדה והטלה

ההצמדה מבטיחה שכאשר משתמש מסיים גלילה או תנועת החלקה, הרשימה תתייצב עם פריט שממוקם בדיוק בנקודה ספציפית, בדרך כלל במרכז המסך. במסכים עגולים, שבהם הפריטים משנים את הגודל והצורה שלהם כשהם מתרחקים מהמרכז, הצמדה שימושית במיוחד כדי לוודא שהפריט הרלוונטי ביותר יישאר גלוי וקריא באופן מלא באזור הצפייה האופטימלי.

כדי להוסיף התנהגות של הצמדה והטלה, מגדירים את הפרמטר flingBehavior לערך TransformingLazyColumnDefaults.snapFlingBehavior(columnState). מגדירים את rotaryScrollableBehavior כך שיתאים, באמצעות RotaryScrollableDefaults.snapBehavior(columnState), לחוויית שימוש עקבית כשמשתמשים בכתר או במסגרת הפיזיים.

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        flingBehavior = TransformingLazyColumnDefaults.snapFlingBehavior(columnState),
        rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(columnState)
    ) {
        // ...
        // ...
    }
}

היפוך הפריסה

כברירת מחדל, רשימה שאפשר לגלול בה מעוגנת לקצה העליון שלה. אם משתמש גלל לתחתית של רשימה רגילה ופריט חדש נוסף לסוף הרשימה, התצוגה של המשתמש נשארת על הפריט הנוכחי. לדוגמה, אם המשתמש צופה בפריט 10 בתחתית המסך, ופריט 11 מתווסף, התצוגה נשארת ממוקדת בפריט 10, ופריט 11 מופיע מחוץ למסך מתחת לתצוגה הנוכחית.

במקרים כמו אפליקציות להעברת הודעות או יומנים בזמן אמת, בדרך כלל לא רוצים את ההתנהגות הזו. כשפריטים חדשים מגיעים, המשתמשים בדרך כלל רוצים לראות את התוכן האחרון באופן מיידי אם הם כבר הגיעו לתחתית הרשימה. אם הרבה פריטים מגיעים בבת אחת, הרשימה צריכה לדלג ולהציג את הפריט האחרון בתחתית (כלומר, יכול להיות שחלק מהפריטים שבאמצע לא יוצגו בכלל אלא אם המשתמש יגלול למעלה).

כדי לתמוך בתרחישי השימוש האלה, TransformingLazyColumn מאפשרת לשנות את פריסת הרכיבים על ידי הגדרת reverseLayout = true. השינוי הזה משנה את העיגון של הרשימה מקצה עליון לקצה תחתון.

כדי שיהיה לכם נוח, ההגדרה reverseLayout = true גם הופכת את הסדר החזותי של הפריטים ואת הכיוון של תנועות הגלילה:

  • הפריטים מורכבים מלמטה למעלה, כלומר אינדקס 0 מופיע בתחתית המסך.
  • אם גוללים למעלה, רואים פריטים עם אינדקסים גבוהים יותר.

כדי להוסיף התנהגות של הצמדה והזזה יחד עם פריסה הפוכה, אפשר לשלב את flingBehavior ו-rotaryScrollableBehavior כמו בקטע הקוד הבא:

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding,
        reverseLayout = true,
        modifier = Modifier.fillMaxWidth()
    ) {
        items(10) { index ->
            Button(
                label = {
                    Text(
                        text = "Item ${index + 1}"
                    )
                },
                onClick = {},
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            )
        }
        item {
            // With reverseLayout = true, the last item declared appears at the top.
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text("Header")
            }
        }
    }
}

בתמונות הבאות אפשר לראות את ההבדל בין רשימה רגילה לרשימה הפוכה:

‫TransformingLazyColumn עם פריסה רגילה, שבו מוצג Item 1 בחלק העליון והפריטים מוצגים בסדר עולה.
איור 1. פריסת רשימה רגילה שבה התוכן ממלא את המסך מלמעלה למטה.
רכיב TransformingLazyColumn עם פריסה הפוכה, שבו הפריט Item 1 נמצא בתחתית והפריטים מסודרים בסדר יורד כלפי החלק העליון.
איור 2. פריסת רשימה הפוכה שבה התוכן מתמלא מלמטה למעלה.

כפתורי קצה ברשימות

ב-Material 3, אפשר להוסיף EdgeButton, שהוא לחצן שצמוד לקצה התחתון של הרשימות. עם זאת, חשוב להקפיד לא להוסיף את זה כפריט ב-TransformingLazyColumn, אלא להשתמש במשבצת edgeButton ב-ScreenScaffold.

שימוש במשבצת edgeButton מבטיח שהלחצן ימוקם בצורה נכונה בתחתית המסך ויתנהג בצורה מתאימה כשגוללים ברשימה.

בקטע הקוד הבא אפשר לראות איך מטמיעים את EdgeButton:

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(
    scrollState = columnState,
    edgeButton = {
        EdgeButton(
            onClick = { /* TODO */ },
            modifier = Modifier.scrollable(
                columnState,
                orientation = Orientation.Vertical,
                reverseDirection = true,
                // Apply overscroll to the EdgeButton for proper scrolling behavior.
                overscrollEffect = rememberOverscrollEffect(),
            )
        ) {
            Text("More")
        }
    }
) { contentPadding ->
    TransformingLazyColumn(
        contentPadding = contentPadding,
        state = columnState,
    ) {
        // ...
        // ...
    }
}

החלקה לחשיפה ברשימות

רכיב SwipeToReveal מאפשר לכם לגשת לפעולות של פריט ברשימה, כמו Card או Chip, באמצעות החלקה. בדרך כלל, בהחלקה לצדדים מופיעים לחצן או שני לחצנים של פעולות (כמו 'מחיקה' או 'עוד').

כשמשתמשים ב-SwipeToReveal בתוך TransformingLazyColumn, חשוב לפעול לפי ההנחיות הבאות:

  • איפוס בגלילה: כשמשתמש גולל ברשימה, כל הפריטים שנפתחו בהחלקה יאופסו למצב המכוסה שלהם.
  • גבהים עקביים: כדי לשמור על מראה עקבי, צריך להגדיר את הגובה של לחצני הפעולה כך שיתאים לגובה של הפריט הפנימי שמופיע אחרי ההחלקה (בין אם מדובר בButton או בCard).
  • שינוי מאגר התגים: מחילים את המשנה transformedHeight ואת transformationSpec על הרכיב SwipeToReveal עצמו.
  • אל תבצעו טרנספורמציה כפולה: אל תשתמשו במגדירי transformedHeight או transformation בפריט הפנימי שניתן להחלקה (הכרטיס או הלחצן בתוך מאגר SwipeToReveal).