إدخال البيانات عبر وحدة تحكُّم دورانية باستخدام Compose

يشير الإدخال الدوار إلى البيانات التي يتم إدخالها من القطع التي تدور أو تدور في ساعتك. في المتوسط، يقضي المستخدمون بضع ثوانٍ فقط في التفاعل مع ساعاتهم. يمكنك تحسين تجربة المستخدم باستخدام مدخل البيانات الدوار للسماح للمستخدم بإنجاز مهام متنوعة بسرعة.

تشمل المصادر الثلاثة الرئيسية لإدخال البيانات عبر وحدة تحكُّم دورانية في معظم الساعات الزر الجانبي الدوّار (RSB)، والحواف الخارجية أو الحافة التي تعمل باللمس، وهي منطقة لمس دائرية حول الشاشة. على الرغم من أن السلوك المتوقع قد يختلف بناءً على نوع الإدخال، تأكد من دعم الإدخال الدوار لجميع التفاعلات الأساسية.

صفحة مواضع التمرير

يتوقّع معظم المستخدمين أن تتيح التطبيقات إيماءة التمرير. أثناء تمرير المحتوى على الشاشة، امنح المستخدمين ملاحظات مرئية استجابة للتفاعلات الدورانية. يمكن أن تتضمّن الملاحظات المرئية مؤشرات تحديد موضع للانتقال العمودي أو مؤشرات الصفحة.

يمكنك تنفيذ الانتقال الدوار باستخدام ComposeAllowed على نظام التشغيل Wear OS. يصف هذا المثال تطبيقًا بسقالة ورمز ScalingLazyColumn يتم تمريره عموديًا. توفر السقالة هيكل التخطيط الأساسي لتطبيقات Wear OS ولديها بالفعل فتحة لمؤشر التمرير. لإظهار تقدم التمرير، أنشئ مؤشر موضع استنادًا إلى كائن حالة القائمة. تكون طرق العرض القابلة للتمرير، بما في ذلك ScalingLazyColumn، قابلة للتمرير لإضافة إدخال دوار. لتلقّي أحداث التمرير الدائري، اتّبِع الخطوات التالية:

  1. طلب التركيز صراحةً باستخدام FocusRequester
  2. يمكنك إضافة عنصر تعديل onRotaryScrollEvent لاعتراض الأحداث التي ينشئها النظام عندما يحرّك المستخدم زر الساعة أو يتدوير الحافة. يحتوي كل حدث دوراني على قيمة وحدة بكسل محددة ويتم تمريره عموديًا أو أفقيًا. ويحتوي مفتاح التعديل أيضًا على معاودة الاتصال للإشارة إلى ما إذا تم استهلاك الفعالية، كما يوقف نشر الحدث لوالديه عند مشاهدته.
val listState = rememberScalingLazyListState()

Scaffold(
    positionIndicator = {
        PositionIndicator(scalingLazyListState = listState)
    }
) {

    val focusRequester = rememberActiveFocusRequester()
    val coroutineScope = rememberCoroutineScope()

    ScalingLazyColumn(
        modifier = Modifier
            .onRotaryScrollEvent {
                coroutineScope.launch {
                    listState.scrollBy(it.verticalScrollPixels)

                    listState.animateScrollBy(0f)
                }
                true
            }
            .focusRequester(focusRequester)
            .focusable(),
        state = listState
    ) { ... }
}

القيم المنفصلة

يمكنك استخدام التفاعلات الدورانية لضبط القيم المنفصلة أيضًا، مثل ضبط السطوع في الإعدادات أو اختيار الأرقام في منتقي الوقت عند ضبط منبه.

كما هي الحال في ScalingLazyColumn، يجب أن يتم التركيز على أدوات الاختيار وشريط التمرير وأداة السائر وغيرها من العناصر القابلة للإنشاء، وذلك لتلقّي بيانات إدخال البيانات عبر وحدة تحكُّم دورانية. وفي حال تعدد الأهداف القابلة للتمرير على الشاشة، مثل الساعات والدقائق في أداة اختيار الوقت، يمكنك إنشاء FocusRequester لكل هدف ومعالجة تغيير التركيز وفقًا لذلك عندما ينقر المستخدم على الساعات أو الدقائق.

