Để lướt qua nội dung theo chiều ngang hoặc chiều dọc, bạn có thể sử dụng các thành phần kết hợp
HorizontalPager và VerticalPager. Các thành phần 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 và VerticalPager chiếm toàn bộ chiều cao. Các bộ phân trang cũng chỉ hất 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 bộ phân trang cuộn theo chiều ngang sang trái và phải, hãy sử 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 bộ 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 trì hoãn
Các trang trong cả HorizontalPager và VerticalPager đều được kết hợp 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 trang ngoài màn hình
Theo mặc định, bộ phân trang chỉ tải các trang hiển thị trên màn hình. Để tải thêm trang ngoài màn hình, hãy đặt beyondBoundsPageCount thành một giá trị lớn hơn 0.
Cuộn đến một mục trong bộ phân trang
Để cuộn đến một trang nhất định trong bộ phân trang, hãy tạo một PagerState đối tượng
bằng rememberPagerState() và truyền đối tượng đó làm tham số state cho
bộ phân trang. Bạn có thể gọi PagerState#scrollToPage() trên trạng thái này, bên trong
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 ảnh độ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 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í chụp nhanh 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ỗcurrentPagesẽ cập nhật ngay lập tức nếu trang đủ gần với vị trí chụp nhanh, nhưngsettledPagevẫ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ể sử dụng hàm snapshotFlow để quan sát các thay đổi đối với các 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 trên mỗi lần 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 chỉ báo vào một trang, hãy sử dụng đối tượng PagerState để lấy thông tin về trang nào được chọn trong số các trang và vẽ chỉ báo tuỳ chỉnh.
Ví dụ: để tạo chỉ báo hình trò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à sử dụng vị trí cuộn để áp dụng hiệu ứng cho các mục trong bộ phân trang. Để tìm hiểu khoảng cách của một trang so với trang đã chọn, bạn có thể sử 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 của các mục so với
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 bộ 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 lần lượt chiếm toàn bộ chiều rộng hoặc toàn bộ chiều cao. Bạn có thể đặt biến pageSize thành
Fixed, Fill (mặc định) hoặc tính toán 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 kích thước các trang dựa trên kích thước khung nhìn, hãy sử dụng cách tính toán kích thước trang tuỳ chỉnh. Tạo đố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 ảnh hưởng đến kích thước tối đa và căn chỉnh 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 đặt 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 hiệu ứng tương tự cho VerticalPager. Giá trị 32.dp chỉ được dùng làm ví dụ ở đây; bạn có thể đặt từng kích thước 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 bộ phân trang. Tuy nhiên, bạn có thể tuỳ chỉnh và thay đổi các giá trị mặc định 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ỉ lướt có thể cuộn qua thành một trang tại một thời điểm. Để thay đổi
điều này, hãy đặt pagerSnapDistance trên 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 bộ phân trang tự động chuyển trang
Phần này mô tả cách tạo bộ phân trang tự động chuyển trang có chỉ báo trang trong Compose. Tập hợp các mục tự động cuộn theo chiều ngang, nhưng người dùng cũng có thể vuốt thủ công giữa các mục. Nếu người dùng tương tác với bộ phân trang, thì bộ phân trang sẽ dừng quá trình tự động chuyển trang.
Ví dụ cơ bản
Cùng nhau, các đoạn mã sau sẽ tạo một cách triển khai bộ phân trang tự động chuyển trang cơ bản có chỉ báo trực quan, trong đó mỗi trang hiển thị dưới dạng một màu khác nhau:
@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
AutoAdvancePagertạo một khung hiển thị phân trang theo chiều ngang có tính năng tự động chuyển trang. Hàm này nhận một danh sách các đối tượngColorlàm dữ liệu đầu vào, được dùng làm màu nền cho mỗi trang. pagerStateđược tạo bằngrememberPagerState, chứa trạng thái của bộ phân trang.pagerIsDraggedvàpageIsPressedtheo dõi lượt tương tác của người dùng.LaunchedEffecttự động chuyển trang bộ phân trang sau mỗi 2 giây, trừ phi người dùng kéo bộ phân trang hoặc nhấn một trong các trang.HorizontalPagerhiển thị danh sách các trang, mỗi trang có một thành phần kết hợpTexthiển thị số trang. Đối tượng sửa đổi sẽ điền vào trang, đặt màu nền từpageItemsvà giúp 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đóng vai trò là phần tử gốc và chứa mộtRowđể sắp xếp các chỉ báo trang theo chiều ngang. - Chỉ báo trang tuỳ chỉnh được hiển thị dưới dạng một hàng hình tròn, trong đó mỗi
Boxđược cắt thànhCircleShapeđại diện cho một trang. - Hình tròn của trang hiện tại có màu
DarkGray, trong khi các hình tròn khác có màuLightGray. Tham sốcurrentPageIndexxác định hình tròn nào hiển thị màu xám đậm.
Kết quả
Video này hiển thị bộ phân trang tự động chuyển trang cơ bản từ các đoạn mã trước đó: