底部 sheet + Android MotionLayout 实现
Bottom sheet + Android MotionLayout implementation
我正在尝试使用 MotionLayout 实现底部 sheet。它适用于一个微不足道的情况 - 当 bottom sheet 应该只在屏幕的一半可见时(例如)。但是我无法在底部 sheet 扩展并填满整个屏幕的情况下工作。
所以这里可以有 3 个状态:
- 底部sheet隐藏
- 底部sheet 占半屏
- 底部sheet 填满整个屏幕
布局如下:
<androidx.constraintlayout.motion.widget.MotionLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/scene_description">
<FrameLayout
android:id="@+id/fragmentPlaceholder"
android:layout_width="match_parent"
android:layout_height="570dp"
android:elevation="8dp"> some content here </FrameLayout>
</androidx.constraintlayout.motion.widget.MotionLayout>
这里570dp
等于半屏(例如)
以及scene_description.xml
的内容:
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
android:id="@+id/transition1"
app:constraintSetStart="@id/bottomSheetHidden"
app:constraintSetEnd="@id/bottomSheetHalfOpen"
app:duration="300">
<OnSwipe
app:dragDirection="dragDown"
app:onTouchUp="autoCompleteToStart"
app:touchAnchorId="@+id/fragmentPlaceholder"
app:touchAnchorSide="bottom"/>
</Transition>
<Transition
android:id="@+id/transition2"
app:constraintSetStart="@id/bottomSheetHalfOpen"
app:constraintSetEnd="@id/bottomSheetOpenFullScreen"
app:duration="300">
</Transition>
<Transition
android:id="@+id/transition3"
app:constraintSetStart="@id/bottomSheetHidden"
app:constraintSetEnd="@id/bottomSheetOpenFullScreen"
app:duration="300">
<OnSwipe
app:dragDirection="dragDown"
app:touchAnchorId="@+id/fragmentPlaceholder"
app:touchAnchorSide="bottom" />
</Transition>
<ConstraintSet android:id="@+id/bottomSheetHidden">
<Constraint android:id="@id/fragmentPlaceholder">
<Layout
android:layout_width="match_parent"
android:layout_height="570dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="parent"/> <!-- below screen, not visible-->
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/bottomSheetHalfOpen"
app:deriveConstraintsFrom="@id/bottomSheetHidden">
<Constraint android:id="@id/fragmentPlaceholder">
<Layout
android:layout_width="match_parent"
android:layout_height="570dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/bottomSheetOpenFullScreen"
app:deriveConstraintsFrom="@id/bottomSheetHidden">
<Constraint android:id="@id/fragmentPlaceholder">
<Layout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="30dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</Constraint>
</ConstraintSet>
</MotionScene>
问题是 OnSwipe 在底部 sheet 处于全屏状态时不起作用,但在底部 sheet 处于半屏状态时起作用。 我想要使用滑动移动隐藏底部 sheet 的机会。
这个问题怎么解决的?是否应该添加或修改过渡?
您的转换只需要稍微调整一下,约束很好:
- 您不需要第三次转换。如果您在同一个方向的不同状态之间滑动 - 或者实际上即使您改变方向,MotionLayout 会负责干净地移动通过多个
onSwipe
转换。
- 您的第二个和第三个 ConstraintSets 实际上并没有改变您正在移动的片段的底部边缘的位置,但是您指定场景应该根据相对于滑动手势的边缘来计算进度。我建议仔细阅读 MotionLayout codelab and specifically Lesson 9,它很好地说明了这个概念。因此,我将
touchAnchorSide
更改为 top
。
duration
对OnSwipe
转换没有任何意义,可以删除它
- 无论您使用
dragUp
还是 dragDown
,它都以相同的基于垂直手势的动画结束。我认为 dragUp
在这种情况下更容易理解。
修改后的转换:
<Transition
android:id="@+id/transition1"
app:constraintSetStart="@id/bottomSheetHidden"
app:constraintSetEnd="@id/bottomSheetHalfOpen">
<OnSwipe
app:dragDirection="dragUp"
app:onTouchUp="autoCompleteToStart"
app:touchAnchorId="@+id/fragmentPlaceholder"
app:touchAnchorSide="top"/>
</Transition>
<Transition
android:id="@+id/transition2"
app:constraintSetStart="@id/bottomSheetHalfOpen"
app:constraintSetEnd="@id/bottomSheetOpenFullScreen">
<OnSwipe
app:dragDirection="dragUp"
app:touchAnchorId="@+id/fragmentPlaceholder"
app:touchAnchorSide="top" />
</Transition>
我正在尝试使用 MotionLayout 实现底部 sheet。它适用于一个微不足道的情况 - 当 bottom sheet 应该只在屏幕的一半可见时(例如)。但是我无法在底部 sheet 扩展并填满整个屏幕的情况下工作。
所以这里可以有 3 个状态:
- 底部sheet隐藏
- 底部sheet 占半屏
- 底部sheet 填满整个屏幕
布局如下:
<androidx.constraintlayout.motion.widget.MotionLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/scene_description">
<FrameLayout
android:id="@+id/fragmentPlaceholder"
android:layout_width="match_parent"
android:layout_height="570dp"
android:elevation="8dp"> some content here </FrameLayout>
</androidx.constraintlayout.motion.widget.MotionLayout>
这里570dp
等于半屏(例如)
以及scene_description.xml
的内容:
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
android:id="@+id/transition1"
app:constraintSetStart="@id/bottomSheetHidden"
app:constraintSetEnd="@id/bottomSheetHalfOpen"
app:duration="300">
<OnSwipe
app:dragDirection="dragDown"
app:onTouchUp="autoCompleteToStart"
app:touchAnchorId="@+id/fragmentPlaceholder"
app:touchAnchorSide="bottom"/>
</Transition>
<Transition
android:id="@+id/transition2"
app:constraintSetStart="@id/bottomSheetHalfOpen"
app:constraintSetEnd="@id/bottomSheetOpenFullScreen"
app:duration="300">
</Transition>
<Transition
android:id="@+id/transition3"
app:constraintSetStart="@id/bottomSheetHidden"
app:constraintSetEnd="@id/bottomSheetOpenFullScreen"
app:duration="300">
<OnSwipe
app:dragDirection="dragDown"
app:touchAnchorId="@+id/fragmentPlaceholder"
app:touchAnchorSide="bottom" />
</Transition>
<ConstraintSet android:id="@+id/bottomSheetHidden">
<Constraint android:id="@id/fragmentPlaceholder">
<Layout
android:layout_width="match_parent"
android:layout_height="570dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="parent"/> <!-- below screen, not visible-->
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/bottomSheetHalfOpen"
app:deriveConstraintsFrom="@id/bottomSheetHidden">
<Constraint android:id="@id/fragmentPlaceholder">
<Layout
android:layout_width="match_parent"
android:layout_height="570dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/bottomSheetOpenFullScreen"
app:deriveConstraintsFrom="@id/bottomSheetHidden">
<Constraint android:id="@id/fragmentPlaceholder">
<Layout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="30dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</Constraint>
</ConstraintSet>
</MotionScene>
问题是 OnSwipe 在底部 sheet 处于全屏状态时不起作用,但在底部 sheet 处于半屏状态时起作用。 我想要使用滑动移动隐藏底部 sheet 的机会。
这个问题怎么解决的?是否应该添加或修改过渡?
您的转换只需要稍微调整一下,约束很好:
- 您不需要第三次转换。如果您在同一个方向的不同状态之间滑动 - 或者实际上即使您改变方向,MotionLayout 会负责干净地移动通过多个
onSwipe
转换。 - 您的第二个和第三个 ConstraintSets 实际上并没有改变您正在移动的片段的底部边缘的位置,但是您指定场景应该根据相对于滑动手势的边缘来计算进度。我建议仔细阅读 MotionLayout codelab and specifically Lesson 9,它很好地说明了这个概念。因此,我将
touchAnchorSide
更改为top
。 duration
对OnSwipe
转换没有任何意义,可以删除它- 无论您使用
dragUp
还是dragDown
,它都以相同的基于垂直手势的动画结束。我认为dragUp
在这种情况下更容易理解。
修改后的转换:
<Transition
android:id="@+id/transition1"
app:constraintSetStart="@id/bottomSheetHidden"
app:constraintSetEnd="@id/bottomSheetHalfOpen">
<OnSwipe
app:dragDirection="dragUp"
app:onTouchUp="autoCompleteToStart"
app:touchAnchorId="@+id/fragmentPlaceholder"
app:touchAnchorSide="top"/>
</Transition>
<Transition
android:id="@+id/transition2"
app:constraintSetStart="@id/bottomSheetHalfOpen"
app:constraintSetEnd="@id/bottomSheetOpenFullScreen">
<OnSwipe
app:dragDirection="dragUp"
app:touchAnchorId="@+id/fragmentPlaceholder"
app:touchAnchorSide="top" />
</Transition>