Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> ระบบ >> Android

การใช้ Tooltips ใน Jetpack Compose: คู่มือทีละขั้นตอน

เมื่อผมเขียนบทความล่าสุดเกี่ยวกับ Jetpack Compose ผมได้กล่าวว่ามันขาดบางคอมโพเนนต์พื้นฐาน (ในความคิดของผม) และหนึ่งในนั้นคือ tooltip

ในขณะนั้น ไม่มี composable built-in สำหรับแสดง tooltips และมีโซลูชันทางเลือกหลายตัวหมุนเวียนออนไลน์ ปัญหาของโซลูชันเหล่านั้นคือเมื่อ Jetpack Compose เปิดตัวเวอร์ชันใหม่ โซลูชันเหล่านั้นอาจเสียหาย จึงไม่ใช่ความคิดที่ดี และชุมชนถูกทิ้งให้หวังว่าจะมีการเพิ่มการรองรับ tooltips ในอนาคต

ผมยินดีรายงานว่าตั้งแต่ version 1.1.0 ของ Compose Material 3 เรามีการรองรับ tooltip แบบ built-in แล้ว 👏

แม้สิ่งนี้จะดีแล้ว แต่มาเกิน 1 ปีตั้งแต่เวอร์ชันนั้นออกมา และกับเวอร์ชันต่อๆ มา API ที่เกี่ยวข้องกับ tooltips เปลี่ยนแปลงไปอย่างมากด้วย

หากคุณอ่าน changelog จะเห็นว่า public และ internal APIs เปลี่ยนไปอย่างไร จงจำไว้ว่าเมื่อคุณอ่านบทความนี้ สิ่งต่างๆ อาจยังคงเปลี่ยนแปลง เพราะทุกอย่างที่เกี่ยวกับ Tooltips ยังถูกทำเครื่องหมายด้วย annotation ExperimentalMaterial3Api::class

❗️ เวอร์ชัน Material 3 ที่ใช้ในบทความนี้คือ 1.2.1 ซึ่งเผยแพร่เมื่อวันที่ 6 มีนาคม 2024

ตอนนี้เรามีการรองรับ tooltip 2 ประเภท:

  1. Plain tooltip

  2. Rich media tooltip

Plain Tooltip

คุณสามารถใช้ประเภทแรกเพื่อให้ข้อมูลเกี่ยวกับ icon button ที่ไม่ชัดเจน เช่น ใช้ plain tooltip เพื่อบอกผู้ใช้ว่า icon button นั้นแทนอะไร

การใช้ Tooltips ใน Jetpack Compose: คู่มือทีละขั้นตอน

เพื่อเพิ่ม tooltip ให้แอปพลิเคชัน คุณใช้ composable TooltipBox composable นี้รับอาร์กิวเมนต์หลายอย่าง:

fun TooltipBox(
    positionProvider: PopupPositionProvider,
    tooltip: @Composable TooltipScope.() -> Unit,
    state: TooltipState,
    modifier: Modifier = Modifier,
    focusable: Boolean = true,
    enableUserInput: Boolean = true,
    content: @Composable () -> Unit,
)

บางอย่างควรคุ้นเคยหากเคยใช้ Composables มาแล้ว ผมจะเน้นเฉพาะที่มี use case เฉพาะที่นี่:

  • positionProvider - ประเภท PopupPositionProvider ใช้คำนวณตำแหน่งของ tooltip

  • tooltip - นี่คือที่ที่คุณออกแบบ UI ว่า tooltip จะมีลักษณะอย่างไร

  • state - เก็บ state ที่เชื่อมโยงกับ Tooltip instance เฉพาะ มัน expose methods เช่น showing/dismissing tooltip และเมื่อสร้าง instance คุณสามารถประกาศว่า tooltip ควรเป็น persistent หรือไม่ (คือควรแสดงอยู่บนจอจนกว่าผู้ใช้จะคลิกนอก tooltip)

  • content - นี่คือ UI ที่ tooltip จะแสดงเหนือ/ใต้

นี่คือตัวอย่างการสร้าง BasicTooltipBox พร้อมอาร์กิวเมนต์ที่เกี่ยวข้องทั้งหมด:

@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun BasicTooltip() {
    val tooltipPosition = TooltipDefaults.rememberPlainTooltipPositionProvider()
    val tooltipState = rememberBasicTooltipState(isPersistent = false)
    BasicTooltipBox(
        positionProvider = tooltipPosition,
        tooltip = { Text("Hello World") },
        state = tooltipState
    ) {
        IconButton(onClick = { }) {
            Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Your icon's description")
        }
    }
}

การใช้ Tooltips ใน Jetpack Compose: คู่มือทีละขั้นตอน

Jetpack Compose มี class built-in ชื่อ TooltipDefaults คุณสามารถใช้ class นี้ช่วยสร้างอาร์กิวเมนต์ที่ประกอบ TooltipBox ตัวอย่างเช่น คุณสามารถใช้ TooltipDefaults.rememberPlainTooltipPositionProvider เพื่อจัดตำแหน่ง tooltip ให้ถูกต้องเกี่ยวกับ anchor element

Rich Tooltip

Rich media tooltip ใช้พื้นที่มากกว่า plain tooltip และสามารถใช้ให้บริบทเพิ่มเติมเกี่ยวกับฟังก์ชันของ icon button เมื่อ tooltip แสดง คุณสามารถเพิ่มปุ่มและลิงก์เข้าไปเพื่อให้คำอธิบายหรือนิยามเพิ่มเติม

