גלילה

מקשי שינוי לגלילה

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

@Composable
private fun ScrollBoxes() {
    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .verticalScroll(rememberScrollState())
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

רשימה אנכית פשוטה שמגיבה לתנועות גלילה
איור 1. רשימה אנכית פשוטה שמגיבה לתנועות גלילה.

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

@Composable
private fun ScrollBoxesSmooth() {
    // Smoothly scroll 100px on first composition
    val state = rememberScrollState()
    LaunchedEffect(Unit) { state.animateScrollTo(100) }

    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .padding(horizontal = 8.dp)
            .verticalScroll(state)
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

התאמה של אזור שניתן לגלילה

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

בדרך כלל, עדיף להשתמש בפתרונות מוכנים מראש כמו verticalScroll, horizontalScroll או רכיבים שאפשר להרכיב כמו LazyColumn לרשימות גלילה רגילות, ולא ב-scrollableArea שמשמשת להטמעות בהתאמה אישית. הרכיבים ברמה הגבוהה יותר פשוטים יותר לתרחישי שימוש נפוצים, והם עצמם בנויים באמצעות scrollableArea.

ההבדל בין משני scrollableArea לבין משני scrollable

ההבדל העיקרי בין scrollableArea לבין scrollable הוא באופן שבו הם מפרשים את תנועות הגלילה של המשתמשים:

  • scrollable (raw delta): הדלתא משקפת ישירות את התנועה הפיזית של הקלט של המשתמש (למשל, גרירת מצביע) על המסך.
  • scrollableArea (דלתא מבוססת-תוכן): הערך delta הוא הפוך מבחינה סמנטית כדי לייצג את השינוי שנבחר במיקום הגלילה, כך שהתוכן ייראה כאילו הוא זז עם תנועת המשתמש, שבדרך כלל הפוכה מתנועת המצביע.

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

בטבלה הבאה מפורטים סימני הדלתא בתרחישים נפוצים:

תנועת משתמש

השינוי שדווח ל-dispatchRawDelta על ידי scrollable

השינוי שדווח ל-dispatchRawDelta על ידי scrollableArea*

הסמן זז למעלה

שלילי

חיובי

הסמן זז למטה

חיובי

שלילי

הסמן זז ימינה

שלילי

חיובי (שלילי ל-RTL)

הסמן זז ימינה

חיובי

שלילי (חיובי בשפות RTL)

‫(*) הערה לגבי scrollableArea סימן הדלתא: הסימן של הדלתא מ-scrollableArea הוא לא רק היפוך פשוט. הוא לוקח בחשבון באופן חכם את:

  1. כיוון: אנכי או אופקי.
  2. LayoutDirection: משמאל לימין או מימין לשמאל (חשוב במיוחד לגלילה אופקית).
  3. הדגל reverseScrolling: קובע אם כיוון הגלילה הפוך.

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

מתי כדאי להשתמש במאפיין scrollableArea

כדאי להשתמש במגדיר scrollableArea כשצריך ליצור רכיב גלילה בהתאמה אישית שלא מקבל מענה מספק מהמגדירים horizontalScroll או verticalScroll או מפריסות בטעינה עצלה. לרוב מדובר במקרים שבהם:

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

יצירת רשימות מותאמות אישית דמויות גלגל באמצעות scrollableArea

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

איור 2. רשימה אנכית מותאמת אישית באמצעות scrollableArea.

@Composable
private fun ScrollableAreaSample() {
    // ...
    Layout(
        modifier =
            Modifier
                .size(150.dp)
                .scrollableArea(scrollState, Orientation.Vertical)
                .background(Color.LightGray),
        // ...
    ) { measurables, constraints ->
        // ...
        // Update the maximum scroll value to not scroll beyond limits and stop when scroll
        // reaches the end.
        scrollState.maxValue = (totalHeight - viewportHeight).coerceAtLeast(0)

        // Position the children within the layout.
        layout(constraints.maxWidth, viewportHeight) {
            // The current vertical scroll position, in pixels.
            val scrollY = scrollState.value
            val viewportCenterY = scrollY + viewportHeight / 2

            var placeableLayoutPositionY = 0
            placeables.forEach { placeable ->
                // This sample applies a scaling effect to items based on their distance
                // from the center, creating a wheel-like effect.
                // ...
                // Place the item horizontally centered with a layer transformation for
                // scaling to achieve wheel-like effect.
                placeable.placeRelativeWithLayer(
                    x = constraints.maxWidth / 2 - placeable.width / 2,
                    // Offset y by the scroll position to make placeable visible in the viewport.
                    y = placeableLayoutPositionY - scrollY,
                ) {
                    scaleX = scaleFactor
                    scaleY = scaleFactor
                }
                // Move to the next item's vertical position.
                placeableLayoutPositionY += placeable.height
            }
        }
    }
}
// ...

מגביל שאפשר לגלול בו

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

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

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

@Composable
private fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableFloatStateOf(0f) }
    Box(
        Modifier
            .size(150.dp)
            .scrollable(
                orientation = Orientation.Vertical,
                // Scrollable state: describes how to consume
                // scrolling delta and update offset
                state = rememberScrollableState { delta ->
                    offset += delta
                    delta
                }
            )
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(offset.toString())
    }
}

רכיב בממשק המשתמש שמזהה את הלחיצה של האצבע ומציג את הערך המספרי של המיקום של האצבע
איור 3. רכיב בממשק המשתמש שמזהה את הלחיצה של האצבע ומציג את הערך המספרי של מיקום האצבע.