ลากคอมโพสิเบิล

การอนุญาตให้ผู้ใช้ลากคอมโพเนนต์ไปมาบนหน้าจอเป็นสิ่งสำคัญสำหรับแอป Android สมัยใหม่ ไม่ว่าคุณจะสร้างแถบเลื่อนที่กำหนดเอง คอมโพเนนต์ปัดเพื่อปิด หรือ Canvas แบบอินเทอร์แอกทีฟ Jetpack Compose ก็มีชุด API ที่ครอบคลุมเพื่อจัดการเหตุการณ์การลากได้อย่างง่ายดาย

API การลากของ Compose จะแยกความซับซ้อนของการโต้ตอบแบบสัมผัสข้อมูลดิบออกไป แทนที่จะคำนวณพิกัดการสัมผัสและจัดการวงจรของท่าทางสัมผัสด้วยตนเองโดยใช้ pointerInput คุณสามารถใช้ตัวแก้ไขในตัวที่เชื่อมโยงโดยตรงกับระบบการจัดการสถานะและภาพเคลื่อนไหวของ Compose

หน้านี้อธิบายวิธีและกรณีที่ควรใช้ API การลากแต่ละรายการ

เลือก API การลากที่ถูกต้อง

มี API หลายรายการที่คุณใช้เพื่อใช้เนื้อหาที่ลากได้

  • เลือกคอมโพเนนต์ Material มาตรฐาน (เช่น SwipeToDismissBox หรือลิ้นชักการนำทาง) เมื่อ: คุณต้องการลักษณะการปัดหรือลาก Material 3 เริ่มต้น (เช่น การลบอีเมล การเก็บงานถาวร หรือการเปิดเมนูด้านข้าง / Bottom Sheet) โดยมีลักษณะการทำงานในตัวโดยไม่ต้องมีการจัดการสถานะหรือเลย์เอาต์ที่กำหนดเอง
  • เลือก Modifier.draggable เมื่อ: คุณต้องการเพียงเดลต้าการลากแบบข้อมูลดิบ (เช่น การลากแถบเลื่อนที่กำหนดเอง) ดูข้อมูลเพิ่มเติมได้ที่ลากตามแกนเดียวด้วย draggable
    • เลือก Modifier.draggable2D เมื่อ: คุณต้องการให้คอมโพเนนต์เคลื่อนที่อย่างต่อเนื่องทั้งในแกน X และ Y พร้อมกัน (เช่น การเลื่อนดูแผนที่หรือการสร้างปุ่มการทำงานแบบลอยที่ผู้ใช้ลากไปที่ใดก็ได้บนหน้าจอ) ดูข้อมูลเพิ่มเติมได้ที่ลากใน 2 มิติด้วย Modifier.draggable2D
  • เลือก Modifier.anchoredDraggable เมื่อ: คุณกำลังสร้างคอมโพเนนต์แบบอินเทอร์แอกทีฟที่กำหนดเองซึ่งต้องสแนประหว่างตำแหน่งที่ไม่ต่อเนื่องกัน 2 ตำแหน่งขึ้นไป (เช่น Bottom Sheet, ลิ้นชักการนำทาง หรือการปัดเพื่อแสดง) ดูข้อมูลเพิ่มเติมได้ที่ลากคอมโพเนนต์ระหว่างจุดยึดด้วย anchoredDraggable

ลากตามแกนเดียวด้วย draggable

ตัวแก้ไข draggable เป็นจุดแรกเข้าในระดับสูงสำหรับท่าทางสัมผัสการลากในทิศทางเดียว และรายงานระยะทางในการลากเป็นพิกเซล

โปรดทราบว่าตัวแก้ไขนี้คล้ายกับ scrollable ตรงที่ตรวจจับเฉพาะท่าทางสัมผัสเท่านั้น คุณต้องคงสถานะไว้และแสดงสถานะบนหน้าจอ เช่น โดยการย้ายองค์ประกอบผ่านตัวแก้ไข offset ดังนี้