@Composable
fun TimePicker() {
    var selectedColumn by remember { mutableStateOf(0) }
    val focusRequester1 = remember { FocusRequester() }
    val focusRequester2 = remember { FocusRequester() }

    Row {
       Picker(...)
       Picker(...)
    }

    LaunchedEffect(selectedColumn) {
        listOf(focusRequester1,
               focusRequester2)[selectedColumn]
            .requestFocus()
    }
}

الإجراءات المخصّصة

يمكنك أيضًا إنشاء إجراءات مخصّصة تستجيب لإدخال البيانات عبر وحدة تحكُّم دورانية في تطبيقك. على سبيل المثال، يمكنك استخدام طريقة إدخال البيانات عبر وحدة تحكُّم دورانية للتكبير والتصغير أو للتحكّم في مستوى الصوت في تطبيق وسائط.

إذا كان المكوِّن لا يتوافق في الأصل مع أحداث التمرير مثل التحكم في مستوى الصوت، يمكنك معالجة أحداث التمرير بنفسك.

// VolumeScreen.kt

val focusRequester: FocusRequester = remember { FocusRequester() }

Column(
    modifier = Modifier
        .fillMaxSize()
        .onRotaryScrollEvent {
            // handle rotary scroll events
            true
        }
        .focusRequester(focusRequester)
        .focusable(),
) { ... }

يمكنك إنشاء حالة مخصّصة مُدارة في نموذج العرض، ومعاودة اتصال مخصّصة تُستخدم لمعالجة أحداث التمرير الدوراني.

// VolumeViewModel.kt

object VolumeRange(
    public val max: Int = 10
    public val min: Int = 0
)

val volumeState: MutableStateFlow<Int> = ...

fun onVolumeChangeByScroll(pixels: Float) {
    volumeState.value = when {
        pixels > 0 -> min (volumeState.value + 1, VolumeRange.max)
        pixels < 0 -> max (volumeState.value - 1, VolumeRange.min)
    }
}

ولتبسيط الأمر، يستخدم المثال السابق قيم وحدات بكسل والتي إذا تم استخدامها فعليًا، من المرجّح أن تكون شديدة الحساسية.

استخدِم معاودة الاتصال بعد تلقّي الأحداث، كما هو موضّح في المقتطف التالي.

val focusRequester: FocusRequester = remember { FocusRequester() }
val volumeState by volumeViewModel.volumeState.collectAsState()

Column(
    modifier = Modifier
        .fillMaxSize()
        .onRotaryScrollEvent {
            volumeViewModel
                .onVolumeChangeByScroll(it.verticalScrollPixels)
            true
        }
        .focusRequester(focusRequester)
        .focusable(),
) { ... }

مراجع إضافية

يمكنك استخدام تطبيق Hootor، وهو مشروع مفتوح المصدر من Google يوفّر مجموعة من مكتبات Wear التي تكمّل الوظائف التي يوفّرها تطبيق ComposeAllowed على نظام التشغيل Wear OS وواجهات برمجة تطبيقات نظام التشغيل Wear OS الأخرى. توفر شركة Hoology عملية تنفيذ لحالات الاستخدام المتقدمة، والعديد من التفاصيل الخاصة بالجهاز.

على سبيل المثال، قد يختلف حساسية مصادر الإدخال الدوراني المختلفة. للانتقال بسلاسة بين القيم، يمكنك تقييمها أو إضافة إطارات أو صور متحركة للانتقال. يسمح هذا بتحويل السرعة لتبدو طبيعية أكبر للمستخدمين. يتضمن خبير في علم التخصصات تعديلاً للمكونات القابلة للتمرير والقيم المنفصلة. ويشمل أيضًا أدوات لمعالجة التركيز ومكتبة واجهة مستخدم صوتية لتنفيذ التحكم في مستوى الصوت باستخدام تقنية اللمس.

لمزيد من المعلومات، راجع Horology على GitHub.