ค่าเริ่มต้นของ API

Material, Compose UI และ Foundation API จะใช้และเสนอแนวทางปฏิบัติที่เข้าถึงได้หลายอย่างโดยค่าเริ่มต้น ซึ่งมีอรรถศาสตร์ในตัวที่เป็นไปตามบทบาทและฟังก์ชันเฉพาะของตนเอง ซึ่งหมายความว่าการรองรับการช่วยเหลือพิเศษส่วนใหญ่จะพร้อมใช้งานโดยไม่ต้องดำเนินการเพิ่มเติมหรือดำเนินการเพียงเล็กน้อย

การใช้ API ที่เหมาะสมเพื่อวัตถุประสงค์ที่เหมาะสมหมายความว่าคอมโพเนนต์มักจะมาพร้อมกับลักษณะการทำงานด้านการช่วยเหลือพิเศษที่กำหนดไว้ล่วงหน้าซึ่งครอบคลุมกรณีการใช้งานมาตรฐาน อย่างไรก็ตาม โปรดตรวจสอบอีกครั้งเสมอว่าค่าเริ่มต้นเหล่านี้ตรงกับความต้องการด้านการช่วยเหลือพิเศษของคุณหรือไม่ หากไม่ Compose จะมีวิธีที่ครอบคลุมข้อกำหนดที่เฉพาะเจาะจงมากขึ้น

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

ขนาดเป้าหมายการสัมผัสขั้นต่ำ

องค์ประกอบบนหน้าจอที่ผู้ใช้สามารถคลิก แตะ หรือโต้ตอบด้วยได้ต้องมีขนาดใหญ่พอสำหรับการโต้ตอบที่เชื่อถือได้ เมื่อปรับขนาดองค์ประกอบเหล่านี้ ให้ตั้งค่าขนาดขั้นต่ำเป็น 48dp เพื่อให้เป็นไปตามหลักเกณฑ์การช่วยเหลือพิเศษของ Material Design อย่างถูกต้อง

คอมโพเนนต์ Material เช่น Checkbox, RadioButton, Switch, Slider และ Surface จะตั้งค่าขนาดขั้นต่ำนี้ภายใน แต่จะตั้งค่าก็ต่อเมื่อคอมโพเนนต์รับการดำเนินการของผู้ใช้ได้เท่านั้น ตัวอย่างเช่น เมื่อ Checkbox มีการตั้งค่าพารามิเตอร์ onCheckedChange เป็นค่าที่ไม่ใช่ Null ช่องทำเครื่องหมายจะมีระยะห่างจากขอบเพื่อให้มีความกว้างและความสูงอย่างน้อย 48 dp

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

ช่องทำเครื่องหมายที่มีระยะห่างจากขอบเริ่มต้นซึ่งมีความกว้างและความสูง 48 dp
รูปที่ 1 ช่องทำเครื่องหมายที่มีการตั้งค่าระยะห่างจากขอบเริ่มต้น

เมื่อตั้งค่าพารามิเตอร์ onCheckedChange เป็น Null ระบบจะไม่รวมระยะห่างจากขอบ เนื่องจากไม่สามารถโต้ตอบกับคอมโพเนนต์ได้โดยตรง

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

ช่องทำเครื่องหมายที่ไม่มีระยะห่างจากขอบ
รูปที่ 2 ช่องทำเครื่องหมายที่ไม่มีระยะห่างจากขอบ

เมื่อใช้การควบคุมการเลือก เช่น Switch, RadioButton หรือ Checkbox โดยปกติแล้วคุณจะยกเลิกการทำงานที่คลิกได้ไปยังคอนเทนเนอร์ระดับบนโดยตั้งค่า Callback การคลิกใน Composable เป็น null และเพิ่มตัวแก้ไข toggleable หรือ selectable ลงใน Composable ระดับบน

@Composable
private fun CheckableRow() {
    MaterialTheme {
        var checked by remember { mutableStateOf(false) }
        Row(
            Modifier
                .toggleable(
                    value = checked,
                    role = Role.Checkbox,
                    onValueChange = { checked = !checked }
                )
                .padding(16.dp)
                .fillMaxWidth()
        ) {
            Text("Option", Modifier.weight(1f))
            Checkbox(checked = checked, onCheckedChange = null)
        }
    }
}

ช่องทำเครื่องหมายข้างข้อความ "ตัวเลือก" ที่เลือกและยกเลิกการเลือกอยู่
รูปที่ 3 ช่องทำเครื่องหมายที่มีลักษณะการทำงานแบบคลิกได้