@Composable
private fun DraggableText() {
    var offsetX by remember { mutableFloatStateOf(0f) }
    Text(
        modifier = Modifier
            .offset { IntOffset(offsetX.roundToInt(), 0) }
            .draggable(
                orientation = Orientation.Horizontal,
                state = rememberDraggableState { delta ->
                    offsetX += delta
                }
            ),
        text = "Drag me!"
    )
}

หากต้องการควบคุมท่าทางการลากทั้งหมด ให้พิจารณาใช้ตัวตรวจจับท่าทางการลากแทนผ่านตัวปรับแต่ง pointerInput

@Composable
private fun DraggableTextLowLevel() {
    Box(modifier = Modifier.fillMaxSize()) {
        var offsetX by remember { mutableFloatStateOf(0f) }
        var offsetY by remember { mutableFloatStateOf(0f) }

        Box(
            Modifier
                .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
                .background(Color.Blue)
                .size(50.dp)
                .pointerInput(Unit) {
                    detectDragGestures { change, dragAmount ->
                        change.consume()
                        offsetX += dragAmount.x
                        offsetY += dragAmount.y
                    }
                }
        )
    }
}

แทรกข้อความแสดงแทนที่นี่
รูปที่ 1 องค์ประกอบ UI ที่ลากด้วยนิ้ว

ลากคอมโพเนนต์ระหว่างจุดยึดด้วย anchoredDraggable

ตัวแก้ไข anchoredDraggable ช่วยให้คุณลากคอมโพเนนต์ระหว่างชุดตำแหน่งที่กำหนดไว้ล่วงหน้าซึ่งเรียกว่าจุดยึดได้ เมื่อผู้ใช้ปล่อยท่าทางการลาก คอมโพเนนต์จะหยุดที่ Anchor ที่ใกล้ที่สุด anchoredDraggable มีประโยชน์สำหรับการสร้างคอมโพเนนต์ เช่น Bottom Sheet ที่กำหนดเอง แถบเลื่อนที่ซับซ้อน หรือแผงลิ้นชัก

แพลตฟอร์ม API

AnchoredDraggable API ประกอบด้วยส่วนหลักๆ ต่อไปนี้

  • Anchors (DraggableAnchors): ตัวสร้างที่กำหนดการจับคู่ระหว่างสถานะที่กำหนดเอง (เช่น คลาส enum สำหรับ Start, Center, End) กับค่าออฟเซ็ตพิกเซลที่เกี่ยวข้อง
  • State (AnchoredDraggableState<T>): ตัวเก็บสถานะที่จัดการตำแหน่งปัจจุบัน (ออฟเซ็ต) ของคอมโพเนนต์ จุดยึดที่คอมโพเนนต์อยู่ และความคืบหน้าในการลาก ควรสร้างโดยใช้ rememberSaveable และ AnchoredDraggableState.Saver<T> เพื่อรักษาสถานะไว้เมื่อมีการเปลี่ยนแปลงการกำหนดค่า
  • ตัวปรับแต่งท่าทางสัมผัส (Modifier.anchoredDraggable): ตัวปรับแต่งที่ตรวจจับท่าทางสัมผัสการลากตาม Orientation อัปเดตสถานะด้วยเดลต้าการลาก และจัดการท่าทางสัมผัสการเหวี่ยงเพื่อทำให้คอมโพเนนต์เคลื่อนไหวไปยัง Anchor ที่ใกล้ที่สุดเมื่อปล่อย

