แป้นกดร่วมการเลื่อน

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

@Composable
private fun ScrollBoxes() {
    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .verticalScroll(rememberScrollState())
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

รายการแนวตั้งแบบง่ายที่ตอบสนองต่อท่าทางสัมผัสการเลื่อน
รูปที่ 1 รายการแนวตั้งแบบง่ายที่ตอบสนองต่อท่าทางการเลื่อน

ScrollState ช่วยให้คุณเปลี่ยนตำแหน่งการเลื่อนหรือรับสถานะปัจจุบันได้ หากต้องการสร้างโดยใช้พารามิเตอร์เริ่มต้น ให้ใช้ rememberScrollState()

@Composable
private fun ScrollBoxesSmooth() {
    // Smoothly scroll 100px on first composition
    val state = rememberScrollState()
    LaunchedEffect(Unit) { state.animateScrollTo(100) }

    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .padding(horizontal = 8.dp)
            .verticalScroll(state)
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

ตัวแก้ไขพื้นที่ที่เลื่อนได้

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

แม้ว่า scrollableArea จะใช้สำหรับการติดตั้งใช้งานที่กำหนดเอง แต่โดยทั่วไปคุณควร เลือกใช้โซลูชันสำเร็จรูป เช่น verticalScroll, horizontalScroll หรือ Composable เช่น LazyColumn สำหรับรายการเลื่อนมาตรฐาน คอมโพเนนต์ระดับสูงเหล่านี้ จะเรียบง่ายกว่าสำหรับกรณีการใช้งานทั่วไป และสร้างขึ้นโดยใช้ scrollableArea

ความแตกต่างระหว่างตัวแก้ไข scrollableArea กับ scrollable

ความแตกต่างหลักระหว่าง scrollableArea กับ scrollable อยู่ที่วิธี ตีความท่าทางการเลื่อนของผู้ใช้ ดังนี้

  • scrollable (เดลต้ารูปแบบดิบ): เดลต้าแสดงถึงการเคลื่อนไหวทางกายภาพของอินพุตของผู้ใช้ (เช่น การลากเคอร์เซอร์) บนหน้าจอโดยตรง
  • scrollableArea (เดลต้าที่เน้นเนื้อหา): delta จะกลับด้านเชิงความหมาย เพื่อแสดงการเปลี่ยนแปลงที่เลือกในตำแหน่งการเลื่อนเพื่อให้เนื้อหาปรากฏขึ้นพร้อมกับการเคลื่อนไหวของผู้ใช้ ซึ่งมักจะตรงกันข้ามกับการเคลื่อนที่ของเคอร์เซอร์

ลองนึกภาพว่า scrollable บอกวิธีที่เคอร์เซอร์เคลื่อนที่ ขณะที่ scrollableArea แปลการเคลื่อนที่ของเคอร์เซอร์นั้นเป็นวิธีที่เนื้อหาควร เคลื่อนที่ภายในมุมมองที่เลื่อนได้ทั่วไป การกลับด้านนี้เองที่ทำให้ scrollableArea ดูเป็นธรรมชาติมากขึ้นเมื่อใช้คอนเทนเนอร์ที่เลื่อนได้มาตรฐาน

ตารางต่อไปนี้สรุปเครื่องหมายเดลต้าสำหรับสถานการณ์ทั่วไป

ท่าทางผู้ใช้

เดลต้าที่รายงานต่อ dispatchRawDelta โดย scrollable

ส่วนต่างที่รายงานต่อ dispatchRawDelta โดย scrollableArea*

Pointer เลื่อนขึ้น

เชิงลบ

เชิงบวก

เคอร์เซอร์เลื่อนลง

เชิงบวก

เชิงลบ

เคอร์เซอร์เลื่อนไปทางซ้าย

เชิงลบ

บวก (ลบสําหรับ RTL)

เคอร์เซอร์เลื่อนไปทางขวา

เชิงบวก

ลบ (บวกสำหรับ RTL)

(*) หมายเหตุเกี่ยวกับscrollableAreaเครื่องหมายเดลต้า: เครื่องหมายของเดลต้าจาก scrollableAreaไม่ใช่การกลับค่าอย่างง่าย โดยจะพิจารณาอย่างชาญฉลาดจากปัจจัยต่อไปนี้

  1. การวางแนว: แนวตั้งหรือแนวนอน
  2. LayoutDirection: LTR หรือ RTL (สำคัญอย่างยิ่งสำหรับการเลื่อนแนวนอน)
  3. reverseScrolling flag: Whether scroll direction is inverted.

นอกจากการกลับค่าเดลต้าการเลื่อนแล้ว scrollableArea ยังตัดเนื้อหา ให้พอดีกับขอบเขตของเลย์เอาต์และจัดการการแสดงผลเอฟเฟกต์การเลื่อนเกิน ด้วย โดยค่าเริ่มต้น ฟังก์ชันนี้จะใช้เอฟเฟกต์ที่ LocalOverscrollFactory มีให้ คุณปรับแต่งหรือปิดใช้ได้โดยใช้การโอเวอร์โหลด scrollableArea ที่ยอมรับพารามิเตอร์ OverscrollEffect

กรณีที่ควรใช้ตัวแก้ไข scrollableArea

คุณควรใช้ตัวแก้ไข scrollableArea เมื่อต้องการสร้างคอมโพเนนต์การเลื่อนแบบกำหนดเองที่ตัวแก้ไข horizontalScroll หรือ verticalScroll หรือเลย์เอาต์แบบ Lazy ไม่ได้ให้บริการอย่างเพียงพอ ซึ่งมักเกี่ยวข้องกับกรณีต่อไปนี้

  • ตรรกะเลย์เอาต์ที่กำหนดเอง: เมื่อการจัดเรียงรายการเปลี่ยนแปลงแบบไดนามิก ตามตำแหน่งการเลื่อน
  • เอฟเฟกต์ภาพที่ไม่ซ้ำกัน: การใช้การเปลี่ยนรูป การปรับขนาด หรือเอฟเฟกต์อื่นๆ กับเด็กขณะเลื่อน
  • การควบคุมโดยตรง: ต้องการการควบคุมกลไกการเลื่อนอย่างละเอียด นอกเหนือจากที่ verticalScroll หรือเลย์เอาต์แบบเลซี่แสดง

สร้างรายการที่กำหนดเองคล้ายวงล้อโดยใช้ scrollableArea

ตัวอย่างต่อไปนี้แสดงการใช้ scrollableArea เพื่อสร้างรายการแนวตั้งที่กำหนดเองซึ่งรายการจะเล็กลงเมื่อเคลื่อนออกจากตรงกลาง ทำให้เกิดเอฟเฟกต์ภาพคล้าย "วงล้อ" การเปลี่ยนรูปแบบที่ขึ้นอยู่กับการเลื่อนประเภทนี้เป็น กรณีการใช้งานที่สมบูรณ์แบบสำหรับ scrollableArea

รูปที่ 2 รายการแนวตั้งที่ปรับแต่งโดยใช้ scrollableArea

@Composable
private fun ScrollableAreaSample() {
    // ...
    Layout(
        modifier =
            Modifier
                .size(150.dp)
                .scrollableArea(scrollState, Orientation.Vertical)
                .background(Color.LightGray),
        // ...
    ) { measurables, constraints ->
        // ...
        // Update the maximum scroll value to not scroll beyond limits and stop when scroll
        // reaches the end.
        scrollState.maxValue = (totalHeight - viewportHeight).coerceAtLeast(0)

        // Position the children within the layout.
        layout(constraints.maxWidth, viewportHeight) {
            // The current vertical scroll position, in pixels.
            val scrollY = scrollState.value
            val viewportCenterY = scrollY + viewportHeight / 2

            var placeableLayoutPositionY = 0
            placeables.forEach { placeable ->
                // This sample applies a scaling effect to items based on their distance
                // from the center, creating a wheel-like effect.
                // ...
                // Place the item horizontally centered with a layer transformation for
                // scaling to achieve wheel-like effect.
                placeable.placeRelativeWithLayer(
                    x = constraints.maxWidth / 2 - placeable.width / 2,
                    // Offset y by the scroll position to make placeable visible in the viewport.
                    y = placeableLayoutPositionY - scrollY,
                ) {
                    scaleX = scaleFactor
                    scaleY = scaleFactor
                }
                // Move to the next item's vertical position.
                placeableLayoutPositionY += placeable.height
            }
        }
    }
}
// ...

ตัวแก้ไขที่เลื่อนได้

ตัวปรับแต่ง scrollable แตกต่างจากตัวปรับแต่งการเลื่อนตรงที่ scrollable จะตรวจหา ท่าทางการเลื่อนและบันทึกเดลต้า แต่จะไม่ชดเชยเนื้อหาโดยอัตโนมัติ แต่จะมอบสิทธิ์ให้ผู้ใช้ผ่าน ScrollableState ซึ่งจำเป็นเพื่อให้ตัวแก้ไขนี้ทำงานได้อย่างถูกต้อง

เมื่อสร้าง ScrollableState คุณต้องระบุฟังก์ชัน consumeScrollDelta ซึ่งจะเรียกใช้ในแต่ละขั้นตอนการเลื่อน (โดยการป้อนท่าทางสัมผัส การเลื่อนแบบสมูท หรือการปัด) พร้อมกับเดลต้าในหน่วยพิกเซล ฟังก์ชันนี้ต้องแสดงผล ระยะการเลื่อนที่ใช้ไป เพื่อให้แน่ใจว่าระบบจะส่งต่อเหตุการณ์อย่างถูกต้อง ในกรณีที่มีองค์ประกอบที่ซ้อนกันซึ่งมีตัวแก้ไข scrollable

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

@Composable
private fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableFloatStateOf(0f) }
    Box(
        Modifier
            .size(150.dp)
            .scrollable(
                orientation = Orientation.Vertical,
                // Scrollable state: describes how to consume
                // scrolling delta and update offset
                state = rememberScrollableState { delta ->
                    offset += delta
                    delta
                }
            )
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(offset.toString())
    }
}

องค์ประกอบ UI ที่ตรวจหาการกดนิ้วและแสดงค่าตัวเลขสำหรับตำแหน่งของนิ้ว
รูปที่ 3 องค์ประกอบ UI ที่ตรวจจับการกดด้วยนิ้วและแสดงค่าตัวเลขสำหรับตำแหน่งของนิ้ว