التحكّم في ترتيب التمرير

يتم تلقائيًا تنفيذ سلوك قارئ الشاشة المتعلّق بتسهيل الاستخدام في تطبيق Compose بترتيب القراءة المتوقّع، والذي يكون عادةً من اليسار إلى اليمين ثم من أعلى إلى أسفل. ومع ذلك، هناك بعض أنواع تخطيطات التطبيقات حيث لا يمكن للخوارزمية تحديد ترتيب القراءة الفعلي بدون تلميحات إضافية. في التطبيقات المستندة إلى العرض، يمكنك حلّ هذه المشاكل باستخدام السمتَين traversalBefore وtraversalAfter. بدءًا من Compose 1.5، توفّر واجهة برمجة التطبيقات Compose واجهة برمجة تطبيقات مرنة بدرجة كبيرة ولكن بنموذج مفاهيمي جديد.

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) في Material Design. أساس هذا المثال هو التخطيط التالي:

تصميم يتضمن شريط تطبيق علوي ونموذج نص وزر إجراء عائم
  وشريط تطبيق سفلي.
الشكل 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) إلى أنّ المربّع العائم يظهر قبل كل العناصر الأخرى التي تظهر على الشاشة.

بعد ذلك، يمكنك وضع المربع العائم والعناصر الأخرى في سقالة، مما ينفذ تخطيطًا للتصميم المتعدد الأبعاد:

@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 ← شريط التطبيق السفلي

مراجع إضافية