เพจเจอร์ใน Compose

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

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

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 จะกำหนดจำนวนสูงสุด หน้าเว็บที่ท่าทางสัมผัสการสะบัดสามารถเลื่อนผ่านไปยังหน้าได้ครั้งละ 1 หน้า หากต้องการเปลี่ยน ตั้งค่านี้ 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)
    }
}