เพจเจอร์ใน Compose

หากต้องการเลื่อนดูเนื้อหาไปทางซ้ายและขวาหรือขึ้นและลง คุณสามารถใช้คอมโพสิเบิล 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()
    )
}

การสร้างแบบ Lazy

หน้าเว็บทั้งใน HorizontalPager และ VerticalPager จะคอมไพล์แบบไม่ใช้ทรัพยากรและจัดวางเมื่อจำเป็น เมื่อผู้ใช้เลื่อนดูหน้าต่างๆ คอมโพสิเบิลจะนำหน้าเว็บที่ไม่จําเป็นออก

โหลดหน้าเว็บเพิ่มเติมนอกหน้าจอ

โดยค่าเริ่มต้น ตัวแบ่งหน้าจะโหลดเฉพาะหน้าที่มองเห็นได้บนหน้าจอเท่านั้น หากต้องการโหลดหน้าเว็บเพิ่มเติมที่ไม่ได้อยู่ในหน้าจอ ให้ตั้งค่า beyondBoundsPageCount เป็นค่าที่มากกว่า 0

เลื่อนไปยังรายการในโปรแกรมแบ่งหน้า

หากต้องการเลื่อนไปยังหน้าที่ต้องการในโปรแกรมเลื่อนหน้า ให้สร้างออบเจ็กต์ 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 มีพร็อพเพอร์ตี้ 3 รายการที่มีข้อมูลเกี่ยวกับหน้าเว็บ ได้แก่ currentPage, settledPage และ targetPage

  • currentPage: หน้าเว็บที่อยู่ใกล้กับตำแหน่งการจับภาพมากที่สุด โดยค่าเริ่มต้น ตำแหน่งการยึดจะอยู่ที่จุดเริ่มต้นของเลย์เอาต์
  • 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),
        beyondViewportPageCount = 10,
        flingBehavior = fling
    ) {
        PagerSampleItem(page = it)
    }
}

สร้างโปรแกรมเลื่อนหน้าอัตโนมัติ

ส่วนนี้จะอธิบายวิธีสร้างตัวเลื่อนหน้าเว็บที่เลื่อนไปข้างหน้าโดยอัตโนมัติพร้อมตัวบ่งชี้หน้าเว็บในเครื่องมือเขียน คอลเล็กชันรายการจะเลื่อนในแนวนอนโดยอัตโนมัติ แต่ผู้ใช้ก็ปัดดูรายการต่างๆ ด้วยตนเองได้เช่นกัน หากผู้ใช้โต้ตอบกับโปรแกรมเลื่อนหน้า ระบบจะหยุดการเลื่อนอัตโนมัติ

ตัวอย่างพื้นฐาน

ร่วมกัน ข้อมูลโค้ดต่อไปนี้จะสร้างการใช้งานตัวเลื่อนหน้าเว็บแบบเลื่อนอัตโนมัติขั้นพื้นฐานพร้อมตัวบ่งชี้ภาพ ซึ่งแต่ละหน้าจะแสดงผลเป็นสีที่แตกต่างกัน

@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 จะเลื่อนหน้าต่อไปโดยอัตโนมัติทุก 2 วินาที เว้นแต่ผู้ใช้จะลากตัวเลื่อนหรือกดหน้าใดหน้าหนึ่ง
  • 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 เป็นองค์ประกอบรูท
    • ภายใน Box คอมโพสิชัน Row จะจัดเรียงตัวบ่งชี้หน้าเว็บในแนวนอน
  • ตัวบ่งชี้หน้าเว็บที่กำหนดเองจะแสดงเป็นแถววงกลม โดยที่ Box ที่คลิปกับ circle แต่ละรายการแสดงถึงหน้าเว็บ
  • วงกลมของหน้าปัจจุบันจะเป็นสี DarkGray ส่วนวงกลมอื่นๆ จะเป็น LightGray พารามิเตอร์ currentPageIndex จะกำหนดวงกลมที่จะแสดงผลเป็นสีเทาเข้ม

ผลลัพธ์

วิดีโอนี้แสดงตัวเลื่อนหน้าเว็บแบบเลื่อนอัตโนมัติพื้นฐานจากตัวอย่างก่อนหน้า

รูปที่ 1 หน้าเลื่อนอัตโนมัติที่เลื่อนไปทีละหน้าโดยมีความล่าช้า 2 วินาที