זימונית ב'כתיבה'

כדי לדפדף בתוכן ימינה או שמאלה או למעלה או למטה, אפשר להשתמש ברכיבי ה-Composable‏ HorizontalPager ו-VerticalPager, בהתאמה. לרכיבים האלה יש פונקציות דומות לViewPager במערכת התצוגות. כברירת מחדל, HorizontalPager תופס את כל רוחב המסך, VerticalPager תופס את כל הגובה והמכשירים להעברת דפים מעבירים רק דף אחד בכל פעם. אפשר לשנות את כל הגדרות ברירת המחדל האלה.

HorizontalPager

כדי ליצור חלונית גלילה שאפשר לגלול בה אופקית שמאלה וימינה, משתמשים ב-HorizontalPager:

איור 1. הדגמה של HorizontalPager

// Display 10 items
val pagerState = rememberPagerState(pageCount = {
    10
})
HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier.fillMaxWidth()
    )
}

VerticalPager

כדי ליצור חלונית גלילה שאפשר לגלול בה למעלה ולמטה, משתמשים ב-VerticalPager:

איור 2. הדגמה של VerticalPager

// Display 10 items
val pagerState = rememberPagerState(pageCount = {
    10
})
VerticalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier.fillMaxWidth()
    )
}

יצירת נכסים באופן מושהה

הדפים ב-HorizontalPager וב-VerticalPager מורכבים באופן עצל ומוצבים כשצריך. כשהמשתמש גולל בין הדפים, הרכיב הניתן להתאמה מסיר דפים שכבר לא נדרשים.

טעינת דפים נוספים מחוץ למסך

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

גלילה לפריט בחלונית הגלילה

כדי לגלול לדף מסוים בחלונית הגלילה, יוצרים אובייקט PagerState באמצעות rememberPagerState() ומעבירים אותו כפרמטר state לחלונית הגלילה. אפשר להפעיל את הפונקציה PagerState#scrollToPage() במצב הזה, בתוך CoroutineScope:

val pagerState = rememberPagerState(pageCount = {
    10
})
HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier
            .fillMaxWidth()
            .height(100.dp)
    )
}

// scroll to page
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
    coroutineScope.launch {
        // Call scroll to on pagerState
        pagerState.scrollToPage(5)
    }
}, modifier = Modifier.align(Alignment.BottomCenter)) {
    Text("Jump to Page 5")
}

כדי להציג אנימציה בדף, משתמשים בפונקציה PagerState#animateScrollToPage():

val pagerState = rememberPagerState(pageCount = {
    10
})

HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier
            .fillMaxWidth()
            .height(100.dp)
    )
}

// scroll to page
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
    coroutineScope.launch {
        // Call scroll to on pagerState
        pagerState.animateScrollToPage(5)
    }
}, modifier = Modifier.align(Alignment.BottomCenter)) {
    Text("Jump to Page 5")
}

קבלת התראות על שינויים במצב הדף

ל-PagerState יש שלושה מאפיינים עם מידע על דפים: currentPage,‏ settledPage ו-targetPage.

  • currentPage: הדף הקרוב ביותר למיקום הצמדה. כברירת מחדל, מיקום הצמדה הוא בתחילת הפריסה.
  • settledPage: מספר הדף כשאין אנימציה או גלילה. ההבדל בין המאפיין הזה לבין המאפיין currentPage הוא שהמאפיין currentPage מתעדכן באופן מיידי אם הדף קרוב מספיק למיקום הצמדה, אבל הערך של settledPage נשאר ללא שינוי עד שכל האנימציות מסתיימות.
  • targetPage: מיקום העצירה המוצע לתנועת גלילה.

אפשר להשתמש בפונקציה snapshotFlow כדי לעקוב אחרי השינויים במשתנים האלה ולהגיב להם. לדוגמה, כדי לשלוח אירוע Analytics בכל שינוי דף, אפשר לבצע את הפעולות הבאות:

val pagerState = rememberPagerState(pageCount = {
    10
})

LaunchedEffect(pagerState) {
    // Collect from the a snapshotFlow reading the currentPage
    snapshotFlow { pagerState.currentPage }.collect { page ->
        // Do something with each page change, for example:
        // viewModel.sendPageSelectedEvent(page)
        Log.d("Page change", "Page changed to $page")
    }
}

VerticalPager(
    state = pagerState,
) { page ->
    Text(text = "Page: $page")
}