ตัวอย่าง: สร้างคอมโพเนนต์ SwipeToDismiss ที่กำหนดเอง

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

  1. กำหนดค่า Anchor กำหนด enum ที่แสดงถึงตำแหน่งที่ไม่ต่อเนื่องซึ่งเนื้อหาที่ลากได้สามารถวางได้ ดังนี้

    private enum class AnchoredDraggableSwipeDismissValue { DismissedStart, Resting }

  2. สร้างอินสแตนซ์และบันทึก AnchoredDraggableState ใช้ rememberSaveable กับ AnchoredDraggableState.Saver() เพื่อคงตำแหน่งปัจจุบันของคอมโพเนนต์ไว้ตลอดการสร้างกิจกรรมใหม่และการเปลี่ยนแปลงการกำหนดค่า (เช่น การหมุนหน้าจอหรือการเปลี่ยนธีม)

    val state =
        rememberSaveable(saver = AnchoredDraggableState.Saver()) {
            AnchoredDraggableState(initialValue = AnchoredDraggableSwipeDismissValue.Resting)
        }

  3. ใช้ตัวแก้ไข layout, offset และ draggable หากต้องการทำให้คอมโพเนนต์ลากได้และตรวจสอบว่าจุดยึดปรับตามขนาดของคอมโพเนนต์ ให้ใช้ตัวแก้ไขต่อไปนี้กับเนื้อหาที่ควรเคลื่อนไหวในเชิงภาพ

    • Modifier.layout: ตำแหน่ง Anchor มักจะขึ้นอยู่กับขนาดของคอมโพเนนต์ ซึ่งไม่ทราบในระหว่างการคอมโพสิชันเริ่มต้น ใช้ตัวปรับแต่ง layout เพื่อวัดคอมโพเนนต์ คำนวณแผนที่ DraggableAnchors แบบไดนามิกตามความกว้าง และอัปเดตสถานะโดยใช้ state.updateAnchors()

    • Modifier.anchoredDraggable: เชื่อมต่อกับสถานะ ส่งเดลต้าการลากในทิศทางที่ระบุ และจัดการท่าทางสัมผัสการเหวี่ยงเพื่อทำให้คอมโพเนนต์เคลื่อนไหวไปยัง Anchor ที่ใกล้ที่สุดเมื่อปล่อย เพิ่มตัวแก้ไขนี้ลงในคอมโพเนนต์ที่ควรตอบสนองต่อท่าทางการลากและส่ง state และลาก orientation (และ overscrollEffect (ไม่บังคับ))
    • Modifier.offset: อ่านออฟเซ็ตจาก state.requireOffset() ภายในแลมบ์ดาออฟเซ็ต การอ่านออฟเซ็ตภายใน Lambda นี้ช่วยให้มั่นใจได้ว่าระบบจะอ่านค่าเฉพาะในระยะการวางเท่านั้น เพื่อหลีกเลี่ยงการจัดองค์ประกอบใหม่ที่ไม่จำเป็นระหว่างการลาก ใช้ตัวแก้ไขนี้กับเนื้อหาที่ควรเคลื่อนไหวในลักษณะที่มองเห็นได้

    Box(
        modifier = Modifier
            .fillMaxWidth()
            // Read state.requireOffset() inside the offset lambda to defer reading to the placement phase
            .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
            .layout { measurable, constraints ->
                val placeable = measurable.measure(constraints)
                val anchors = DraggableAnchors {
                    AnchoredDraggableSwipeDismissValue.DismissedStart at -placeable.width.toFloat()
                    AnchoredDraggableSwipeDismissValue.Resting at 0f
                }
                state.updateAnchors(anchors)
                layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
            }
            // Pass overscrollEffect to anchoredDraggable so deltas beyond the bounds (min and max anchor) are dispatched to overscrollEffect, and render the visual effect with Modifier.overscroll.
            .overscroll(overscrollEffect)
            .anchoredDraggable(
                state,
                Orientation.Horizontal,
                overscrollEffect = overscrollEffect
            )
    ) {
        dismissContent()
    }

  4. [ไม่บังคับ] ผสานรวมการเลื่อนเกิน overscrollEffect API ใช้เอฟเฟกต์ภาพที่แสดงเมื่อเลื่อนหรือเหวี่ยงไปถึงขอบของคอนเทนเนอร์ที่เลื่อนได้ คุณสามารถแสดงภาพเพื่อตอบสนองเมื่อผู้ใช้ลากผ่านขอบเขต Anchor ที่กำหนดด้วยเอฟเฟกต์การเลื่อนเกิน (โดยปกติจะเป็นการยืดหรือเรืองแสง)

    1. Dispatch: ส่งอินสแตนซ์ overscrollEffect ไปยังตัวแก้ไข anchoredDraggable ซึ่งจะส่งเดลต้าการลากไปยังสถานะ AnchoredDraggable หรือเอฟเฟกต์การเลื่อนเกิน ขึ้นอยู่กับว่าท่าทางสัมผัสอยู่ภายในหรือภายนอกขอบเขต Anchor
    2. แสดงผล: ใช้ Modifier.overscroll กับอินสแตนซ์ OverscrollEffect เดียวกันกับคอมโพเนนต์ที่ควรแสดงผลเอฟเฟกต์เป็นภาพ

