Để lật nội dung theo chiều trái và phải hoặc lên và xuống, bạn có thể dùng các thành phần kết hợp HorizontalPager
và VerticalPager
tương ứng. Các thành phần kết hợp này có chức năng tương tự như ViewPager
trong hệ thống khung hiển thị. Theo mặc định, HorizontalPager
chiếm toàn bộ chiều rộng màn hình, VerticalPager
chiếm toàn bộ chiều cao và các trình phân trang chỉ chuyển một trang tại một thời điểm. Bạn có thể định cấu hình tất cả các giá trị mặc định này.
HorizontalPager
Để tạo một trình phân trang cuộn ngang sang trái và phải, hãy dùng 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
Để tạo một trình phân trang cuộn lên và xuống, hãy sử dụng VerticalPager
:
VerticalPager
// Display 10 items val pagerState = rememberPagerState(pageCount = { 10 }) VerticalPager(state = pagerState) { page -> // Our page content Text( text = "Page: $page", modifier = Modifier.fillMaxWidth() ) }
Tạo theo cách thức lazy
Các trang trong cả HorizontalPager
và VerticalPager
đều được tạo thành một cách trì hoãn và bố trí khi cần. Khi người dùng cuộn qua các trang, thành phần kết hợp sẽ xoá mọi trang không còn cần thiết.
Tải thêm các trang ngoài màn hình
Theo mặc định, trình phân trang chỉ tải các trang hiển thị trên màn hình. Để tải thêm các trang ngoài màn hình, hãy đặt beyondBoundsPageCount
thành một giá trị lớn hơn 0.
Di chuyển đến một mục trong bộ phân trang
Để di chuyển đến một trang nhất định trong trình phân trang, hãy tạo một đối tượng PagerState
bằng cách sử dụng rememberPagerState()
và truyền đối tượng đó làm tham số state
cho trình phân trang. Bạn có thể gọi PagerState#scrollToPage()
trên trạng thái này, bên trong một 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") }
Nếu bạn muốn tạo hiệu ứng chuyển động cho trang, hãy sử dụng hàm 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") }
Nhận thông báo về các thay đổi về trạng thái trang
PagerState
có 3 thuộc tính chứa thông tin về các trang: currentPage
, settledPage
và targetPage
.
currentPage
: Trang gần nhất với vị trí chụp nhanh. Theo mặc định, vị trí snap nằm ở đầu bố cục.settledPage
: Số trang khi không có ảnh động hoặc thao tác cuộn nào đang chạy. Thuộc tính này khác với thuộc tínhcurrentPage
ở chỗcurrentPage
sẽ cập nhật ngay lập tức nếu trang đủ gần với vị trí chụp nhanh, nhưngsettledPage
vẫn giữ nguyên cho đến khi tất cả ảnh động chạy xong.targetPage
: Vị trí dừng được đề xuất cho một chuyển động cuộn.
Bạn có thể dùng hàm snapshotFlow
để theo dõi các thay đổi đối với những biến này và phản ứng với các thay đổi đó. Ví dụ: để gửi một sự kiện phân tích về mỗi thay đổi trang, bạn có thể làm như sau:
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") }
Thêm chỉ báo trang
Để thêm một chỉ báo vào trang, hãy dùng đối tượng PagerState
để lấy thông tin về trang được chọn trong số các trang và vẽ chỉ báo tuỳ chỉnh của bạn.
Ví dụ: nếu muốn có một chỉ báo hình tròn đơn giản, bạn có thể lặp lại số lượng hình tròn và thay đổi màu hình tròn dựa trên việc trang có được chọn hay không, bằng cách sử dụng 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) ) } }