เมื่อขนาดของ Composable ที่คลิกได้เล็กกว่าขนาดเป้าหมายการสัมผัสขั้นต่ำ Compose จะยังคงเพิ่มขนาดเป้าหมายการสัมผัส โดยการขยายขนาดเป้าหมายการสัมผัสออกไปนอกขอบเขตของ Composable

ตัวอย่างต่อไปนี้มี Box ที่คลิกได้ขนาดเล็กมาก พื้นที่เป้าหมายการสัมผัสจะขยายออกไปนอกขอบเขตของ Box โดยอัตโนมัติ ดังนั้นการแตะข้าง Box จะยังคงทริกเกอร์กิจกรรมการคลิก

@Composable
private fun SmallBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .size(1.dp)
        )
    }
}

ช่องที่คลิกได้ขนาดเล็กมากซึ่งขยายเป็นเป้าหมายการสัมผัสขนาดใหญ่ขึ้นโดยการแตะข้างช่อง
รูปที่ 4 ช่องที่คลิกได้ขนาดเล็กมากซึ่งขยายเป็นเป้าหมายการสัมผัสขนาดใหญ่ขึ้น

เพื่อป้องกันไม่ให้พื้นที่การสัมผัสของ Composable ต่างๆ ทับซ้อนกัน ให้ใช้ขนาดขั้นต่ำที่ใหญ่พอสำหรับ Composable เสมอ ในตัวอย่างนี้ หมายความว่าคุณต้องใช้ตัวปรับแต่ง sizeIn เพื่อกำหนดขนาดขั้นต่ำสำหรับกล่องด้านใน ดังนี้

@Composable
private fun LargeBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .sizeIn(minWidth = 48.dp, minHeight = 48.dp)
        )
    }
}

กล่องขนาดเล็กมากจากตัวอย่างก่อนหน้าได้รับการเพิ่มขนาดเพื่อสร้างเป้าหมายการสัมผัสที่ใหญ่ขึ้น
รูปที่ 5 เป้าหมายการสัมผัสกล่องที่ใหญ่ขึ้น

หากองค์ประกอบต้องคงขนาดที่มองเห็นได้ให้เล็กลง ให้ใช้ตัวแก้ไข minimumInteractiveComponentSize จาก Material 3 แทน โดยจะสงวนพื้นที่เลย์เอาต์ไว้อย่างน้อย 48 dp ในแต่ละมิติข้อมูลและจัดองค์ประกอบให้อยู่กึ่งกลางในพื้นที่นั้น เพื่อให้องค์ประกอบคงขนาดที่วาดไว้และองค์ประกอบรอบๆ ย้ายเพื่อสร้างพื้นที่ว่าง ตัวแก้ไขจะไม่ขยายเป้าหมายการสัมผัสเอง แต่จะเว้นที่ว่างในเลย์เอาต์สำหรับการขยายตามที่อธิบายไว้ก่อนหน้านี้ คอมโพเนนต์ Material เช่น Checkbox ใช้ตัวแก้ไขนี้ภายใน โดยให้วางไว้ก่อนตัวปรับแต่งขนาด เนื่องจากตัวปรับแต่งขนาดที่อยู่ก่อนหน้าจะจำกัดพื้นที่ที่สามารถจองได้

@Composable
private fun MinimumInteractiveBox() {
    var clicked by remember { mutableStateOf(false) }
    Row(verticalAlignment = Alignment.CenterVertically) {
        Box(
            Modifier
                .clickable { clicked = !clicked }
                .minimumInteractiveComponentSize()
                .size(24.dp)
                .background(if (clicked) Color.DarkGray else Color.Black)
        )
        Text("Option")
    }
}

องค์ประกอบกราฟิก

เมื่อคุณกำหนด Composable Image หรือ Icon จะไม่มีวิธีอัตโนมัติที่เฟรมเวิร์ก Android จะเข้าใจสิ่งที่แอปแสดง คุณต้องส่งคำอธิบายแบบข้อความขององค์ประกอบกราฟิก

ลองนึกภาพหน้าจอที่ผู้ใช้สามารถแชร์หน้าปัจจุบันกับเพื่อนๆ ได้ หน้าจอนี้มีไอคอนแชร์ที่คลิกได้

แถบไอคอนที่คลิกได้ 4 รายการ โดยไฮไลต์ไอคอน "แชร์"
รูปที่ 6 แถวของไอคอนที่คลิกได้ซึ่งมีไอคอน "แชร์" ที่เลือกไว้

เฟรมเวิร์ก Android ไม่สามารถอธิบายไอคอนเพียงอย่างเดียวให้ผู้ใช้ที่มีความบกพร่องทางสายตาเข้าใจได้ เฟรมเวิร์ก Android ต้องมีคำอธิบายข้อความเพิ่มเติมของไอคอน

พารามิเตอร์ contentDescription อธิบายองค์ประกอบกราฟิก ใช้สตริงที่แปลแล้ว เนื่องจากผู้ใช้จะเห็นสตริงนี้

@Composable
private fun ShareButton(onClick: () -> Unit) {
    IconButton(onClick = onClick) {
        Icon(
            imageVector = Icons.Filled.Share,
            contentDescription = stringResource(R.string.label_share)
        )
    }
}

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

@Composable
private fun PostImage(post: Post, modifier: Modifier = Modifier) {
    val image = post.imageThumb ?: painterResource(R.drawable.placeholder_1_1)

    Image(
        painter = image,
        // Specify that this image has no semantic meaning
        contentDescription = null,
        modifier = modifier
            .size(40.dp, 40.dp)
            .clip(MaterialTheme.shapes.small)
    )
}

contentDescription มีไว้เพื่อใช้กับองค์ประกอบกราฟิกเป็นหลัก เช่น รูปภาพ คอมโพเนนต์ Material เช่น Button หรือ Text และลักษณะการทำงานที่ดำเนินการได้ เช่น clickable หรือ toggleable มาพร้อมกับความหมายอื่นๆ ที่กำหนดไว้ล่วงหน้าซึ่งอธิบายลักษณะการทำงานที่แท้จริง และสามารถเปลี่ยนแปลงได้ผ่าน Compose API อื่นๆ

องค์ประกอบแบบอินเทอร์แอกทีฟ

API ของ Material และ Foundation Compose จะสร้างองค์ประกอบ UI ที่ผู้ใช้โต้ตอบได้ผ่าน API ตัวแก้ไข clickable และ toggleable เนื่องจากคอมโพเนนต์ที่โต้ตอบได้อาจประกอบด้วยองค์ประกอบหลายรายการ clickable และ toggleable จึงผสานความหมายขององค์ประกอบย่อยโดยค่าเริ่มต้น เพื่อให้คอมโพเนนต์ได้รับการปฏิบัติเป็นเอนทิตีเชิงตรรกะรายการเดียว

เช่น Button ของ Material อาจประกอบด้วยไอคอนย่อยและข้อความบางส่วน แทนที่จะปฏิบัติต่อองค์ประกอบย่อยเป็นองค์ประกอบเดี่ยวๆ Button จะผสานความหมายขององค์ประกอบย่อยโดยค่าเริ่มต้น เพื่อให้บริการการช่วยเหลือพิเศษสามารถจัดกลุ่มองค์ประกอบเหล่านั้นตามความเหมาะสม

ปุ่มที่มีความหมายขององค์ประกอบย่อยที่ไม่ได้ผสานรวมกับที่ผสานรวม
รูปที่ 7 ปุ่มที่มีความหมายขององค์ประกอบย่อยที่ไม่ได้ผสานรวมกับที่ผสานรวม

ในทำนองเดียวกัน การใช้ตัวแก้ไข clickable ยังทำให้ Composable รวมความหมายขององค์ประกอบสืบทอดเป็นเอนทิตีเดียว ซึ่งจะส่งไปยังบริการการช่วยเหลือพิเศษพร้อมกับการแสดงการดำเนินการที่เกี่ยวข้อง ดังนี้

Row(
    // Uses `mergeDescendants = true` under the hood
    modifier = Modifier.clickable { openArticle() }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open",
    )
    Text("Accessibility in Compose")
}

นอกจากนี้ คุณยังตั้งค่า onClickLabel ที่เฉพาะเจาะจงในองค์ประกอบหลักที่คลิกได้เพื่อให้ข้อมูลเพิ่มเติมแก่บริการการช่วยเหลือพิเศษและนำเสนอการดำเนินการที่ละเอียดยิ่งขึ้นได้ด้วย

Row(
    modifier = Modifier
        .clickable(onClickLabel = "Open this article") {
            openArticle()
        }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open"
    )
    Text("Accessibility in Compose")
}

เมื่อใช้ TalkBack เป็นตัวอย่าง ตัวแก้ไข clickable นี้และป้ายกำกับการคลิกจะช่วยให้ TalkBack แสดงคำแนะนำการดำเนินการว่า "แตะสองครั้งเพื่อเปิดบทความนี้" แทนที่จะเป็นความคิดเห็นเริ่มต้นทั่วไปที่ว่า "แตะสองครั้งเพื่อเปิดใช้งาน"

ความคิดเห็นนี้จะเปลี่ยนแปลงไปตามประเภทของการดำเนินการ การคลิกค้างจะแสดงคำแนะนำของ TalkBack ว่า "แตะสองครั้งค้างไว้เพื่อ" ตามด้วยป้ายกำกับ ดังนี้

Row(
    modifier = Modifier
        .combinedClickable(
            onLongClickLabel = "Bookmark this article",
            onLongClick = { addToBookmarks() },
            onClickLabel = "Open this article",
            onClick = { openArticle() },
        )
) {}

ในบางกรณี คุณอาจไม่มีสิทธิ์เข้าถึงตัวแก้ไข clickable โดยตรง (เช่น เมื่อตั้งค่าไว้ที่ใดที่หนึ่งในเลเยอร์ที่ฝังอยู่ด้านล่าง) แต่ยังคงต้องการเปลี่ยนป้ายกำกับประกาศจากค่าเริ่มต้น โดยแยกการตั้งค่า clickable ออกจากการแก้ไขประกาศโดยใช้ตัวแก้ไข semantics และตั้งค่าป้ายกำกับการคลิกที่นั่นเพื่อแก้ไขการแสดงการดำเนินการ ดังนี้

@Composable
private fun ArticleList(openArticle: () -> Unit) {
    NestedArticleListItem(
        // Clickable is set separately, in a nested layer:
        onClickAction = openArticle,
        // Semantics are set here:
        modifier = Modifier.semantics {
            onClick(
                label = "Open this article",
                action = {
                    // Not needed here: openArticle()
                    true
                }
            )
        }
    )
}

คุณไม่จำเป็นต้องส่งการดำเนินการคลิก 2 ครั้ง API ของ Compose ที่มีอยู่ เช่น clickable หรือ Button จะจัดการเรื่องนี้ให้คุณ ตรรกะการผสานจะตรวจสอบว่ามีการใช้ป้ายกำกับและดำเนินการกับตัวแก้ไขที่อยู่นอกสุดสำหรับข้อมูลที่มีอยู่ ในตัวอย่างก่อนหน้านี้ NestedArticleListItem จะส่งการดำเนินการคลิก openArticle() ไปยังความหมาย clickable โดยอัตโนมัติ คุณสามารถปล่อยให้การดำเนินการคลิกเป็นค่า Null ในการดำเนินการตัวแก้ไขความหมายที่ 2 ได้ อย่างไรก็ตาม ป้ายกำกับการคลิกจะนำมาจากตัวแก้ไขความหมายที่ 2 onClick(label = "Open this document") เนื่องจากไม่มีอยู่ในตัวแก้ไขแรก

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

คอมโพเนนต์ที่กำหนดเอง

เมื่อสร้างคอมโพเนนต์ที่กำหนดเอง ให้ตรวจสอบการใช้งานคอมโพเนนต์ที่คล้ายกันในไลบรารี Material หรือไลบรารี Compose อื่นๆ จากนั้นเลียนแบบหรือแก้ไขลักษณะการทำงานของการช่วยเหลือพิเศษตามความเหมาะสม ตัวอย่างเช่น หากคุณแทนที่ Checkbox ของ Material ด้วยการติดตั้งใช้งานของคุณเอง การดูการติดตั้งใช้งาน Checkbox ที่มีอยู่จะช่วยเตือนให้คุณเพิ่มตัวแก้ไข triStateToggleable ซึ่งจะจัดการพร็อพเพอร์ตี้การช่วยเหลือพิเศษสำหรับคอมโพเนนต์ นอกจากนี้ ให้ใช้ตัวแก้ไข Foundation อย่างเต็มที่ เนื่องจากตัวแก้ไขเหล่านี้รวมถึงข้อควรพิจารณาด้านการช่วยเหลือพิเศษในตัวและแนวทางปฏิบัติของ Compose ที่มีอยู่ซึ่งครอบคลุมในส่วนนี้

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