ข้อมูลโค้ดต่อไปนี้แสดงวิธีเพิ่มเอฟเฟกต์การยืดมาตรฐาน

val overscrollEffect = rememberOverscrollEffect()
Box(
    modifier.fillMaxWidth()
) {
    Box(Modifier.fillMaxWidth()) {
        background()
    }
    Box(
        modifier = Modifier
            .fillMaxWidth()
            // Read state.requireOffset() inside the offset lambda to defer reading to the placement phase
            .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
            .layout { measurable, constraints ->
                val placeable = measurable.measure(constraints)
                val anchors = DraggableAnchors {
                    AnchoredDraggableSwipeDismissValue.DismissedStart at -placeable.width.toFloat()
                    AnchoredDraggableSwipeDismissValue.Resting at 0f
                }
                state.updateAnchors(anchors)
                layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
            }
            // Pass overscrollEffect to anchoredDraggable so deltas beyond the bounds (min and max anchor) are dispatched to overscrollEffect, and render the visual effect with Modifier.overscroll.
            .overscroll(overscrollEffect)
            .anchoredDraggable(
                state,
                Orientation.Horizontal,
                overscrollEffect = overscrollEffect
            )
    ) {
        dismissContent()
    }
}

ผลลัพธ์

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

วิดีโอ 2 การติดตั้งใช้งานคอมโพเนนต์การปัดเพื่อปิดที่กำหนดเอง

ตัวอย่าง: สร้างลิ้นชักการนำทางที่กำหนดเองซึ่งทำงานด้วยท่าทางสัมผัส

