针对可折叠设备设计应用

ConstraintLayout 2.1 版本中,我们添加了多项功能来帮助管理可折叠设备,包括 SharedValuesReactiveGuide,以及通过 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);

您可以查看 FoldableExperiments 示例,了解我们如何使用 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() 接受一个 ID,该 ID 表示值的第一个参数,而要注入的值作为第二个参数。

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) 以显示或隐藏折叠边。
  • 为您要支持的每个可折叠设备状态(closedfoldedfully open)使用单独的 ConstraintSet

ConstraintSet 添加动画效果

2.1 版本中新增了 MotionLayout 中的 updateStateAnimate() 函数:

Kotlin

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

Java

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

更新给定的 ConstraintSet 时,此函数会自动为更改添加动画效果,而不是立即更新(您可以使用 updateState(stateId, constraintset) 执行此操作)。这样一来,您就可以根据更改(例如您所处的可折叠状态)实时更新界面。

MotionLayout 中的 ReactiveGuide

MotionLayout 内使用时,ReactiveGuide 还支持以下两个有用的属性:

  • app:reactiveGuide_animateChange="true|false"

  • app:reactiveGuide_applyToAllConstraintSets="true|false"

第一个文件将修改当前的 ConstraintSet,并自动为更改添加动画效果。第二个请求会将 ReactiveGuide 位置的新值应用于 MotionLayout 中的所有 ConstraintSet。可折叠设备的典型方法是使用表示折叠位置的 ReactiveGuide,设置相对于 ReactiveGuide 的布局元素。

使用多个 ConstraintSet 表示可折叠设备状态

如需支持可折叠设备,另一种构建界面的方式是创建特定的单独状态(包括 closedfoldedfully open),而不是更新当前的 MotionLayout 状态。

在这种情况下,您可能仍然需要使用 ReactiveGuide 来表示折叠,但您可以更好地控制(与更新当前 ConstraintSet 时的自动动画相比)如何转换每种状态。

使用此方法,在 DeviceState 监听器中,您只需通过 MotionLayout.transitionToState(stateId) 方法指示 MotionLayout 转换到特定状态即可。