הוספת אינדיקטור דף

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

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

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    modifier = Modifier.fillMaxSize()
) { page ->
    // Our page content
    Text(
        text = "Page: $page",
    )
}
Row(
    Modifier
        .wrapContentHeight()
        .fillMaxWidth()
        .align(Alignment.BottomCenter)
        .padding(bottom = 8.dp),
    horizontalArrangement = Arrangement.Center
) {
    repeat(pagerState.pageCount) { iteration ->
        val color = if (pagerState.currentPage == iteration) Color.DarkGray else Color.LightGray
        Box(
            modifier = Modifier
                .padding(2.dp)
                .clip(CircleShape)
                .background(color)
                .size(16.dp)
        )
    }
}

סרגל גלילה עם אינדיקטור עגול מתחת לתוכן
איור 3. סרגל גלילה עם אינדיקטור עגול מתחת לתוכן

החלת אפקטים של גלילה של פריטים על תוכן

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

איור 4. החלת טרנספורמציות על תוכן של Pager

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

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(state = pagerState) { page ->
    Card(
        Modifier
            .size(200.dp)
            .graphicsLayer {
                // Calculate the absolute offset for the current page from the
                // scroll position. We use the absolute value which allows us to mirror
                // any effects for both directions
                val pageOffset = (
                    (pagerState.currentPage - page) + pagerState
                        .currentPageOffsetFraction
                    ).absoluteValue

                // We animate the alpha, between 50% and 100%
                alpha = lerp(
                    start = 0.5f,
                    stop = 1f,
                    fraction = 1f - pageOffset.coerceIn(0f, 1f)
                )
            }
    ) {
        // Card content
    }
}

גדלי דפים מותאמים אישית

כברירת מחדל, HorizontalPager ו-VerticalPager תופסים את כל רוחב המסך או את כל הגובה שלו, בהתאמה. אפשר להגדיר את המשתנה pageSize כך שיכלול את הערך Fixed, את הערך Fill (ברירת המחדל) או חישוב מותאם אישית של גודל.

לדוגמה, כדי להגדיר דף ברוחב קבוע של 100.dp:

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    pageSize = PageSize.Fixed(100.dp)
) { page ->
    // page content
}

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

private val threePagesPerViewport = object : PageSize {
    override fun Density.calculateMainAxisPageSize(
        availableSpace: Int,
        pageSpacing: Int
    ): Int {
        return (availableSpace - 2 * pageSpacing) / 3
    }
}

הוספת תוכן

גם HorizontalPager וגם VerticalPager תומכים בשינוי של הרווח מסביב לתוכן, שמאפשר לכם להשפיע על הגודל המקסימלי ועל היישור של הדפים.

לדוגמה, הגדרת הרווח start מיישר את הדפים לקראת הסוף:

סרגל גלילה עם מרווח תחילה שבו התוכן מיושר לקראת הסוף

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    contentPadding = PaddingValues(start = 64.dp),
) { page ->
    // page content
}

הגדרת הערך של הרווח start ושל הרווח end לאותו ערך מרכזת את הפריט אופקית:

סרגל גלילה עם מרווחים בהתחלה ובסוף שמציגים את התוכן במרכז

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    contentPadding = PaddingValues(horizontal = 32.dp),
) { page ->
    // page content
}

הגדרת הרווח end מיישר את הדפים לכיוון ההתחלה:

סרגל גלילה עם מרווחים בהתחלה ובסיום, שבו התוכן מיושר להתחלה

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    contentPadding = PaddingValues(end = 64.dp),
) { page ->
    // page content
}

אפשר להגדיר את הערכים של top ו-bottom כדי להשיג אפקטים דומים ל-VerticalPager. הערך 32.dp משמש כאן רק כדוגמה. אפשר להגדיר לכל אחד ממאפייני המילוי ערך כלשהו.

התאמה אישית של התנהגות הגלילה

רכיבי ה-Composable HorizontalPager ו-VerticalPager שמוגדרים כברירת מחדל קובעים איך מחוות הגלילה פועלות עם הגלילה. עם זאת, אפשר להתאים אישית ולשנות את הגדרות ברירת המחדל, כמו pagerSnapDistance או flingBehavior.

מרחק הצמדה

כברירת מחדל, HorizontalPager ו-VerticalPager מגדירים את המספר המקסימלי של דפים שאפשר לגלול דרכם בתנועת זריקה לדף אחד בכל פעם. כדי לשנות את ההגדרה הזו, מגדירים את הערך pagerSnapDistance ב-flingBehavior:

val pagerState = rememberPagerState(pageCount = { 10 })

val fling = PagerDefaults.flingBehavior(
    state = pagerState,
    pagerSnapDistance = PagerSnapDistance.atMost(10)
)

Column(modifier = Modifier.fillMaxSize()) {
    HorizontalPager(
        state = pagerState,
        pageSize = PageSize.Fixed(200.dp),
        beyondViewportPageCount = 10,
        flingBehavior = fling
    ) {
        PagerSampleItem(page = it)
    }
}