ตัวอย่างนี้สาธิตวิธีสร้างลิ้นชักการนำทางที่กำหนดเองซึ่งทำงานด้วยท่าทางสัมผัส เมื่อผู้ใช้ลากในแนวนอน หน้าจอที่อยู่เบื้องหน้า (ScreenContents) จะเลื่อนไปทางขวา ลดขนาดจาก 1.0f เป็น 0.8f และปรับมุมให้โค้งมนเพื่อแสดงลิ้นชักการนำทางที่อยู่ด้านล่าง

  1. กำหนดสถานะและตำแหน่งของโฆษณา Anchor ในตัวอย่างนี้ Anchor คือสถานะเปิดและปิดของลิ้นชัก กำหนด enum สำหรับสถานะ Open และ Closed แล้วแมปกับออฟเซ็ตพิกเซลโดยใช้ DraggableAnchors ดังนี้

    enum class DrawerState {
        Open,
        Closed
    }
    
        val anchors = DraggableAnchors {
            DrawerState.Open at drawerWidth
            DrawerState.Closed at 0f
        }
        val state = remember {
            AnchoredDraggableState(
                initialValue = DrawerState.Closed,
                anchors = anchors,
            )
        }

  2. ทำให้ anchoredDraggableState เคลื่อนไหวเมื่อคลิกรายการ วาง HomeScreenDrawer ไว้ที่ด้านล่างของ Box และวาง ScreenContents ไว้ด้านบน ลิ้นชักจะอยู่กับที่ ขณะที่เลเยอร์ด้านบนจะเลื่อนและปรับขนาดอยู่ด้านบน

    เมื่อผู้ใช้เลือกรายการในลิ้นชัก ให้อัปเดตสถานะหน้าจอที่ใช้งานอยู่และปิดลิ้นชักโดยอัตโนมัติโดยใช้ state.animateTo(DrawerState.Closed) ดังนี้

    @Composable
    private fun AnchoredDraggableDrawerOnClickSample(
        modifier: Modifier = Modifier,
        state: AnchoredDraggableState<DrawerState>,
        coroutineScope: CoroutineScope
    ) {
        var screenState by remember { mutableStateOf(ScreenState.Home) }
    
        Box(modifier = modifier.fillMaxSize()) {
            HomeScreenDrawer(
                selectedScreen = screenState,
                onScreenSelected = { screen ->
                    screenState = screen
                    // Dismiss drawer when a destination is selected
                    coroutineScope.launch { state.animateTo(DrawerState.Closed) }
                }
            )
    
            // ScreenContents(...)
        }
    }

  3. ใช้ state.requireOffset() เพื่อเปลี่ยนพร็อพเพอร์ตี้ภาพหลายรายการ คุณสามารถเปลี่ยนพร็อพเพอร์ตี้หลายรายการด้วย Modifier.graphicsLayer เพื่อให้ภาพเคลื่อนไหวของการแปล ขนาด และรัศมีมุมทำงานพร้อมกันตามความคืบหน้าของการลาก แทนที่จะชดเชยเนื้อหาเพียงอย่างเดียว

    • ใช้ state.requireOffset() เพื่อรับออฟเซ็ตการลากปัจจุบัน
    • ใช้ lerp() (การประมาณค่าในช่วงเชิงเส้น) เพื่อคำนวณสัดส่วน (1f ถึง 0.8f) และรัศมีมุม (0.dp ถึง 32.dp) ที่สัมพันธ์กับออฟเซ็ตและความกว้างของลิ้นชัก
    • Modifier.graphicsLayer จะดำเนินการเปลี่ยนแปลงในระยะการวาด เพื่อให้มั่นใจว่าภาพเคลื่อนไหวจะมีประสิทธิภาพสูงโดยไม่ทำให้เกิดการจัดองค์ประกอบใหม่ระหว่างการลาก

    @Composable
    private fun AnchoredDraggableDrawerGraphicsLayerSample(
        state: AnchoredDraggableState<DrawerState>,
        drawerWidth: Float,
        selectedScreen: ScreenState,
        onDrawerClicked: () -> Unit
    ) {
        ScreenContents(
            selectedScreen = selectedScreen,
            onDrawerClicked = onDrawerClicked,
            modifier = Modifier
                .graphicsLayer {
                    this.translationX = state.requireOffset()
                    val scale = lerp(1f, 0.8f, state.requireOffset() / drawerWidth)
                    this.scaleX = scale
                    this.scaleY = scale
                    val cornerRadius = lerp(0f, 32f, state.requireOffset() / drawerWidth)
                    this.shape = RoundedCornerShape(cornerRadius.dp)
                    this.clip = true
                }
                .anchoredDraggable(state, Orientation.Horizontal)
        )
    }

  4. ทริกเกอร์ภาพเคลื่อนไหวแบบเป็นโปรแกรมด้วย animateTo ควบคุมลิ้นชัก แบบเป็นโปรแกรม (เช่น เมื่อแตะไอคอนเมนูหรือเลือกรายการลิ้นชัก ) โดยเปิด state.animateTo() ภายในขอบเขต Coroutine

    fun toggleDrawerState(
        state: AnchoredDraggableState<DrawerState>,
        coroutineScope: CoroutineScope
    ) {
        coroutineScope.launch {
            if (state.currentValue == DrawerState.Open) {
                state.animateTo(DrawerState.Closed)
            } else {
                state.animateTo(DrawerState.Open)
            }
        }
    }

ผลลัพธ์

วิดีโอ 3 หน้าจอที่อยู่เบื้องหน้า (Sleep) ถูกลากไปทางขวาเพื่อแสดงลิ้นชักการนำทางที่อยู่ด้านล่าง

ลากใน 2 มิติด้วย Modifier.draggable2D

ใช้ตัวปรับแต่ง draggable2D เพื่อย้ายองค์ประกอบ UI แต่ละรายการทั้งในแกน X และ Y พร้อมกัน

ลากองค์ประกอบที่ประกอบกันได้

ตัวอย่างนี้แสดงกรณีการใช้งานที่พบบ่อยที่สุดสำหรับ draggable2D ซึ่งช่วยให้ผู้ใช้เลือกองค์ประกอบ UI และเปลี่ยนตำแหน่งได้ทุกที่ภายในคอนเทนเนอร์หลัก