Áp dụng hiệu ứng cuộn mục cho nội dung
Một trường hợp sử dụng phổ biến là dùng vị trí cuộn để áp dụng hiệu ứng cho các mục trong trình phân trang. Để biết một trang cách trang hiện đang chọn bao xa, bạn có thể dùng PagerState.currentPageOffsetFraction
.
Sau đó, bạn có thể áp dụng hiệu ứng biến đổi cho nội dung dựa trên khoảng cách từ trang đã chọn.
Ví dụ: để điều chỉnh độ mờ của các mục dựa trên khoảng cách từ tâm, hãy thay đổi alpha
bằng cách sử dụng Modifier.graphicsLayer
trên một mục bên trong trình phân trang:
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 } }
Kích thước trang tuỳ chỉnh
Theo mặc định, HorizontalPager
và VerticalPager
sẽ chiếm toàn bộ chiều rộng hoặc toàn bộ chiều cao, tương ứng. Bạn có thể đặt biến pageSize
thành Fixed
, Fill
(mặc định) hoặc một cách tính kích thước tuỳ chỉnh.
Ví dụ: để đặt trang có chiều rộng cố định là 100.dp
:
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, pageSize = PageSize.Fixed(100.dp) ) { page -> // page content }
Để định cỡ các trang dựa trên kích thước khung nhìn, hãy sử dụng một phép tính kích thước trang tuỳ chỉnh. Tạo một đối tượng PageSize
tuỳ chỉnh và chia availableSpace
cho 3, có tính đến khoảng cách giữa các mục:
private val threePagesPerViewport = object : PageSize { override fun Density.calculateMainAxisPageSize( availableSpace: Int, pageSpacing: Int ): Int { return (availableSpace - 2 * pageSpacing) / 3 } }
Khoảng đệm nội dung
Cả HorizontalPager
và VerticalPager
đều hỗ trợ thay đổi khoảng đệm nội dung, cho phép bạn tác động đến kích thước tối đa và việc căn chỉnh của các trang.
Ví dụ: việc đặt khoảng đệm start
sẽ căn chỉnh các trang về phía cuối:
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, contentPadding = PaddingValues(start = 64.dp), ) { page -> // page content }
Việc đặt cả khoảng đệm start
và end
thành cùng một giá trị sẽ căn giữa mục theo chiều ngang:
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, contentPadding = PaddingValues(horizontal = 32.dp), ) { page -> // page content }
Việc thiết lập khoảng đệm end
sẽ căn chỉnh các trang về phía đầu:
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, contentPadding = PaddingValues(end = 64.dp), ) { page -> // page content }
Bạn có thể đặt các giá trị top
và bottom
để đạt được các hiệu ứng tương tự cho VerticalPager
. Giá trị 32.dp
chỉ được dùng ở đây làm ví dụ; bạn có thể đặt từng phương diện khoảng đệm thành bất kỳ giá trị nào.
Tuỳ chỉnh hành vi cuộn
Các thành phần kết hợp HorizontalPager
và VerticalPager
mặc định chỉ định cách hoạt động của cử chỉ cuộn với trình chuyển trang. Tuy nhiên, bạn có thể tuỳ chỉnh và thay đổi các giá trị mặc định, chẳng hạn như pagerSnapDistance
hoặc flingBehavior
.
Khoảng cách chụp nhanh
Theo mặc định, HorizontalPager
và VerticalPager
đặt số lượng trang tối đa mà cử chỉ hất có thể cuộn qua thành một trang tại một thời điểm. Để thay đổi chế độ này, hãy đặt pagerSnapDistance
thành 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) } }
Tạo trình chuyển trang tự động chuyển
Phần này mô tả cách tạo một trình phân trang tự động chuyển trang có các chỉ báo trang trong Compose. Tập hợp các mục sẽ tự động cuộn theo chiều ngang, nhưng người dùng cũng có thể vuốt giữa các mục theo cách thủ công. Nếu người dùng tương tác với bộ phân trang, thì bộ phân trang sẽ ngừng tiến trình tự động.
Ví dụ cơ bản
Các đoạn mã sau đây cùng nhau tạo ra một cách triển khai trình phân trang cơ bản tự động chuyển trang với một chỉ báo trực quan, trong đó mỗi trang hiển thị dưới dạng một màu khác:
@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) } }
Các điểm chính về mã
- Hàm
AutoAdvancePager
tạo một khung hiển thị phân trang theo chiều ngang với tính năng tự động chuyển trang. Thành phần này nhận một danh sách các đối tượngColor
làm đầu vào, được dùng làm màu nền cho mỗi trang. pagerState
được tạo bằngrememberPagerState
, giữ trạng thái của trình phân trang.pagerIsDragged
vàpageIsPressed
theo dõi hoạt động tương tác của người dùng.LaunchedEffect
sẽ tự động chuyển trang sau mỗi 2 giây, trừ phi người dùng kéo bộ phân trang hoặc nhấn vào một trong các trang.HorizontalPager
hiển thị danh sách các trang, mỗi trang có một thành phần kết hợpText
hiển thị số trang. Đối tượng sửa đổi này sẽ lấp đầy trang, đặt màu nền từpageItems
và làm cho trang có thể nhấp vào.
@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) ) } } } }
Các điểm chính về mã
- Một thành phần kết hợp
Box
được dùng làm phần tử gốc.- Bên trong
Box
, thành phần kết hợpRow
sắp xếp các chỉ báo trang theo chiều ngang.
- Bên trong
- Chỉ báo trang tuỳ chỉnh xuất hiện dưới dạng một hàng gồm các vòng tròn, trong đó mỗi
Box
được cắt thànhcircle
đại diện cho một trang. - Vòng tròn của trang hiện tại có màu
DarkGray
, trong khi các vòng tròn khác có màuLightGray
. Tham sốcurrentPageIndex
xác định vòng tròn nào sẽ hiển thị màu xám đậm.
Kết quả
Video này cho thấy bộ phân trang cơ bản tự động chuyển trang từ các đoạn mã trước: