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

يتم تلقائيًا تنفيذ سلوك قارئ الشاشة لتسهيل الاستخدام في تطبيق 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 في ترتيب الاجتياز لزر الإجراء العائم في Material Design (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 مع العناصر بالترتيب التالي:

زر الإجراء الرئيسي ← شريط التطبيق العلوي ← نماذج النصوص من 0 إلى 6 ← شريط التطبيق السفلي

مصادر إضافية