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

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

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

כלי לדפדוף עם ריפוד בהתחלה, שבו התוכן מיושר לסוף
איור 5. כלי לדפדוף עם מרווח פנימי בהתחלה.

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

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

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

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

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

כלי לדפדוף עם ריפוד בהתחלה ובסוף, שבו התוכן מיושר להתחלה
איור 7. כלי לדפדוף עם ריפוד בסוף.

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)
    }
}

יצירת כלי לדפדוף אוטומטי

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

דוגמה בסיסית

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

@Composable
fun AutoAdvancePager(pageItems: List<Color>, modifier: Modifier = Modifier) {
    Box(modifier = Modifier.fillMaxSize()) {
        val pagerState = rememberPagerState(pageCount = { pageItems.size })
        val pagerIsDragged by pagerState.interactionSource.collectIsDraggedAsState()

        val pageInteractionSource = remember { MutableInteractionSource() }
        val pageIsPressed by pageInteractionSource.collectIsPressedAsState()

        // Stop auto-advancing when pager is dragged or one of the pages is pressed
        val autoAdvance = !pagerIsDragged && !pageIsPressed

        if (autoAdvance) {
            LaunchedEffect(pagerState, pageInteractionSource) {
                while (true) {
                    delay(2000)
                    val nextPage = (pagerState.currentPage + 1) % pageItems.size
                    pagerState.animateScrollToPage(nextPage)
                }
            }
        }

        HorizontalPager(
            state = pagerState
        ) { page ->
            Text(
                text = "Page: $page",
                textAlign = TextAlign.Center,
                modifier = modifier
                    .fillMaxSize()
                    .background(pageItems[page])
                    .clickable(
                        interactionSource = pageInteractionSource,
                        indication = LocalIndication.current
                    ) {
                        // Handle page click
                    }
                    .wrapContentSize(align = Alignment.Center)
            )
        }

        PagerIndicator(pageItems.size, pagerState.currentPage)
    }
}

מידע חשוב על הקוד

  • הפונקציה AutoAdvancePager יוצרת תצוגת דפדוף אופקית עם מעבר אוטומטי. היא מקבלת כקלט רשימה של אובייקטים מסוג Color, שמשמשים כצבעי הרקע של כל דף.
  • ‫pagerState נוצר באמצעות rememberPagerState, שמכיל את המצב של הכלי לדפדוף.
  • ‫pagerIsDragged ו-pageIsPressed עוקבים אחרי האינטראקציה של המשתמשים.
  • ה-LaunchedEffect מעביר את הכלי לדפדוף באופן אוטומטי כל שתי שניות, אלא אם המשתמש גורר את הכלי לדפדוף או לוחץ על אחד מהדפים.
  • HorizontalPager מציג רשימה של דפים, כל אחד עם Text composable שמציג את מספר הדף. המשתנה ממלא את הדף, מגדיר את צבע הרקע מ-pageItems והופך את הדף לניתן ללחיצה.

@Composable
fun PagerIndicator(pageCount: Int, currentPageIndex: Int, modifier: Modifier = Modifier) {
    Box(modifier = Modifier.fillMaxSize()) {
        Row(
            modifier = Modifier
                .wrapContentHeight()
                .fillMaxWidth()
                .align(Alignment.BottomCenter)
                .padding(bottom = 8.dp),
            horizontalArrangement = Arrangement.Center
        ) {
            repeat(pageCount) { iteration ->
                val color = if (currentPageIndex == iteration) Color.DarkGray else Color.LightGray
                Box(
                    modifier = modifier
                        .padding(2.dp)
                        .clip(CircleShape)
                        .background(color)
                        .size(16.dp)
                )
            }
        }
    }
}

מידע חשוב על הקוד

  • רכיב קומפוזבילי Box משמש כרכיב הבסיס ומכיל Row כדי לסדר את האינדיקטורים של הדפים בצורה אופקית.
  • אינדיקטור מותאם אישית של דף מוצג כשורה של עיגולים, שבה כל Box שחתוך ל-CircleShape מייצג דף.
  • העיגול של הדף הנוכחי הוא בצבע DarkGray, והעיגולים האחרים הם בצבע LightGray. הפרמטר currentPageIndex קובע איזה עיגול מוצג באפור כהה.

תוצאה

בסרטון הזה מוצג הכלי הבסיסי לדפדוף עם מעבר אוטומטי מקטעי הקוד הקודמים:

איור 8. כלי לדפדוף אוטומטי עם השהיה של שתי שניות בין כל דף.

מקורות מידע נוספים