เพจเจอร์ใน Compose

หากต้องการเลื่อนดูเนื้อหาในลักษณะซ้าย ขวา หรือขึ้นและลง คุณสามารถใช้ Composable ตามลำดับ 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 จะคอมไพล์แบบไม่ใช้การแคชและจัดวางเมื่อจำเป็น เมื่อผู้ใช้เลื่อนดูหน้าต่างๆ Composable จะลบหน้าใดๆ ที่ไม่จำเป็นอีกต่อไป

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

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