มัน instantiate แบบคล้าย plain tooltip ภายใน TooltipBox แต่คุณใช้ composable RichTooltip

TooltipBox(
    positionProvider = tooltipPosition,
    tooltip = {
        RichTooltip(
            title = { Text("RichTooltip") },
            caretSize = caretSize,
            action = {
                TextButton(onClick = { scope.launch { 
                    tooltipState.dismiss() 
                    tooltipState.onDispose() 
                }}) {
                    Text("Dismiss")
                }
            }
        ) {
            Text("This is where a description would go.")
        }
    },
    state = tooltipState
) {
    IconButton(onClick = { /* Icon button's click event */ }) {
        Icon(imageVector = tooltipIcon, contentDescription = "Your icon's description", tint = iconColor)
    }
}

บางอย่างที่สังเกตได้เกี่ยวกับ Rich tooltip:

  1. Rich tooltip รองรับ caret (สามเหลี่ยมเล็กที่ชี้ไปที่ anchor)

  2. คุณสามารถเพิ่ม action (คือปุ่ม) ให้ tooltip เพื่อให้ผู้ใช้มีตัวเลือกหาข้อมูลเพิ่ม

  3. คุณสามารถเพิ่มตรรกะเพื่อ dismiss tooltip

การใช้ Tooltips ใน Jetpack Compose: คู่มือทีละขั้นตอน

การใช้ Tooltips ใน Jetpack Compose: คู่มือทีละขั้นตอน

Edge Cases

เมื่อคุณเลือกทำเครื่องหมาย tooltip state ว่า persistent หมายความว่าหากผู้ใช้โต้ตอบกับ UI ที่แสดง tooltip มันจะคงแสดงอยู่จนกว่าผู้ใช้จะกดที่อื่นบนหน้าจอ

หากคุณดูตัวอย่าง Rich tooltip ด้านบน คุณอาจสังเกตว่าเราเพิ่มปุ่มเพื่อ dismiss tooltip เมื่อถูกคลิก

มีปัญหาที่เกิดขึ้นเมื่อผู้ใช้กดปุ่มนั้น: เนื่องจาก dismiss action ทำงานบน tooltip หากผู้ใช้อยาก long press อีกครั้งบน UI item ที่เรียก tooltip นี้ tooltip จะไม่แสดงอีก นั่นหมายถึง state ของ tooltip persistent อยู่ที่ dismissed แล้ว ดังนั้นเราแก้ยังไง?

การใช้ Tooltips ใน Jetpack Compose: คู่มือทีละขั้นตอน

เพื่อ "รีเซ็ต" state ของ tooltip เราต้องเรียก method onDispose ที่ expose ผ่าน tooltip state เมื่อเราทำอย่างนั้น tooltip state จะรีเซ็ต และ tooltip จะแสดงอีกครั้งเมื่อผู้ใช้ long press บน UI item

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RichTooltip() {
    val tooltipPosition = TooltipDefaults.rememberRichTooltipPositionProvider()
    val tooltipState = rememberTooltipState(isPersistent = true)
    val scope = rememberCoroutineScope()
    TooltipBox(
        positionProvider = tooltipPosition,
        tooltip = {
            RichTooltip(
                title = { Text("RichTooltip") },
                caretSize = TooltipDefaults.caretSize,
                action = {
                    TextButton(onClick = { scope.launch {
                        tooltipState.dismiss()
                        tooltipState.onDispose() /// <---- HERE
                    }}) {
                        Text("Dismiss")
                    }
                }
            ) {
            }
        },
        state = tooltipState
    ) {
        IconButton(onClick = { }) {
            Icon(imageVector = Icons.Filled.Call, contentDescription = "Your icon's description")
        }
    }
}

การใช้ Tooltips ใน Jetpack Compose: คู่มือทีละขั้นตอน

อีกสถานการณ์หนึ่งที่ tooltip state ไม่รีเซ็ต คือ ถ้าผู้ใช้คลิกนอก tooltip แทนที่จะกดปุ่ม dismiss เอง นี่เรียก dismiss method ใต้หลอด และ tooltip state ตั้งค่าเป็น dismissed การ long press บน UI element เพื่อดู tooltip อีกครั้งจะไม่ได้ผล

การใช้ Tooltips ใน Jetpack Compose: คู่มือทีละขั้นตอน

ตรรกะของเราที่เรียก onDispose ของ tooltip ไม่ได้ถูกทริกเกอร์ ดังนั้นเราจะรีเซ็ต state ของ tooltip ยังไง?

ตอนนี้ผมยังหาวิธีไม่ได้ อาจเกี่ยวกับ Mutex ของ tooltip MutatorMutex บางทีใน releases ใกล้หน้าจะมี API สำหรับนี้ ผมสังเกตว่าถ้ามี tooltips อื่นบนหน้าจอและถูกกด จะรีเซ็ต tooltip ที่กดไปก่อนหน้านี้

การใช้ Tooltips ใน Jetpack Compose: คู่มือทีละขั้นตอน

หากคุณอยากดูโค้ดที่นำเสนอที่นี่ ไปที่ GitHub repository นี้

หากคุณอยากดู tooltips ในแอปพลิเคชัน ตรวจสอบที่นี่

References

  • Material3 Tooltip Overview

  • Tooltip Defaults

  • Tooltip Source Code

เรียนรู้การเขียนโค้ดฟรี หลักสูตรโอเพนซอร์สของ freeCodeCamp ช่วยให้ผู้คนมากกว่า 40,000 คนได้งานเป็นนักพัฒนา เริ่มต้นเลยวันนี้