รายการช่วยให้ผู้ใช้เลือกรายการจากชุดตัวเลือกในอุปกรณ์ Wear OS ได้
อุปกรณ์ Wear OS หลายรุ่นใช้หน้าจอทรงกลม ซึ่งทำให้ดูรายการที่ปรากฏใกล้กับด้านบนและด้านล่างของหน้าจอได้ยากขึ้น
ด้วยเหตุนี้
Compose สำหรับ Wear OS จึงมีLazyColumnเวอร์ชันหนึ่งที่เรียกว่า
TransformingLazyColumn ซึ่งรองรับการปรับขนาดและภาพเคลื่อนไหวแบบมอร์ฟ
เมื่อรายการเลื่อนไปที่ขอบ รายการจะเล็กลงและจางหายไป
วิธีใช้เอฟเฟกต์การปรับขนาดและการเลื่อนที่แนะนำ
- ใช้
Modifier.transformedHeightเพื่อให้ Compose คำนวณการเปลี่ยนแปลงความสูงขณะที่รายการเลื่อนผ่าน หน้าจอ - ใช้
transformation = SurfaceTransformation(transformationSpec)เพื่อใช้ เอฟเฟกต์ภาพ ซึ่งรวมถึงการลดขนาดเนื้อหาของไอเทม - ใช้
TransformationSpecที่กำหนดเองสำหรับคอมโพเนนต์ที่ไม่ใช้transformationเป็นพารามิเตอร์ เช่นText
ภาพเคลื่อนไหวต่อไปนี้แสดงวิธีที่องค์ประกอบรายการปรับขนาดและเปลี่ยนรูปร่าง เมื่อเข้าใกล้ด้านบนและด้านล่างของหน้าจอ
ข้อมูลโค้ดต่อไปนี้แสดงวิธีสร้างรายการโดยใช้เลย์เอาต์TransformingLazyColumn เพื่อสร้างเนื้อหาที่
ดูดีในขนาดหน้าจอ Wear OS ที่หลากหลาย
ข้อมูลโค้ดยังแสดงให้เห็นการใช้ตัวแก้ไข
minimumVerticalContentPadding ซึ่งคุณควรตั้งค่าในรายการ
เพื่อใช้ระยะห่างที่ถูกต้องที่ด้านบนและด้านล่างของรายการ
หากต้องการแสดงตัวบ่งชี้การเลื่อน ให้แชร์ columnState ระหว่าง
ScreenScaffold กับ TransformingLazyColumn โดยทำดังนี้
val columnState = rememberTransformingLazyColumnState() val transformationSpec = rememberTransformationSpec() ScreenScaffold( scrollState = columnState ) { contentPadding -> TransformingLazyColumn( state = columnState, contentPadding = contentPadding ) { item { ListHeader( modifier = Modifier .fillMaxWidth() .transformedHeight(this, transformationSpec) .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding), transformation = SurfaceTransformation(transformationSpec) ) { Text(text = "Header") } } // ... other items item { Button( modifier = Modifier .fillMaxWidth() .transformedHeight(this, transformationSpec) .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding), transformation = SurfaceTransformation(transformationSpec), onClick = { /* ... */ }, icon = { Icon( imageVector = Icons.Default.Build, contentDescription = "build", ) }, ) { Text( text = "Build", maxLines = 1, overflow = TextOverflow.Ellipsis, ) } } } }
เพิ่มเอฟเฟกต์ดีดนิ้ว
การสแนปช่วยให้มั่นใจได้ว่าเมื่อผู้ใช้เลื่อนหรือปัดจนสุด รายการจะ หยุดลงโดยมีรายการที่วางไว้ตรงจุดที่เฉพาะเจาะจง ซึ่งโดยปกติคือ กึ่งกลางของหน้าจอ ในหน้าจอทรงกลมซึ่งรายการจะปรับขนาดและเปลี่ยนรูปร่างเมื่อเลื่อนออกจากตรงกลาง การสแนปจะมีประโยชน์อย่างยิ่งในการตรวจสอบว่ารายการที่เกี่ยวข้องมากที่สุดยังคงมองเห็นและอ่านได้อย่างเต็มที่ในพื้นที่การดูที่เหมาะสมหากต้องการเพิ่มลักษณะการทำงานแบบดีดและปัด ให้ตั้งค่าพารามิเตอร์ flingBehavior เป็น
TransformingLazyColumnDefaults.snapFlingBehavior(columnState)
ตั้งค่า rotaryScrollableBehavior ให้ตรงกันโดยใช้
RotaryScrollableDefaults.snapBehavior(columnState) เพื่อให้ได้รับประสบการณ์การใช้งานที่สอดคล้องกัน
เมื่อใช้เม็ดมะยมหรือขอบหน้าปัดจริง
val columnState = rememberTransformingLazyColumnState() ScreenScaffold(scrollState = columnState) { contentPadding -> TransformingLazyColumn( state = columnState, flingBehavior = TransformingLazyColumnDefaults.snapFlingBehavior(columnState), rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(columnState) ) { // ... // ... } }
เลย์เอาต์ย้อนกลับ
โดยค่าเริ่มต้น รายการที่เลื่อนได้จะยึดกับขอบด้านบน หากผู้ใช้เลื่อนไปที่ด้านล่างของรายการมาตรฐานและมีการเพิ่มรายการใหม่ที่ส่วนท้าย รายการจะยังคงมุมมองของผู้ใช้ในรายการปัจจุบันไว้ เช่น หากผู้ใช้กำลังดูรายการที่ 10 ที่ด้านล่างของหน้าจอ และมีการเพิ่มรายการที่ 11 มุมมองจะยังคงโฟกัสที่รายการที่ 10 และรายการที่ 11 จะปรากฏนอกหน้าจอใต้มุมมองปัจจุบัน
สำหรับกรณีการใช้งาน เช่น แอปพลิเคชันการรับส่งข้อความหรือบันทึกแบบเรียลไทม์ โดยปกติแล้วจะไม่ต้องการลักษณะการทำงานนี้ เมื่อมีรายการใหม่เข้ามา ผู้ใช้มักต้องการดูเนื้อหาล่าสุดทันทีหากเลื่อนไปถึงด้านล่างของรายการแล้ว หากมีรายการจำนวนมาก เข้ามาพร้อมกัน รายการควรข้ามไปแสดงรายการล่าสุดที่ด้านล่าง (ซึ่งหมายความว่าอาจไม่มีการแสดงรายการบางรายการเลย เว้นแต่ผู้ใช้จะเลื่อนกลับขึ้นไป)
TransformingLazyColumn ช่วยให้คุณกลับเลย์เอาต์ได้โดยการตั้งค่า reverseLayout = true เพื่อรองรับกรณีการใช้งานเหล่านี้ ซึ่งจะเปลี่ยนจุดยึดของรายการจากขอบด้านบนเป็นขอบด้านล่าง
เพื่อความสะดวก การตั้งค่า reverseLayout = true จะกลับลำดับภาพ
ของรายการและทิศทางของท่าทางการเลื่อนด้วย
- โดยรายการจะเรียงจากล่างขึ้นบน ซึ่งหมายความว่าดัชนี 0 จะปรากฏที่ด้านล่างของหน้าจอ
- การเลื่อนขึ้นจะแสดงรายการที่มีดัชนีสูงกว่า
หากต้องการเพิ่มลักษณะการทำงานแบบดีดและปัดพร้อมกับเลย์เอาต์ย้อนกลับ คุณสามารถรวม
flingBehavior และ rotaryScrollableBehavior ดังที่แสดงใน
ข้อมูลโค้ดต่อไปนี้
val columnState = rememberTransformingLazyColumnState() val transformationSpec = rememberTransformationSpec() ScreenScaffold(scrollState = columnState) { contentPadding -> TransformingLazyColumn( state = columnState, contentPadding = contentPadding, reverseLayout = true, modifier = Modifier.fillMaxWidth() ) { items(10) { index -> Button( label = { Text( text = "Item ${index + 1}" ) }, onClick = {}, modifier = Modifier .fillMaxWidth() .transformedHeight(this, transformationSpec) .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding), transformation = SurfaceTransformation(transformationSpec) ) } item { // With reverseLayout = true, the last item declared appears at the top. ListHeader( modifier = Modifier .fillMaxWidth() .transformedHeight(this, transformationSpec) .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding), transformation = SurfaceTransformation(transformationSpec) ) { Text("Header") } } } }
รูปภาพต่อไปนี้แสดงความแตกต่างระหว่างรายการปกติกับรายการที่กลับด้าน
ปุ่มขอบในรายการ
สำหรับ Material 3 คุณสามารถเพิ่ม EdgeButton ซึ่งเป็นปุ่มที่อยู่ขอบที่ด้านล่างของรายการได้
อย่างไรก็ตาม โปรดระมัดระวังอย่าเพิ่มเป็นรายการภายใน TransformingLazyColumn แต่ให้ใช้ช่อง edgeButton ใน ScreenScaffold แทน
การใช้ช่อง edgeButton จะช่วยให้มั่นใจว่าปุ่มจะอยู่ในตำแหน่งที่ถูกต้องที่ด้านล่างของหน้าจอและทำงานได้อย่างเหมาะสมเมื่อเลื่อนรายการ
ข้อมูลโค้ดต่อไปนี้แสดงวิธีใช้ EdgeButton
val columnState = rememberTransformingLazyColumnState() ScreenScaffold( scrollState = columnState, edgeButton = { EdgeButton( onClick = { /* TODO */ }, modifier = Modifier.scrollable( columnState, orientation = Orientation.Vertical, reverseDirection = true, // Apply overscroll to the EdgeButton for proper scrolling behavior. overscrollEffect = rememberOverscrollEffect(), ) ) { Text("More") } } ) { contentPadding -> TransformingLazyColumn( contentPadding = contentPadding, state = columnState, ) { // ... // ... } }
ปัดเพื่อแสดงในรายการ
คอมโพเนนต์ SwipeToReveal ช่วยให้คุณเข้าถึงการดำเนินการสำหรับรายการในลิสต์ เช่น Card หรือ Chip ได้โดยการปัด โดยปกติการปัดจะแสดงปุ่มการดำเนินการ 1 หรือ 2 ปุ่ม (เช่น "ลบ" หรือ "เพิ่มเติม") จากด้านข้าง
เมื่อใช้ SwipeToReveal ภายใน TransformingLazyColumn ให้ทำตามหลักเกณฑ์ต่อไปนี้
- รีเซ็ตเมื่อเลื่อน: เมื่อผู้ใช้เลื่อนรายการ ให้รีเซ็ตรายการที่ปัดเปิด ไปแล้วให้กลับสู่สถานะที่ปิด
- ความสูงที่สอดคล้องกัน: ตั้งค่าความสูงของปุ่มการดำเนินการให้ตรงกับรายการที่ปัดด้านใน (ไม่ว่าจะเป็น
ButtonหรือCard) เพื่อให้ดูสอดคล้องกัน - เปลี่ยนรูปแบบคอนเทนเนอร์: ใช้ตัวแก้ไข
transformedHeightและtransformationSpecกับคอมโพเนนต์SwipeToRevealเอง - อย่าเปลี่ยนรูปแบบซ้ำ: อย่าใช้ตัวแก้ไข
transformedHeightหรือtransformationกับรายการที่ปัดด้านใน (การ์ดหรือปุ่ม ภายในคอนเทนเนอร์SwipeToReveal)
Composable ที่กำหนดเองในรายการ
เมื่อสร้างคอมโพเนนต์ Surface ที่กำหนดเองสำหรับ TransformingLazyColumn ให้ทำตาม
แนวทางปฏิบัติแนะนำเหล่านี้เพื่อให้คอมโพเนนต์ที่ประกอบได้ปรับขนาด จาง และเปลี่ยนรูปร่างได้อย่างราบรื่นใกล้กับ
ขอบจอแสดงผล
- Expose
SurfaceTransformation: ยอมรับพารามิเตอร์SurfaceTransformationที่ไม่บังคับ (ค่าเริ่มต้นคือnull) ซึ่งตรงกับ คอมโพเนนต์ Material 3 ของ Wear Compose มาตรฐาน เช่นCardและButtonซึ่งจะช่วยให้ผู้โทรผ่านTransformingLazyColumnSurfaceTransformation(transformationSpec)ได้ ขณะที่คอมโพเนนต์ทำงาน ตามปกติเมื่ออยู่นอกรายการ - ใช้
Modifier.transformedHeightเป็นอันดับแรกในโค้ดการเรียก: เมื่อวาง Composable ที่กำหนดเองในTransformingLazyColumnให้ส่งModifier.transformedHeight(this, transformationSpec)เป็นตัวแก้ไขแรก ในเชนตัวแก้ไขของโค้ดการเรียก ขณะที่SurfaceTransformationใช้ เอฟเฟกต์การปรับขนาดและการจางภาพtransformedHeightก็มีความสำคัญต่อ การบอกเลย์เอาต์รายการให้คำนวณความสูงของรายการใหม่เมื่อรายการเล็กลง - ใช้เลเยอร์การเปลี่ยนรูปแบบ, ผู้โทร
modifierและจิตรกรตามลำดับ- เลเยอร์การเปลี่ยนรูปแบบคอนเทนเนอร์: เริ่มห่วงโซ่ตัวแก้ไขของคอนเทนเนอร์รูทด้วย
Modifier.graphicsLayerและapplyContainerTransformation()เพื่อให้ทั้งพื้นหลังและ เนื้อหาแสดงภายในพื้นที่พิกัดที่ปรับขนาดและเอียง - ผู้โทร
modifier: ใช้พารามิเตอร์modifierที่ส่งโดยผู้โทร (ซึ่งรวมถึงModifier.transformedHeight) ถัดไป ก่อนการปรับขนาดหรือการเว้นวรรคภายใน - ตัดรูปร่างเมื่อไม่มีการเปลี่ยนรูปแบบ: หาก
transformationเป็นnullให้ใช้Modifier.clip(shape)ก่อนวาดพื้นหลังcreateContainerPainter()ที่ส่งคืนโดย Painter จะตัดตัวเองให้พอดีกับรูปร่าง แต่ Painter ธรรมดาจะไม่ทำเช่นนั้น ดังนั้นหากไม่มีการตั้งค่านี้ ระบบจะวาดพื้นหลังเป็นมุมสี่เหลี่ยมจัตุรัสนอกรายการ - เครื่องมือวาดภาพพื้นหลังที่เปลี่ยนรูป: วาดพื้นหลังภายในเลเยอร์คอนเทนเนอร์โดยใช้
Modifier.drawBehindและเครื่องมือวาดภาพที่สร้างจากcreateContainerPainter() - เลเยอร์การเปลี่ยนรูปแบบเนื้อหา: ใช้
Modifier.graphicsLayerที่ 2 กับapplyContentTransformation()และคลิป กับรูปร่างคอนเทนเนอร์เพื่อให้เนื้อหาด้านในจางหายไปก่อนหน้านี้เมื่อเข้าใกล้ขอบ
- เลเยอร์การเปลี่ยนรูปแบบคอนเทนเนอร์: เริ่มห่วงโซ่ตัวแก้ไขของคอนเทนเนอร์รูทด้วย
ข้อมูลโค้ดต่อไปนี้แสดงวิธีใช้ Composable ของ BoardingPassCard
ที่กำหนดเองซึ่งใช้การเปลี่ยนรูปแบบเหล่านี้ตามลำดับ
@Composable fun BoardingPassCard( flightNumber: String, origin: String, destination: String, gate: String, seat: String, departureTime: String, modifier: Modifier = Modifier, transformation: SurfaceTransformation? = null, shape: Shape = RoundedCornerShape(18.dp), statusBadge: @Composable () -> Unit = {} ) { // 1. Create morphing container painter val backgroundPainter = ColorPainter(MaterialTheme.colorScheme.surfaceContainer) val finalPainter = if (transformation != null) { remember(transformation, backgroundPainter, shape) { transformation.createContainerPainter(backgroundPainter, shape, border = null) } } else { backgroundPainter } Column( modifier = Modifier // 2a. Container layer: Scales, fades, and tilts the whole card surface .then( if (transformation != null) { Modifier.graphicsLayer { transformation.run { applyContainerTransformation() } } } else Modifier ) // 2b. Caller modifier: Includes Modifier.transformedHeight in a list .then(modifier) .fillMaxWidth() // 2c. Shape clip: Only needed without a transformation, because the // painter from createContainerPainter clips itself to the shape .then(if (transformation == null) Modifier.clip(shape) else Modifier) // 2d. Morphing background: Drawn inside the transformed container layer .drawBehind { with(finalPainter) { draw(size) } } // 2e. Content layer: Fades content earlier and clips children to shape .then( if (transformation != null) { Modifier.graphicsLayer { this.shape = shape this.clip = true transformation.run { applyContentTransformation() } } } else Modifier ) .padding(horizontal = 14.dp, vertical = 10.dp) ) { // Card content goes here } }
จากนั้นคุณจะใช้ BoardingPassCard ภายใน TransformingLazyColumn ได้โดยส่ง
Modifier.transformedHeight เป็นตัวแก้ไขแรกพร้อมกับ
SurfaceTransformation(transformationSpec)
@Composable fun BoardingPassListSample(flights: List<FlightInfo>) { val listState = rememberTransformingLazyColumnState() val transformationSpec = rememberTransformationSpec() ScreenScaffold(scrollState = listState) { contentPadding -> TransformingLazyColumn( state = listState, contentPadding = contentPadding, modifier = Modifier.fillMaxSize() ) { items(flights.size) { index -> val flight = flights[index] BoardingPassCard( flightNumber = flight.number, origin = flight.origin, destination = flight.destination, gate = flight.gate, seat = flight.seat, departureTime = flight.time, modifier = Modifier .transformedHeight(this, transformationSpec) .minimumVerticalContentPadding( CardDefaults.minimumVerticalListContentPadding ), transformation = SurfaceTransformation(transformationSpec) ) } } } }
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- Codelab Compose สำหรับ Wear OS
- รายการและตารางกริด
- การใช้ View ใน Compose