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

כדי לדפדף בתוכן בכיוון שמאל, ימינה או למעלה או למטה, אפשר להשתמש ה 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: הדף הקרוב ביותר למיקום הצמדה. כברירת מחדל, סמל ה-Snap נמצא בתחילת הפריסה.
  • settledPage: מספר הדף כשלא פועלות אנימציה או גלילה. הזה שונה מהנכס currentPage ב-currentPage מתעדכנת מיד אם הדף קרוב מספיק למיקום הצמדה, אבל settledPage יישאר ללא שינוי עד שכל האנימציות יסתיימו.
  • targetPage: מיקום העצירה המוצע לתנועת גלילה.

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

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. החלת טרנספורמציות על תוכן של זימונית

לדוגמה, כדי להתאים את רמת האטימות של פריטים לפי המרחק שלהם במרכז, משנים את 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 ב-3, תוך התחשבות ברווחים בין הפריטים:

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 משמש כאן רק כדוגמה; אפשר להגדיר כל אחד ממאפייני המרווח הפנימי לערך כלשהו.

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

התכנים הקומפוזביליים 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),
        beyondBoundsPageCount = 10,
        flingBehavior = fling
    ) {
        PagerSampleItem(page = it)
    }
}