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

ตัวแก้ไข 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, หรือคอมโพสได้ เช่น LazyColumn สำหรับรายการการเลื่อนมาตรฐาน คอมโพเนนต์ระดับสูงเหล่านี้ใช้งานง่ายกว่าสำหรับกรณีการใช้งานทั่วไป และสร้างขึ้นโดยใช้ scrollableArea

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

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

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

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

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

ท่าทางสัมผัสของผู้ใช้

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

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