Mendesain aplikasi untuk perangkat foldable

Dalam rilis ConstraintLayout 2.1, beberapa fitur ditambahkan untuk membantu mengelola perangkat foldable, termasuk SharedValues, ReactiveGuide, dan peningkatan dukungan untuk animasi dengan MotionLayout.

Nilai yang Dibagikan

Kami menambahkan mekanisme baru untuk memasukkan nilai runtime di ConstraintLayout – ini dimaksudkan untuk digunakan untuk nilai seluruh sistem, karena semua instance ConstraintLayout dapat mengakses nilai.

Dalam konteks perangkat foldable, kita dapat menggunakan mekanisme ini untuk memasukkan posisi lipatan saat runtime:

Kotlin

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

Java

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

Dalam helper kustom, Anda dapat mengakses nilai bersama dengan menambahkan pemroses untuk setiap perubahan:

Kotlin

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

Java

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

Anda dapat melihat contoh FoldableExperiments untuk melihat cara kami merekam posisi lipatan menggunakan library Jetpack WindowManager dan memasukkan posisi ke 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() mengambil ID yang mewakili nilai sebagai parameter pertama dan nilai yang akan dimasukkan sebagai parameter kedua.

ReactiveGuide

Salah satu cara untuk memanfaatkan SharedValue di tata letak, tanpa harus menulis kode, adalah dengan menggunakan helper ReactiveGuide. Tindakan ini akan memosisikan panduan horizontal atau vertikal sesuai dengan SharedValue tertaut.

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

Selanjutnya, data ini dapat digunakan seperti yang Anda lakukan dengan panduan normal.

MotionLayout untuk perangkat foldable

Kami menambahkan beberapa fitur dalam MotionLayout pada versi 2.1 yang membantu memetakan status – sesuatu yang sangat berguna untuk perangkat foldable, karena biasanya kami harus menangani animasi di antara berbagai tata letak yang mungkin.

Ada dua pendekatan yang tersedia untuk perangkat foldable:

  • Saat runtime, perbarui tata letak saat ini (ConstraintSet) untuk menampilkan atau menyembunyikan lipatan.
  • Gunakan ConstraintSet terpisah untuk setiap status perangkat foldable yang ingin Anda dukung (closed, folded, atau fully open).

Menganimasikan ConstraintSet

Fungsi updateStateAnimate() di MotionLayout telah ditambahkan dalam rilis 2.1:

Kotlin

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

Java

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

Fungsi ini akan otomatis menganimasikan perubahan saat mengupdate ConstraintSet tertentu, bukan melakukan update langsung (yang dapat Anda lakukan dengan updateState(stateId, constraintset)). Hal ini memungkinkan Anda mengupdate UI dengan cepat, bergantung pada perubahan, seperti status perangkat foldable mana yang Anda gunakan.

ReactiveGuide di dalam MotionLayout

ReactiveGuide juga mendukung dua atribut yang berguna saat digunakan di dalam MotionLayout:

  • app:reactiveGuide_animateChange="true|false"

  • app:reactiveGuide_applyToAllConstraintSets="true|false"

Fungsi pertama akan mengubah ConstraintSet saat ini dan menganimasikan perubahan secara otomatis. Yang kedua akan menerapkan nilai baru dari posisi ReactiveGuide ke semua ConstraintSet di MotionLayout. Pendekatan standar untuk perangkat foldable adalah menggunakan ReactiveGuide yang mewakili posisi lipatan, dengan menyiapkan elemen tata letak relatif terhadap ReactiveGuide.

Menggunakan beberapa ConstraintSet untuk menampilkan status perangkat foldable

Daripada memperbarui status MotionLayout saat ini, cara lain untuk merancang UI Anda guna mendukung perangkat foldable adalah dengan membuat status terpisah tertentu (termasuk closed, folded, dan fully open).

Dalam skenario ini, Anda mungkin masih ingin menggunakan ReactiveGuide untuk mewakili lipatan, tetapi Anda akan memiliki lebih banyak kontrol (dibandingkan dengan animasi otomatis saat memperbarui ConstraintSet saat ini) terkait cara setiap status akan beralih ke status lain.

Dengan pendekatan ini, di pemroses DeviceState, Anda cukup mengarahkan MotionLayout untuk bertransisi ke status tertentu melalui metode MotionLayout.transitionToState(stateId).