Skip to content

Foldable Support

Nicolas Roard edited this page Mar 11, 2021 · 6 revisions

In the 2.1 release, we added several features to help manage foldable devices.

Shared Values

We added a new mechanism to inject runtime values in ConstraintLayout -- this is intended to be used for system-wide values, as all instances of ConstraintLayout are able to access the value.

In the context of Foldable devices, we can use this mechanism to inject the position of the fold at runtime:

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

In a custom helper, you can access the shared values by adding a listener for any changes:

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

You can look at the the Foldable example to see how we capture the position of the fold using the WindowManager library and inject it in ConstraintLayout.

DeviceState.POSTURE_HALF_OPENED -> {
  // The foldable device's hinge is in an intermediate position between opened and closed state.
 var fold = foldPosition(motionLayout, windowManager.windowLayoutInfo.displayFeatures)
   ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold)
}
DeviceState.POSTURE_OPENED -> {
  // The foldable device is completely open, the screen space that is presented to the user is flat.
  ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, 0);
}

fireNewValue() takes an id representing the value as first parameter and the value to inject as second parameter.

Reactive Guide

One way to take advantage ot a Shared Value in a layout, without having to write any code, is to use the ReactiveGuide helper. This will position a horizontal or vertical guideline according to the linked Shared Value.

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

It can then be used as a you would with a normal guideline.

MotionLayout with Foldable

We added several features in MotionLayout in 2.1 that helps morphing state -- something particularly useful for Foldable, as we typically have to handle animating between the different possible layouts.

There's two broad approach you can take to support Foldable:

  • update at runtime your current layout (ConstraintSet) to e.g. show/hide the fold
  • use explicit separate ConstraintSet for each of the foldable states you want to support (closed, fold present, fully open)

Animating ConstraintSet updates

We added a new function updateStateAnimate in MotionLayout:

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

which will automatically animate the changes when updating a given ConstraintSet instead of doing an immediate update (which you can do with updateState(stateId, constraintset)). This can let you update your UI on the fly, depending on changes like which foldable state you are in.

ReactiveGuide inside a MotionLayout

ReactiveGuide also supports two useful attributes when used inside a MotionLayout:

app:reactiveGuide_animateChange="true|false"

and

app:reactiveGuide_applyToAllConstraintSets="true|false"

The first one will modify the current ConstraintSet, and animate the change automatically. The second one will apply the new value of the ReactiveGuide position to all ConstraintSets in the MotionLayout. A typical approach with foldable would be to use a ReactiveGuide representing the fold position, setting up your layout elements relative to the ReactiveGuide.

Using multiple ConstraintSets to represent foldable state

Instead of updating the current MotionLayout state, another way to architect your UI to support Foldable is to create specific separate states (e.g. closed, fold, fully open).

You might still want to use in that case a ReactiveGuide to represent the fold, but you would have a lot more control (compared to the automated animation when updating the current ConstraintSet) on how each states would transition into another.

With this approach, in your DeviceState listener, you would simply drive the MotionLayout to transition in specific states (MotionLayout.transitionTo(stateId))