@Composable
private fun DraggableComposableElement() {
    // 1. Track the position of the floating window
    var offset by remember { mutableStateOf(Offset.Zero) }

    Box(modifier = Modifier.fillMaxSize().background(Color(0xFFF5F5F5))) {
        Box(
            modifier = Modifier
                // 2. Apply the offset to the box's position
                .offset { IntOffset(offset.x.roundToInt(), offset.y.roundToInt()) }
                // ...
                // 3. Attach the 2D drag logic
                .draggable2D(
                    state = rememberDraggable2DState { delta ->
                        // 4. Update the position based on the movement delta
                        offset += delta
                    }
                ),
            contentAlignment = Alignment.Center
        ) {
            Text("Video Preview", color = Color.White, fontSize = 12.sp)
        }
    }
}

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

ข้อมูลโค้ดก่อนหน้าประกอบด้วยข้อมูลต่อไปนี้

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

ลาก Composable ย่อยตามพื้นที่การลากของ Composable หลัก

ตัวอย่างนี้แสดงวิธีใช้ draggable2D เพื่อสร้างพื้นที่อินพุต 2 มิติที่ปุ่มเลือกถูกจำกัดให้อยู่ภายในพื้นผิวที่เฉพาะเจาะจง การใช้งานนี้ใช้เดลต้า 2 มิติเพื่อย้าย "ตัวเลือก" ที่ประกอบกันได้ย่อยในตัวเลือกสี ซึ่งแตกต่างจากตัวอย่างองค์ประกอบที่ลากได้ซึ่งจะย้ายคอมโพเนนต์เอง

@Composable
private fun ExampleColorSelector(
    // ...
)  {
    // 1. Maintain the 2D position of the selector in state.
    var selectorOffset by remember { mutableStateOf(Offset.Zero) }

    // 2. Track the size of the background container.
    var containerSize by remember { mutableStateOf(IntSize.Zero) }

    Box(
        modifier = Modifier
            .size(300.dp, 200.dp)
            // Capture the actual pixel dimensions of the container when it's laid out.
            .onSizeChanged { containerSize = it }
            .clip(RoundedCornerShape(12.dp))
            .background(
                brush = remember(hue) {
                    // Create a simple gradient representing Saturation and Value for the given Hue.
                    Brush.linearGradient(listOf(Color.White, Color.hsv(hue, 1f, 1f)))
                }
            )
    ) {
        Box(
            modifier = Modifier
                .size(24.dp)
                .graphicsLayer {
                    // Center the selector on the finger by subtracting half its size.
                    translationX = selectorOffset.x - (24.dp.toPx() / 2)
                    translationY = selectorOffset.y - (24.dp.toPx() / 2)
                }
                // ...
                // 3. Configure 2D touch dragging.
                .draggable2D(
                    state = rememberDraggable2DState { delta ->
                        // 4. Calculate the new position and clamp it to the container bounds
                        val newX = (selectorOffset.x + delta.x)
                            .coerceIn(0f, containerSize.width.toFloat())
                        val newY = (selectorOffset.y + delta.y)
                            .coerceIn(0f, containerSize.height.toFloat())

                        selectorOffset = Offset(newX, newY)
                    }
                )
        )
    }
}

วิดีโอ 5 การไล่ระดับสีพร้อมปุ่มเลือกวงกลมสีขาวที่ลากได้ทุกทิศทาง ซึ่งแสดงให้เห็นว่าเดลต้า 2 มิติถูกจำกัดให้อยู่ภายในขอบเขตของคอนเทนเนอร์เพื่ออัปเดตค่าสีที่เลือกอย่างไร

ข้อมูลโค้ดก่อนหน้าประกอบด้วยข้อมูลต่อไปนี้

  • โดยใช้ตัวแก้ไข onSizeChanged เพื่อบันทึกขนาดจริงของคอนเทนเนอร์การไล่ระดับสี ตัวเลือกจะทราบตำแหน่งของขอบอย่างแม่นยำ
  • ภายใน graphicsLayer จะปรับ translationX และ translationY เพื่อให้ตัวเลือกอยู่ตรงกลางขณะลาก