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

ตัวแก้ไข 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 รู้สึกเป็นธรรมชาติมากขึ้นเมื่อใช้คอนเทนเนอร์แบบเลื่อนได้มาตรฐาน

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

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

delta รายงานต่อ dispatchRawDelta โดย scrollable

delta รายงานไปยัง dispatchRawDelta โดย scrollableArea*

ตัวชี้เลื่อนขึ้น

เชิงลบ

เชิงบวก

ตัวชี้เลื่อนลง

เชิงบวก

เชิงลบ

ตัวชี้เลื่อนไปทางซ้าย

เชิงลบ

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

ตัวชี้เลื่อนไปทางขวา

เชิงบวก

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

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

  1. การวางแนว: แนวตั้งหรือแนวนอน
  2. LayoutDirection: LTR หรือ RTL (สำคัญอย่างยิ่งสำหรับการเลื่อนแนวนอน)
  3. แฟล็ก reverseScrolling: การกลับทิศทางการเลื่อนหรือไม่

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

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

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

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

สร้างรายการแบบวงล้อที่กำหนดเองโดยใช้ 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 ที่ตรวจจับการกดนิ้วและแสดงค่าตัวเลขสำหรับตำแหน่งของนิ้ว