تصميم لأجهزة قابلة للطي

في الإصدار 2.1 من ConstraintLayout تمت إضافة عدة ميزات للمساعدة في إدارة الأجهزة القابلة للطي، بما في ذلك SharedValues وReactiveGuide والدعم المحسّن للصور المتحركة من خلال MotionLayout.

القيم المشتركة

أضفنا آلية جديدة لإدخال قيم وقت التشغيل في ConstraintLayout، والغرض منها هو استخدامها مع القيم على مستوى النظام، لأنّ جميع مثيلات ConstraintLayout يمكنها الوصول إلى القيمة.

في سياق الأجهزة القابلة للطي، يمكننا استخدام هذه الآلية لإدخال موضع الجزء المرئي من الصفحة في وقت التشغيل:

Kotlin

ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold)

Java

ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold);

في مساعد مخصّص، يمكنك الوصول إلى القيم المشتركة من خلال إضافة أداة معالجة لأي تغييرات:

Kotlin

val sharedValues: SharedValues = ConstraintLayout.getSharedValues()
sharedValues.addListener(mAttributeId, this)

Java

SharedValues sharedValues = ConstraintLayout.getSharedValues();
sharedValues.addListener(mAttributeId, this);

يمكنك إلقاء نظرة على مثال التجارب القابلة للطي لمعرفة كيفية تسجيل موضع الجزء المرئي من خلال مكتبة Jetpack WindowManager وإدخال الموضع في ConstraintLayout.

Kotlin

inner class StateContainer : Consumer<WindowLayoutInfo> {

    override fun accept(newLayoutInfo: WindowLayoutInfo) {

        // Add views that represent display features
        for (displayFeature in newLayoutInfo.displayFeatures) {
            val foldFeature = displayFeature as? FoldingFeature
            if (foldFeature != null) {
                if (foldFeature.isSeparating &&
                    foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
                ) {
                    // The foldable device is in tabletop mode
                    val fold = foldPosition(motionLayout, foldFeature)
                    ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold)
                } else {
                    ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, 0);
                }
            }
        }
    }
}

Java

class StateContainer implements Consumer<WindowLayoutInfo> {

    @Override
    public void accept(WindowLayoutInfo newLayoutInfo) {

        // Add views that represent display features
        for (DisplayFeature displayFeature : newLayoutInfo.getDisplayFeatures()) {
            if (displayFeature instanceof FoldingFeature) {
                FoldingFeature foldFeature = (FoldingFeature)displayFeature;
                if (foldFeature.isSeparating() &&
                    foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL
                ) {
                    // The foldable device is in tabletop mode
                    int fold = foldPosition(motionLayout, foldFeature);
                    ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold);
                } else {
                    ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, 0);
                }
            }
        }
    }
}

تحصل fireNewValue() على رقم تعريف يمثل المعلمة الأولى والقيمة المطلوب إدخالها كمعلمة ثانية.

ReactiveGuide

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

    <androidx.constraintlayout.widget.ReactiveGuide
        android:id="@+id/fold"
        app:reactiveGuide_valueId="@id/fold"
        android:orientation="horizontal" />

ويمكن بعد ذلك استخدامه كما تفعل عادةً مع الإرشادات العادية.

MotionLayout للأجهزة القابلة للطي

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

تتوفّر طريقتان للأجهزة القابلة للطي:

  • في وقت التشغيل، يجب تعديل التنسيق الحالي (ConstraintSet) لإظهار الجزء المرئي من الصفحة أو إخفائها.
  • يُرجى استخدام ConstraintSet منفصلة لكل حالة من الحالات القابلة للطي التي تريد إتاحة الوصول إليها (closed أو folded أو fully open).

إضافة تأثيرات حركية إلى ConstraintSet

تمت إضافة الدالة updateStateAnimate() في MotionLayout في إصدار 2.1:

Kotlin

fun updateStateAnimate(stateId: Int, set: ConstraintSet, duration: Int)

Java

void updateStateAnimate(int stateId, ConstraintSet set, int duration);

وستعمل هذه الوظيفة على تحريك التغييرات تلقائيًا عند تحديث ConstraintSet بدلاً من إجراء تحديث فوري (وهو إجراء يمكنك تنفيذه باستخدام updateState(stateId, constraintset)). ويسمح لك ذلك بتعديل واجهة المستخدم بشكل سريع، بناءً على التغييرات، مثل الحالة القابلة للطي التي تستخدمها.

"ReactiveGuide" في MotionLayout

تتوافق السمة ReactiveGuide أيضًا مع سمتَين مفيدتَين عند استخدامهما داخل MotionLayout:

  • app:reactiveGuide_animateChange="true|false"

  • app:reactiveGuide_applyToAllConstraintSets="true|false"

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

استخدام عدة ConstraintSet لتمثيل الحالة القابلة للطي

بدلاً من تعديل حالة MotionLayout الحالية، هناك طريقة أخرى لتصميم واجهة المستخدم لإتاحة الأجهزة القابلة للطي وهي إنشاء حالات منفصلة محدّدة (بما في ذلك closed وfolded وfully open).

في هذا السيناريو، قد تحتاج إلى استخدام ReactiveGuide لتمثيل الجزء المرئي من الصفحة، ولكن سيكون لديك المزيد من التحكّم (مقارنةً بالرسومات المتحركة المبرمَجة عند تعديل ConstraintSet الحالية) في كيفية انتقال كل حالة إلى أخرى.

باستخدام هذا النهج، في المستمع DeviceState، يمكنك ببساطة توجيه MotionLayout للانتقال إلى حالات محددة من خلال MotionLayout.transitionToState(stateId) الطريقة.