כדי להחליף בין תוכן אופקי לתוכן אנכי, אפשר להשתמש ברכיבי ה-Composable HorizontalPager ו-VerticalPager. הפונקציות שלהן דומות לאלה של ViewPager במערכת התצוגה. כברירת מחדל,
HorizontalPager תופס את הרוחב המלא של המסך ו-VerticalPager תופס את הגובה המלא. גם במנגנוני החלקה אפשר להחליק רק דף אחד בכל פעם. אפשר לשנות את כל הגדרות ברירת המחדל האלה.
HorizontalPager
כדי ליצור רכיב החלפה שגולל אופקית שמאלה וימינה, משתמשים ב-HorizontalPager:
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:
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 מורכבים ומסודרים לפי הצורך. כשהמשתמש גולל בין הדפים, רכיב ה-Composable מסיר את הדפים שלא נדרשים יותר.
טעינת דפים נוספים מחוץ למסך
כברירת מחדל, רכיב ה-pager טוען רק את הדפים שמוצגים במסך. כדי לטעון עוד דפים מחוץ למסך, מגדירים את 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) ) } }
החלת אפקטים של גלילה בפריט על התוכן
תרחיש נפוץ לשימוש הוא שימוש במיקום הגלילה כדי להחיל אפקטים על הפריטים של רכיב ה-pager. כדי לדעת כמה רחוק דף מסוים נמצא מהדף שנבחר, אפשר להשתמש בPagerState.currentPageOffsetFraction. לאחר מכן תוכלו להחיל על התוכן אפקטים של טרנספורמציה בהתאם למרחק מהדף שנבחר.
לדוגמה, כדי לשנות את רמת השקיפות של פריטים בהתאם למרחק שלהם מהמרכז, משנים את alpha באמצעות Modifier.graphicsLayer בפריט בתוך רכיב ה-pager:
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 שמוגדרים כברירת מחדל מציינים איך תנועות גלילה פועלות עם רכיב ה-Pager. עם זאת, אפשר להתאים אישית ולשנות את ברירות המחדל, כמו 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רכיב שאפשר להרכיב ממנו תצוגה שמציג את מספר הדף. המשנה ממלא את הדף, מגדיר את צבע הרקע מ-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קובע איזה עיגול יוצג באפור כהה.
התוצאה
בסרטון הזה מוצג רכיב בסיסי של דפדוף אוטומטי מתוך קטעי הקוד הקודמים: