ترتیب پیمایش را کنترل کنید

به‌طور پیش‌فرض، رفتار صفحه‌خوان دسترس‌پذیری در برنامه Compose به ترتیب خواندن مورد انتظار، که معمولاً از چپ به راست و سپس از بالا به پایین است، اجرا می‌شود. با این حال، انواعی از طرح‌بندی برنامه‌ها وجود دارد که الگوریتم نمی‌تواند ترتیب خواندن واقعی را بدون نکات اضافی تعیین کند. در برنامه‌های مبتنی بر view، می‌توانید چنین مشکلاتی را با استفاده از ویژگی‌های traversalBefore و traversalAfter برطرف کنید. با شروع در Compose 1.5 ، Compose یک API به همان اندازه انعطاف پذیر، اما با یک مدل مفهومی جدید ارائه می دهد.

isTraversalGroup و traversalIndex ویژگی های معنایی هستند که به شما امکان می دهند دسترسی و ترتیب تمرکز TalkBack را در سناریوهایی که الگوریتم مرتب سازی پیش فرض مناسب نیست کنترل کنید. isTraversalGroup گروه های مهم معنایی را شناسایی می کند، در حالی که traversalIndex ترتیب عناصر فردی را در آن گروه ها تنظیم می کند. برای سفارشی سازی بیشتر می توانید isTraversalGroup به تنهایی یا با traversalIndex استفاده کنید.

از isTraversalGroup و traversalIndex در برنامه خود برای کنترل ترتیب پیمایش صفحه خوان استفاده کنید.

گروه بندی عناصر با isTraversalGroup

isTraversalGroup یک ویژگی بولی است که تعیین می کند آیا یک گره معنایی یک گروه پیمایش است یا خیر. این نوع گره، گرهی است که عملکرد آن به عنوان یک مرز یا مرز در سازماندهی فرزندان گره است.

تنظیم isTraversalGroup = true در یک گره به این معنی است که همه فرزندان آن گره قبل از انتقال به عناصر دیگر بازدید می شوند. می‌توانید isTraversalGroup را روی گره‌های قابل تمرکز غیرصفحه‌خوان، مانند ستون‌ها، ردیف‌ها یا جعبه‌ها تنظیم کنید.

مثال زیر از isTraversalGroup استفاده می کند. چهار عنصر متنی را منتشر می کند. دو عنصر سمت چپ متعلق به یک عنصر CardBox است، در حالی که دو عنصر سمت راست متعلق به یک عنصر CardBox است:

// CardBox() function takes in top and bottom sample text.
@Composable
fun CardBox(
    topSampleText: String,
    bottomSampleText: String,
    modifier: Modifier = Modifier
) {
    Box(modifier) {
        Column {
            Text(topSampleText)
            Text(bottomSampleText)
        }
    }
}

@Composable
fun TraversalGroupDemo() {
    val topSampleText1 = "This sentence is in "
    val bottomSampleText1 = "the left column."
    val topSampleText2 = "This sentence is "
    val bottomSampleText2 = "on the right."
    Row {
        CardBox(
            topSampleText1,
            bottomSampleText1
        )
        CardBox(
            topSampleText2,
            bottomSampleText2
        )
    }
}

کد خروجی مشابه زیر تولید می کند:

طرح‌بندی با دو ستون متن، با خواندن ستون سمت چپ «این جمله در ستون سمت چپ است» و ستون سمت راست با خواندن «این جمله در سمت راست است».
شکل 1. طرحی با دو جمله (یکی در ستون سمت چپ و دیگری در ستون سمت راست).

از آنجا که هیچ معنایی تنظیم نشده است، رفتار پیش‌فرض صفحه‌خوان این است که عناصر را از چپ به راست و از بالا به پایین طی کند. به دلیل این پیش‌فرض، TalkBack بخش‌های جمله را به ترتیب اشتباه می‌خواند:

"این جمله در است" → "این جمله است" → "ستون سمت چپ." → "در سمت راست."

برای ترتیب صحیح قطعات، قطعه اصلی را تغییر دهید تا isTraversalGroup را روی true تنظیم کنید:

@Composable
fun TraversalGroupDemo2() {
    val topSampleText1 = "This sentence is in "
    val bottomSampleText1 = "the left column."
    val topSampleText2 = "This sentence is"
    val bottomSampleText2 = "on the right."
    Row {
        CardBox(
//      1,
            topSampleText1,
            bottomSampleText1,
            Modifier.semantics { isTraversalGroup = true }
        )
        CardBox(
//      2,
            topSampleText2,
            bottomSampleText2,
            Modifier.semantics { isTraversalGroup = true }
        )
    }
}

از آنجایی که isTraversalGroup به طور خاص روی هر CardBox تنظیم شده است، مرزهای CardBox هنگام مرتب‌سازی عناصر اعمال می‌شوند. در این حالت ابتدا CardBox سمت چپ و سپس CardBox سمت راست خوانده می شود.

اکنون، TalkBack قطعات جمله را به ترتیب صحیح می خواند:

"این جمله در" → "ستون سمت چپ است." → "این جمله است" → "در سمت راست."

سفارشی کردن بیشتر سفارش پیمایش

traversalIndex یک ویژگی شناور است که به شما امکان می دهد ترتیب پیمایش TalkBack را سفارشی کنید. اگر گروه بندی عناصر در کنار هم برای کارکرد صحیح TalkBack کافی نیست، از traversalIndex همراه با isTraversalGroup برای سفارشی کردن بیشتر سفارش صفحه خوان استفاده کنید.

ویژگی traversalIndex دارای ویژگی های زیر است:

  • ابتدا عناصر با مقادیر traversalIndex کمتر اولویت بندی می شوند.
  • می تواند مثبت یا منفی باشد.
  • مقدار پیش فرض 0f است.
  • فقط بر گره‌های قابل تمرکز بر روی صفحه‌خوان تأثیر می‌گذارد، مانند عناصر روی صفحه مانند متن یا دکمه‌ها. به عنوان مثال، تنظیم تنها traversalIndex روی یک ستون هیچ تاثیری نخواهد داشت، مگر اینکه ستون isTraversalGroup نیز روی آن تنظیم شده باشد.

مثال زیر نشان می دهد که چگونه می توانید traversalIndex و isTraversalGroup با هم استفاده کنید.

مثال: صفحه ساعت تراورس

صفحه ساعت یک سناریوی رایج است که در آن ترتیب پیمایش استاندارد کار نمی کند. مثال در این بخش یک انتخابگر زمان است که در آن کاربر می تواند از اعداد روی صفحه ساعت عبور کند و ارقام را برای شکاف های ساعت و دقیقه انتخاب کند.

یک صفحه ساعت با یک انتخابگر زمان در بالای آن.
شکل 2. تصویری از یک صفحه ساعت.

در قطعه ساده شده زیر، یک CircularLayout وجود دارد که در آن 12 عدد ترسیم شده است که با 12 شروع می شود و در جهت عقربه های ساعت حول دایره حرکت می کند:

@Composable
fun ClockFaceDemo() {
    CircularLayout {
        repeat(12) { hour ->
            ClockText(hour)
        }
    }
}

@Composable
private fun ClockText(value: Int) {
    Box(modifier = Modifier) {
        Text((if (value == 0) 12 else value).toString())
    }
}

از آنجایی که صفحه ساعت به صورت منطقی با ترتیب پیش‌فرض چپ به راست و بالا به پایین خوانده نمی‌شود، TalkBack اعداد را نامرتب می‌خواند. برای تصحیح این، از مقدار شمارنده افزایشی استفاده کنید، همانطور که در قطعه زیر نشان داده شده است:

@Composable
fun ClockFaceDemo() {
    CircularLayout(Modifier.semantics { isTraversalGroup = true }) {
        repeat(12) { hour ->
            ClockText(hour)
        }
    }
}

@Composable
private fun ClockText(value: Int) {
    Box(modifier = Modifier.semantics { this.traversalIndex = value.toFloat() }) {
        Text((if (value == 0) 12 else value).toString())
    }
}

برای تنظیم صحیح ترتیب پیمایش، ابتدا CircularLayout یک گروه پیمایش کنید و isTraversalGroup = true را تنظیم کنید. سپس، همانطور که هر متن ساعت بر روی طرح‌بندی کشیده می‌شود، traversalIndex مربوط به آن را روی مقدار شمارنده تنظیم کنید.

از آنجا که مقدار شمارنده به طور مداوم افزایش می‌یابد، هر مقدار ساعت، traversalIndex بزرگ‌تر می‌شود، زیرا اعداد به صفحه اضافه می‌شوند—مقدار ساعت 0 دارای traversalIndex 0 و مقدار ساعت 1 دارای traversalIndex 1 است. به این ترتیب، ترتیبی که TalkBack آنها را در تنظیم می کند. اکنون اعداد داخل CircularLayout به ترتیب مورد انتظار خوانده می شوند.

از آنجایی که traversalIndexes هایی که تنظیم شده اند فقط نسبت به سایر نمایه های داخل همان گروه بندی هستند، بقیه ترتیب صفحه حفظ شده است. به عبارت دیگر، تغییرات معنایی نشان‌داده‌شده در قطعه کد قبلی، فقط ترتیب در صفحه ساعت را تغییر می‌دهد که دارای isTraversalGroup = true است.

توجه داشته باشید که بدون تنظیم معنایی CircularLayout's به isTraversalGroup = true ، تغییرات traversalIndex همچنان اعمال می شود. با این حال، بدون CircularLayout که آنها را متصل کند، دوازده رقم صفحه ساعت در آخر خوانده می‌شوند، پس از بازدید از سایر عناصر روی صفحه. این به این دلیل اتفاق می‌افتد که همه عناصر دیگر دارای traversalIndex پیش‌فرض 0f هستند و عناصر متن ساعت بعد از همه عناصر 0f دیگر خوانده می‌شوند.

مثال: سفارشی کردن ترتیب پیمایش برای دکمه عمل شناور

در این مثال، traversalIndex و isTraversalGroup ترتیب پیمایش یک دکمه عمل شناور طراحی مواد (FAB) را کنترل می کنند. اساس این مثال طرح زیر است:

طرح‌بندی با نوار برنامه بالا، متن نمونه، دکمه عمل شناور و نوار برنامه پایین.
شکل 3. چیدمان با نوار برنامه بالا، متن نمونه، دکمه عمل شناور، و نوار برنامه پایین.

به‌طور پیش‌فرض، طرح‌بندی در این مثال به ترتیب TalkBack زیر است:

نوار برنامه بالا ← متون نمونه 0 تا 6 ← دکمه اقدام شناور (FAB) ← نوار برنامه پایین

ممکن است بخواهید صفحه‌خوان ابتدا روی FAB تمرکز کند. برای تنظیم traversalIndex روی یک عنصر Material مانند FAB، موارد زیر را انجام دهید:

@Composable
fun FloatingBox() {
    Box(modifier = Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) {
        FloatingActionButton(onClick = {}) {
            Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon")
        }
    }
}

در این قطعه، ایجاد کادری با مقدار isTraversalGroup روی true و تنظیم traversalIndex در همان کادر ( -1f کمتر از مقدار پیش‌فرض 0f است) به این معنی است که کادر شناور قبل از سایر عناصر روی صفحه قرار می‌گیرد.

در مرحله بعد، می‌توانید جعبه شناور و سایر عناصر را در یک داربست قرار دهید، که طرح‌بندی Material Design را اجرا می‌کند:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ColumnWithFABFirstDemo() {
    Scaffold(
        topBar = { TopAppBar(title = { Text("Top App Bar") }) },
        floatingActionButtonPosition = FabPosition.End,
        floatingActionButton = { FloatingBox() },
        content = { padding -> ContentColumn(padding = padding) },
        bottomBar = { BottomAppBar { Text("Bottom App Bar") } }
    )
}

TalkBack به ترتیب زیر با عناصر تعامل دارد:

FAB ← نوار برنامه بالا ← متون نمونه 0 تا 6 ← نوار برنامه پایین

منابع اضافی