Thiết kế cho thiết bị có thể gập lại

Trong ConstraintLayout Phiên bản 2.1, một số tính năng đã được thêm vào để giúp quản lý thiết bị có thể gập lại, bao gồm SharedValues, ReactiveGuide và tính năng hỗ trợ nâng cao cho ảnh động với MotionLayout.

Giá trị chung

Chúng tôi đã thêm một cơ chế mới để chèn các giá trị thời gian chạy vào ConstraintLayout – mã này được sử dụng cho các giá trị trên toàn hệ thống, vì tất cả các phiên bản của ConstraintLayout có thể truy cập vào giá trị này.

Trong bối cảnh của thiết bị có thể gập lại, chúng ta có thể sử dụng cơ chế này để chèn vị trí của nếp gấp trong thời gian chạy:

Kotlin

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

Java

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

Trong một trình trợ giúp tuỳ chỉnh, bạn có thể truy cập vào các giá trị được chia sẻ bằng cách thêm trình nghe cho bất kỳ thay đổi nào:

Kotlin

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

Java

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

Bạn có thể xem Ví dụ về Có thể gập lại để xem cách chúng tôi nắm bắt vị trí của đường ranh giới phần hiển thị bằng cách sử dụng Thư viện Jetpack WindowManager và chèn vị trí vào 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() lấy mã nhận dạng đại diện cho giá trị làm tham số đầu tiên và giá trị cần chèn làm tham số thứ hai.

ReactiveGuide

Có một cách để tận dụng SharedValue trong bố cục mà không cần phải viết bất kỳ mã nào, tức là sử dụng ReactiveGuide của chúng tôi. Việc này sẽ định vị nguyên tắc ngang hoặc dọc theo liên kết SharedValue.

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

Sau đó, bạn có thể sử dụng phương pháp này như cách áp dụng quy tắc thông thường.

MotionLayout cho thiết bị có thể gập lại

Chúng tôi đã thêm một số tính năng trong MotionLayout trong phiên bản 2.1 để giúp cải tiến trạng thái – một tính năng đặc biệt hữu ích cho thiết bị có thể gập lại, như chúng ta thường thấy phải xử lý ảnh động giữa các bố cục khác nhau có thể có.

Có 2 phương pháp dành cho thiết bị có thể gập lại:

  • Trong thời gian chạy, hãy cập nhật bố cục hiện tại (ConstraintSet) để hiện hoặc ẩn màn hình đầu tiên.
  • Sử dụng một ConstraintSet riêng cho từng trạng thái gập mà bạn muốn support (closed, folded hoặc fully open).

Tạo ảnh động cho ConstraintSet

Hàm updateStateAnimate() trong MotionLayout đã được thêm vào phiên bản 2.1 bản phát hành:

Kotlin

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

Java

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

Hàm này sẽ tự động tạo ảnh động cho các thay đổi khi cập nhật một giá trị ConstraintSet thay vì thực hiện cập nhật ngay lập tức (bạn có thể làm điều này bằng updateState(stateId, constraintset)). Thao tác này cho phép bạn cập nhật giao diện người dùng trên bay, tuỳ thuộc vào các thay đổi, chẳng hạn như bạn đang ở trạng thái nào của thiết bị có thể gập lại.

ReactiveGuide bên trong một MotionLayout

ReactiveGuide cũng hỗ trợ hai thuộc tính hữu ích khi được dùng bên trong một MotionLayout:

  • app:reactiveGuide_animateChange="true|false"

  • app:reactiveGuide_applyToAllConstraintSets="true|false"

Thao tác đầu tiên sẽ sửa đổi ConstraintSet hiện tại và tạo ảnh động cho thay đổi đó tự động. Hành động thứ hai sẽ áp dụng giá trị mới của ReactiveGuide cho tất cả ConstraintSet trong MotionLayout. Một phương pháp tiếp cận điển hình cho có thể gập lại sẽ sử dụng ReactiveGuide đại diện cho vị trí gập, khi thiết lập các phần tử bố cục tương ứng với ReactiveGuide.

Sử dụng nhiều ConstraintSet để biểu thị trạng thái có thể gập lại

Thay vì cập nhật trạng thái MotionLayout hiện tại, hãy thiết kế một cách khác giao diện người dùng hỗ trợ thiết bị có thể gập lại cần tạo các trạng thái riêng biệt (bao gồm closed, foldedfully open).

Trong trường hợp này, có thể bạn vẫn muốn sử dụng ReactiveGuide để biểu thị nhưng bạn sẽ có nhiều quyền kiểm soát hơn (so với ảnh động khi cập nhật ConstraintSet hiện tại) về cách mỗi trạng thái sẽ sang một tên khác.

Với phương pháp này, trong trình nghe DeviceState, bạn chỉ cần hướng dẫn MotionLayout để chuyển sang các tiểu bang cụ thể thông qua MotionLayout.transitionToState(stateId) .