MotionLayout
ConstraintLayout的子类,是一种可以管理应用中的运动和微件动画的布局类型,关联手势和组件动画。
支持在XML中完全描述一个复杂的动画。
常规使用
依赖
implementation ‘androidx.constraintlayout:constraintlayout:2.0.0-rc1’
布局
<androidx.constraintlayout.motion.widget.MotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout"
app:layoutDescription="@xml/layout_motion_demo_scene"
app:motionDebug="SHOW_PATH"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
android:textSize="12dp"/>
<View
android:id="@+id/view"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
不需要添加效果的子视图按需要的位置添加约束,有效果的可以在描述文件中声明,MotionLayout不会修改未被Constraint引用修饰的视图。
运动描述
layoutDescription
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:motionInterpolator="linear"
motion:duration="2000">
<ConstraintSet android:id="@+id/start">
<Constraint
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button"
motion:layout_constraintStart_toStartOf="parent"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent">
</Constraint>
</ConstraintSet>
</MotionScene>
自动生成的默认xml描述运动的文件,主要标签 <Transition、、。
Transition:运动行为过渡的描述,每个MotionScene都至少有一个,包括起始及结束位置,时间。
motionInterpolator添加过渡动画的插值器:
- linear:线性
- easeIn:缓入
- easeOut:缓出
- easeInOut:缓入缓出
- bounce:弹簧
也可以直接使用布局作为约束:
<Transition
motion:constraintSetStart="@layout/layout_motion_demo"
motion:constraintSetEnd="@layout/layout_motion_demo"
motion:motionInterpolator="linear"
motion:duration="2000">
ConstraintSet…:描述 子视图们 初始位置的约束、结束位置的约束,应用于动画开始、结束。
Constraint…: 子视图各自约束位置描述。
动画编辑器
AS提供了MotionLayout动画的界面工具,基本所有效果都可以界面操作完成。
点击选中start、end,下部会出现相应的约束列表,包含视图中所有子视图,
点击画笔可以添加约束或者删除约束。
点击上部箭头连线开启预览视图
右边为创建关键帧按钮。
Attributes视图可以调整约束代码,避免手写。
右击预览视图可以自动将ConstraintLayout 转化 MotionLayout
开启动画
基于点击事件响应
通过编辑器添加
选择动画响应点击事件的控件
xml中会生成
<OnClick motion:targetId="@+id/button" />
app:targetId:设置触发动画的view的Id,前缀可以使用@+id、@id;
app:clickAction:
<attr name="clickAction">
<flag name="toggle" value="0x0011"/> //切换
<flag name="transitionToEnd" value="0x0001"/> //过渡到end
<flag name="transitionToStart" value="0x0010"/>
<flag name="jumpToEnd" value="0x100"/> //无过渡切换到end
<flag name="jumpToStart" value="0x1000"/>
</attr>
基于拖动事件响应
MotionLayout可以跟踪用户拖动事件或滑动,以创建基于物理的“fling”动画。
xml生成
<OnSwipe
motion:touchAnchorId="@+id/button"
motion:touchAnchorSide="right"
motion:dragDirection="dragRight" />
touchAnchorId:拖动动作关联的子视图对象;
dragDirection:拖动的方向
<attr format="enum" name="dragDirection">
<enum name="dragUp" value="0"/> //由下到上
<enum name="dragDown" value="1"/>
<enum name="dragLeft" value="2"/>
<enum name="dragRight" value="3"/>
<enum name="dragStart" value="4"/>
<enum name="dragEnd" value="5"/>
<enum name="dragClockwise" value="6"/>
<enum name="dragAnticlockwise" value="7"/>
</attr>
touchAnchorSide:设置触摸操作将会拖动对象的哪一边。
MotionLayout监听屏幕布局内任何地方的拖动事件,保持touchAnchorSide的一边和手指距离不变。
场景如:折叠效果
xml布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/layout_tuoch_demo_scene">
<FrameLayout
android:id="@+id/fl_top"
android:background="@color/black"
android:layout_width="match_parent"
android:layout_height="0dp">
</FrameLayout>
<FrameLayout
android:id="@+id/fl_center"
android:background="#FF4C7F"
android:layout_width="50dp"
android:layout_height="50dp">
</FrameLayout>
<FrameLayout
android:id="@+id/fl_bottom"
android:layout_width="match_parent"
android:background="@color/black"
android:layout_height="0dp"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
xml场景描述:
<?xml version="1.0" encoding="utf-8"?>
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<OnSwipe
motion:touchAnchorId="@+id/fl_bottom"
motion:touchAnchorSide="top"
motion:dragDirection="dragUp">
</OnSwipe>
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/fl_top"
android:layout_width="match_parent"
android:layout_height="0dp"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent" />
<Constraint
android:id="@+id/fl_bottom"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintTop_toBottomOf="parent"
android:layout_width="match_parent"
android:layout_height="0dp" />
<Constraint
android:id="@+id/fl_center"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:scaleX="1"
android:scaleY="1"
android:layout_width="50dp"
android:layout_height="50dp" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/fl_bottom"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent"
android:layout_width="match_parent"
android:layout_height="120dp" />
<Constraint
android:id="@+id/fl_top"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="match_parent"
android:layout_height="60dp" />
<Constraint
android:id="@+id/fl_center"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:scaleY="0"
android:scaleX="0"
android:layout_width="50dp"
android:layout_height="50dp" />
</ConstraintSet>
</MotionScene>
动画路径
xml布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:motionDebug="SHOW_PATH" //开启路径调试
app:layoutDescription="@xml/layout_scene_path_demo_scene">
<ImageView
android:id="@+id/iv"
android:src="@drawable/icon_1"
android:layout_width="50dp"
android:layout_height="50dp"/>
<ImageView
android:id="@+id/iv2"
android:src="@drawable/icon_1"
android:layout_width="50dp"
android:layout_height="50dp"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
xml描述:
<?xml version="1.0" encoding="utf-8"?>
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<KeyFrameSet>
</KeyFrameSet>
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/iv"
android:layout_width="50dp"
android:layout_height="50dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_marginBottom="20dp" />
<Constraint
android:id="@+id/iv2"
android:layout_width="50dp"
android:layout_height="50dp"
motion:layout_constraintStart_toStartOf="parent"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
motion:layout_editor_absoluteY="20dp"
motion:layout_constraintBottom_toTopOf="@+id/iv"
android:layout_marginBottom="50dp" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/iv"
android:layout_width="50dp"
android:layout_height="50dp"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_editor_absoluteY="661dp"
motion:layout_constraintBottom_toBottomOf="parent"
android:layout_marginRight="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp" />
<Constraint
android:id="@+id/iv2"
android:layout_width="50dp"
android:layout_height="50dp"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_marginRight="20dp"
android:layout_marginEnd="20dp"
motion:layout_editor_absoluteY="20dp"
motion:layout_constraintBottom_toTopOf="@+id/iv"
android:layout_marginBottom="50dp" />
</ConstraintSet>
</MotionScene>
上例默认路径为 起始约束位置到 结束约束位置
![image.png](https://img-blog.csdnimg.cn/img_convert/f67085de3b718371e9c2e266f9264581.png#clientId=udab1f27c-7db4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=119&id=ud5a7a3ce&margin=[object Object]&name=image.png&originHeight=177&originWidth=376&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9300&status=done&style=none&taskId=uaa17e181-a151-4e89-92fb-7d426968b8d&title=&width=252)
KeyFrameSet
关键帧:对两个状态之间的插值进行一个修改,是临时的修改,ConstraintSet是静止的状态。
- 位置关键帧 KeyPosition
- 属性关键帧 KeyAttribute
- 循环关键帧 KeyCycle
- 周期关键帧 KeyTimeCycle
KeyPosition
如把其中一个视图的动画路径改为一个向上的抛物曲线
Transition标签作修改
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<KeyFrameSet>
<KeyPosition
motion:motionTarget="@+id/iv" //受影响的目标
motion:framePosition="50" //作用时机(0-100)
motion:keyPositionType="parentRelative" //坐标系
motion:transitionEasing="linear" //差值器,默认linear
motion:percentX="0.5" // 坐标位置
motion:percentY="0.5" />
</KeyFrameSet>
</Transition>
路径预览:
![image.png](https://img-blog.csdnimg.cn/img_convert/9dfb5ee391c1ddc85fe7ada65455a3b4.png#clientId=udab1f27c-7db4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=167&id=u9a55a90c&margin=[object Object]&name=image.png&originHeight=227&originWidth=200&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4675&status=done&style=none&taskId=ud08efbef-dfdc-4483-905b-2c86c828028&title=&width=147)
坐标系
parentRelative
![a7b7568d46d9dec7.png](https://img-blog.csdnimg.cn/img_convert/4c28b14856d2e6c627ea2395e68eb5db.png#clientId=u078a1b73-f90e-4&crop=0&crop=0&crop=1&crop=1&from=drop&height=390&id=ub6895cc5&margin=[object Object]&name=a7b7568d46d9dec7.png&originHeight=1600&originWidth=1594&originalType=binary&ratio=1&rotation=0&showTitle=false&size=64298&status=done&style=none&taskId=u39eb523e-693a-46a9-96ad-2b5aa1415e9&title=&width=389)
父相对坐标系,左上(0,0),右下(1,1),使用较多。
deltaRelative
![52.png](https://img-blog.csdnimg.cn/img_convert/80ca70af18e66d9e7f67580fed7571f4.png#clientId=u078a1b73-f90e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=393&id=uedfaaaab&margin=[object Object]&name=52.png&originHeight=1600&originWidth=1583&originalType=binary&ratio=1&rotation=0&showTitle=false&size=66092&status=done&style=none&taskId=u980fbb72-22a4-4730-ad05-9b4dbdfc067&title=&width=389)
左下为起点(0,0),右上为终点(1,1)。
场景:单独控制水平或垂直运动
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<KeyFrameSet >
<KeyPosition
motion:motionTarget="@+id/iv"
motion:framePosition="50"
motion:keyPositionType="deltaRelative"
motion:percentY="0"
motion:percentX="0.3" />
</KeyFrameSet>
</Transition>
目标视图iv会水平移动前半程再向上进行原先的路径到达结束位置。
pathRelative
![path.png](https://img-blog.csdnimg.cn/img_convert/0bd7ed66b68e5ed4f5915df87c4eee1a.png#clientId=u078a1b73-f90e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=319&id=u97f490c6&margin=[object Object]&name=path.png&originHeight=1310&originWidth=1600&originalType=binary&ratio=1&rotation=0&showTitle=false&size=150658&status=done&style=none&taskId=u4a567ae9-67f7-43b2-9754-7008b83bbf0&title=&width=390)
运动路径为X 轴,起点(0,0),终点(1,0);
场景:在动画的一部分期间加速、减速或停止视图,配合viewPager滑动展示动画等;
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<KeyFrameSet >
<KeyPosition
motion:motionTarget="@+id/iv"
motion:framePosition="50"
motion:keyPositionType="pathRelative"
motion:percentX="0.3" />
</KeyFrameSet>
</Transition>
一半的时间运行前30%的动作。
S型曲线运动:
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<KeyFrameSet >
<KeyPosition
motion:motionTarget="@+id/iv"
motion:framePosition="25"
motion:keyPositionType="pathRelative"
motion:percentY="0.25"
motion:percentX="0.25" />
<KeyPosition
motion:motionTarget="@+id/iv"
motion:framePosition="75"
motion:keyPositionType="pathRelative"
motion:percentY="-0.25"
motion:percentX="0.75" />
</KeyFrameSet>
</Transition>
自定属性修改
标签CustomAttribute
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/iv"
android:layout_width="50dp"
android:layout_height="50dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_marginBottom="20dp" >
<CustomAttribute
motion:attributeName="backgroundColor" //自定义属性名称 需设置set/get方法 字体大小颜色透明度等
motion:customColorValue="#D81B60" /> // 属性值
</Constraint>
customColorValue的类型
<declare-styleable name="CustomAttribute">
<attr format="string" name="attributeName"/>
<attr format="string" name="methodName"/>
<attr format="color" name="customColorValue"/>
<attr format="color" name="customColorDrawableValue"/>
<attr format="integer" name="customIntegerValue"/>
<attr format="float" name="customFloatValue"/>
<attr format="string" name="customStringValue"/>
<attr format="dimension" name="customDimension"/>
<attr format="dimension" name="customPixelDimension"/>
<attr format="boolean" name="customBoolean"/>
<attr format="reference" name="customReference"/>
</declare-styleable>
针对不同控件有特殊使用方法场景,如ImageFilterView实现的各种图片过度;
关于Carousel
是一个MotionHelper类,只能在MontionLayout布局中使用,通常在涉及一些轮播的效果中使用;
使用Carousel的话需要描述三种状态约束:start、previous 和 next,轮播是双向的,初始状态,每次轮播前和后的状态;
对于一个横向的轮播视图:
![carousel-ex3.png](https://img-blog.csdnimg.cn/img_convert/5719024ac3ccd21beda427ed5169d0b8.png#clientId=u247703ad-81a4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=425&id=ua2989578&margin=[object Object]&name=carousel-ex3.png&originHeight=621&originWidth=1085&originalType=binary&ratio=1&rotation=0&showTitle=false&size=65234&status=done&style=none&taskId=u525d4562-2c01-483f-b949-b93dc32bc04&title=&width=743)
还需要加forward和backward两种转换,用forward对应的Transition表示 start - previous 的向左滑动的过渡动画,用 backward 对应的 Transition 表示 start - next 的向右滑动的过渡动画
<Transition
motion:constraintSetStart="@id/start"
motion:constraintSetEnd="@+id/next"
motion:duration="1000"
android:id="@+id/forward">
<OnSwipe
motion:dragDirection="dragLeft"
motion:touchAnchorSide="left" />
</Transition>
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/previous"
android:id="@+id/backward">
<OnSwipe
motion:dragDirection="dragRight"
motion:touchAnchorSide="right" />
</Transition>
预览视图也会变成这样:
![image.png](https://img-blog.csdnimg.cn/img_convert/5522c0a82920a341b7b7107130269e1a.png#clientId=u247703ad-81a4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=260&id=ue3f7b523&margin=[object Object]&name=image.png&originHeight=260&originWidth=532&originalType=binary&ratio=1&rotation=0&showTitle=false&size=11774&status=done&style=none&taskId=ub37d4c6a-ed7b-479e-a907-74b5b9515c5&title=&width=532)
如实现网上的这个效果:
![](https://img-blog.csdnimg.cn/img_convert/7d8aab05f699b7bd7add1fdee456b50b.gif#clientId=u247703ad-81a4-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u9442c03f&margin=[object Object]&originHeight=712&originWidth=400&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u603c5e18-7f7c-4574-96f3-4dc8e1c91a3&title=)
xml布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/card_motion_scence"
tools:context=".CardMotionActivity">
<ImageView
android:id="@+id/iv0"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0"
android:scaleType="centerCrop"
android:scaleX="0.5"
android:scaleY="0.5"
android:src="@drawable/card1"
android:translationY="-110dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="5:3"
app:layout_constraintLeft_toLeftOf="@+id/gl1"
app:layout_constraintRight_toRightOf="@+id/gl2"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv1"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:scaleX="0.6"
android:scaleY="0.6"
android:src="@drawable/card1"
android:translationY="-90dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="5:3"
app:layout_constraintLeft_toLeftOf="@+id/gl1"
app:layout_constraintRight_toRightOf="@+id/gl2"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv2"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:scaleX="0.7"
android:scaleY="0.7"
android:src="@drawable/card2"
android:translationY="-70dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="5:3"
app:layout_constraintLeft_toLeftOf="@+id/gl1"
app:layout_constraintRight_toRightOf="@+id/gl2"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv3"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:scaleX="0.8"
android:scaleY="0.8"
android:src="@drawable/card3"
android:translationY="-50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="5:3"
app:layout_constraintLeft_toLeftOf="@+id/gl1"
app:layout_constraintRight_toRightOf="@+id/gl2"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv4"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:scaleX="0.9"
android:scaleY="0.9"
android:src="@drawable/card4"
android:translationY="-30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="5:3"
app:layout_constraintLeft_toLeftOf="@+id/gl1"
app:layout_constraintRight_toRightOf="@+id/gl2"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv5"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:scaleX="1.0"
android:scaleY="1.0"
android:src="@drawable/card5"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="5:3"
app:layout_constraintLeft_toLeftOf="@+id/gl1"
app:layout_constraintRight_toRightOf="@+id/gl2"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv6"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:src="@drawable/card5"
app:layout_constraintDimensionRatio="5:3"
app:layout_constraintLeft_toLeftOf="@+id/gl1"
app:layout_constraintRight_toRightOf="@+id/gl2"
app:layout_constraintBottom_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/gl1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.2" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/gl2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.8" />
</androidx.constraintlayout.motion.widget.MotionLayout>
通过设置缩放偏移量达到重叠的效果,
![image.png](https://img-blog.csdnimg.cn/img_convert/1b687c63193ad8d4608f09f86d8c7c0c.png#clientId=u247703ad-81a4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=490&id=ue6d1b16c&margin=[object Object]&name=image.png&originHeight=490&originWidth=305&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23628&status=done&style=none&taskId=u16f6100d-ddfc-4146-b78f-78df393a1db&title=&width=305)
每个ImagView仅仅偏移量和缩放比列不同,在定义描述文件时也通过改变这两个属性设置每个视图的约束即可,
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
android:id="@+id/forward"
motion:constraintSetEnd="@+id/next"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<OnSwipe
motion:dragDirection="dragUp"
motion:touchAnchorSide="left" />
</Transition>
<Transition
android:id="@+id/backward"
motion:constraintSetEnd="@+id/previous"
motion:constraintSetStart="@+id/start">
<OnSwipe
motion:dragDirection="dragDown"
motion:touchAnchorSide="right" />
</Transition>
<ConstraintSet android:id="@+id/previous">
<Constraint
android:id="@+id/iv1"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0"
android:scaleX="0.5"
android:scaleY="0.5"
android:translationY="-110dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv2"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="0.6"
android:scaleY="0.6"
android:translationY="-90dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv3"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="0.7"
android:scaleY="0.7"
android:translationY="-70dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv4"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="0.8"
android:scaleY="0.8"
android:translationY="-50dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv5"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="0.9"
android:scaleY="0.9"
android:translationY="-30dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv6"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="1.0"
android:scaleY="1.0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/iv0"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0"
android:scaleX="0.5"
android:scaleY="0.5"
android:translationY="-110dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv1"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="0.6"
android:scaleY="0.6"
android:translationY="-90dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv2"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="0.7"
android:scaleY="0.7"
android:translationY="-70dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv3"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="0.8"
android:scaleY="0.8"
android:translationY="-50dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv4"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="0.9"
android:scaleY="0.9"
android:translationY="-30dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv5"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="1.0"
android:scaleY="1.0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv6"
android:layout_width="0dp"
android:layout_height="0dp"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2" />
</ConstraintSet>
<ConstraintSet android:id="@+id/next">
<Constraint
android:id="@+id/iv0"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="0.6"
android:scaleY="0.6"
android:translationY="-90dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv1"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="0.7"
android:scaleY="0.7"
android:translationY="-70dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv2"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="0.8"
android:scaleY="0.8"
android:translationY="-50dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv3"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="0.9"
android:scaleY="0.9"
android:translationY="-30dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv4"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleX="1.0"
android:scaleY="1.0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv5"
android:layout_width="0dp"
android:layout_height="0dp"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintDimensionRatio="5:3"
motion:layout_constraintLeft_toLeftOf="@+id/gl1"
motion:layout_constraintRight_toRightOf="@+id/gl2" />
</ConstraintSet>
</MotionScene>
![image.png](https://img-blog.csdnimg.cn/img_convert/be87a330139769aa8e32c1cf6279aa2d.png#clientId=u247703ad-81a4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=453&id=u4dc6e181&margin=[object Object]&name=image.png&originHeight=453&originWidth=332&originalType=binary&ratio=1&rotation=0&showTitle=false&size=15463&status=done&style=none&taskId=uae07ac73-8bdd-4488-b79b-4e3a1145ded&title=&width=332)
在previous约束中把iv6变成start中的iv5位置,iv1变成初始iv0的位置,其余也都是位置互换。
carsoul布局中的设置:
<androidx.constraintlayout.helper.widget.Carousel
android:id="@+id/carousel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:carousel_forwardTransition="@+id/forward" //start - next
app:carousel_backwardTransition="@+id/backward"
app:carousel_nextState="@+id/previous" //next id
app:carousel_previousState="@+id/next"
app:carousel_infinite="true" //是否是无限轮播
app:carousel_firstView="@+id/iv1" //展示第一个数据的视图
app:constraint_referenced_ids="iv0,iv1,iv2,iv3,iv4,iv5,iv6"
/>
设置Adapter
public interface Adapter {
/**
* Number of items you want to display in the Carousel
* @return number of items
*/
int count();
/**
* Callback to populate the view for the given index
*
* @param view
* @param index
*/
void populate(View view, int index);
/**
* Callback when we reach a new index
* @param index
*/
void onNewItem(int index);
}
官方文档提供了一个很酷炫的效果。
Carousel官方实例效果