1、前言
最近在开发中,同事居然对MontionLayout
一知半解,那怎么行!百里偷闲写出此文章,一起学习、一起进步。如果写的不好,或者有错误之处,恳请在评论、私信、邮箱指出,万分感谢🙏
希望你在阅读这篇文章的时候,已经对下面的内容熟练掌握了
- Animated Vector Drawable
- Property Animation framework
- LayoutTransition animations
- Layout transitions with TransitionManager
对了还有ConstraintLayout务必熟练掌握
对了,如果可以,请跟随敲代码,毕竟你脑补的代码,没有编译器。
当然你也可以阅读我的上一篇文章
- 5分钟带你学会MotionLayou 第一篇
- 5分钟带你学会MotionLayou 第二篇
2、OnSwipe、OnClick
同学们可能发现了,在上篇文章中,我们使用了两个交互动作OnSwipe
和OnClick
<Transitionmotion:constraintSetEnd="@+id/end"motion:constraintSetStart="@+id/start"motion:duration="1000">
<OnSwipemotion:dragDirection="dragEnd"motion:touchAnchorId="@+id/button1"motion:touchAnchorSide="end" /><!--OnClick 用于处理用户点击事件 --><!--targetId 设置触发点击事件的组件 --><!--clickAction 设置点击操作的响应行为,这里是使动画过渡到结束状态 --><OnClickmotion:clickAction="transitionToEnd"motion:targetId="@+id/button1" />
</Transition>
这两位直面意思就是点击事件和滑动事件,OnClick的属性是非常简洁的,因为只是个点击事件而已
-
targetId
:点击事件要应用的视图 ID。 -
clickAction
:定义点击事件的行为,可以有以下几种取值:toggle
:切换视图的状态。transitionToEnd
:将视图从当前位置过渡到结束位置。transitionToStart
:将视图从当前位置过渡到开始位置。jumpToEnd
:立即将视图移动到结束位置。jumpToStart
:立即将视图移动到开始位置。
OnSwipe的话,属性就比较多,但是都很直白,各位直接看看,需要用的时候找一找,多用用多看看就记住啦。
-
dragScale
:定义拖拽操作的缩放比例。这个属性通常用于实现一些放大缩小的效果,可以让用户通过手势对视图进行缩放。 -
dragThreshold
:定义拖拽的最小阈值,当拖拽距离小于该值时,视图不会响应拖拽事件。这个属性可以用于控制视图响应拖拽事件的灵敏度。 -
autoCompleteMode
:定义自动完成的模式,可以有以下两种取值:continuousVelocity
:使用连续的速度自动完成。spring
:使用弹簧效果自动完成。
-
maxVelocity
:定义最大速度,当拖拽速度超过该值时,视图将不再响应拖拽事件。 -
maxAcceleration
:定义最大加速度,当拖拽加速度超过该值时,视图将不再响应拖拽事件。 -
springMass
:定义弹簧质量。 -
springStiffness
:定义弹簧刚度。 -
springDamping
:定义弹簧阻尼。 -
springStopThreshold
:定义弹簧停止的阈值,当速度小于该值时,弹簧将停止弹动。 -
springBoundary
:定义弹簧边界,可以有以下几种取值:overshoot
:超出边界时弹簧会继续弹动。bounceStart
:当拖拽到开始位置时弹簧会弹动。bounceEnd
:当拖拽到结束位置时弹簧会弹动。bounceBoth
:当拖拽到开始或结束位置时弹簧会弹动。
-
dragDirection
:定义拖拽方向,可以有以下几种取值:horizontal
:只能水平拖拽。vertical
:只能垂直拖拽。both
:可以水平和垂直拖拽。
-
touchAnchorId
:定义触摸点的锚定视图 ID。 -
touchAnchorSide
:定义触摸点在锚定视图中的位置,可以有以下几种取值:top
:触摸点位于锚定视图的顶部。bottom
:触摸点位于锚定视图的底部。left
:触摸点位于锚定视图的左侧。right
:触摸点位于锚定视图的右侧。center
:触摸点位于锚定视图的中心。
-
rotationCenterId
:定义旋转中心的视图 ID。 -
touchRegionId
:定义触摸区域的视图 ID。 -
limitBoundsTo
:定义限制边界的视图 ID。 -
nestedScrollFlags
:定义嵌套滚动的标志位,可以有以下几种取值:none
:不支持嵌套滚动。disablePostScroll
:禁止滚动结束后的滚动。disableScroll
:禁止滚动。supportScrollUp
:支持向上滚动。
-
moveWhenScrollAtTop
:定义是否在滚动到顶部时允许拖拽。 -
onTouchUp
:定义当手指离开屏幕时的行为,可以有以下几种取值:autoComplete
:自动完成拖拽。autoCompleteToStart
:自动完成拖拽并回到开始位置。autoCompleteToEnd
:自动完成拖拽并回到结束位置。stop
:停止拖拽。decelerate
:减速拖拽。decelerateAndComplete
:减速拖拽并完成拖拽。neverCompleteToStart
:永远不要自动完成到开始位置。neverCompleteToEnd
:永远不要自动完成到结束位置。
对于弹簧效果,单纯是为了更加自然。
以上就是两个的全部(部分?)属性啦,如果你需要更详细的了解,你可以在MotionLayout的资源目录中找到。
3、KeyFrameSet
在输入<符号时,弹出的三个提示,前面两个我们已经了解了。那么第三个KeyFrameSet
是什么意思呢。
在一些情况下,您可能希望在转换视图状态时,具有中间状态,即一个需要经过但不需要停留的状态。您可以指定多于两个 ConstraintSet,但更轻量的方法是使用 Keyframes。
在约束布局中使用 Keyframes,需要定义一个 KeyPosition
对象和一个 KeyAttribute
对象。KeyPosition
对象定义了中间状态的位置,KeyAttribute
对象定义了中间状态的属性值。您可以将多个 KeyPosition
和 KeyAttribute
对象组合成一个 Keyframes
对象,并将其应用于约束布局中的任何属性。
fragment_motion_04_basic.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:id="@+id/ml_container"android:layout_width="match_parent"android:layout_height="match_parent"app:layoutDescription="@xml/motion_layout_04_scene"tools:showPaths="true">
<Viewandroid:id="@+id/button1"android:layout_width="64dp"android:layout_height="64dp"android:layout_marginStart="8dp"android:background="@color/orange"android:text="Button"/>
<Viewandroid:id="@+id/button2"android:layout_width="64dp"android:layout_height="64dp"android:layout_marginStart="8dp"android:background="@color/orange"android:text="Button" />
</androidx.constraintlayout.motion.widget.MotionLayout>
motion_layout_04_scene.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">
<Transitionmotion:constraintSetEnd="@+id/end"motion:constraintSetStart="@+id/start"motion:duration="1000"motion:motionInterpolator="linear"><OnSwipemotion:dragDirection="dragRight"motion:touchAnchorId="@+id/button1"motion:touchAnchorSide="right" />
<KeyFrameSet><KeyPositionmotion:framePosition="25"motion:keyPositionType="pathRelative"motion:motionTarget="@+id/button1"motion:percentY="0.1" />
<KeyPositionmotion:framePosition="75"motion:keyPositionType="pathRelative"motion:motionTarget="@+id/button1"motion:percentY="-0.1" /></KeyFrameSet>
<KeyFrameSet><KeyPositionmotion:framePosition="25"motion:keyPositionType="pathRelative"motion:motionTarget="@+id/button2"motion:percentY="0.3" />
<KeyPositionmotion:framePosition="75"motion:keyPositionType="pathRelative"motion:motionTarget="@+id/button2"motion:percentY="-0.3" /><KeyAttributeandroid:scaleX="2"android:scaleY="2"android:rotation="-45"motion:framePosition="50"motion:motionTarget="@id/button2" /></KeyFrameSet></Transition>
<ConstraintSet android:id="@+id/start"><Constraintandroid:id="@+id/button1"android:layout_width="64dp"android:layout_height="64dp"
android:layout_marginStart="6dp"motion:layout_constraintBottom_toTopOf="@id/button2"motion:layout_constraintStart_toStartOf="parent"motion:layout_constraintTop_toTopOf="parent" />
<Constraintandroid:id="@+id/button2"android:layout_width="64dp"android:layout_height="64dp"
android:layout_marginStart="8dp"motion:layout_constraintBottom_toBottomOf="parent"motion:layout_constraintStart_toStartOf="parent"motion:layout_constraintTop_toBottomOf="@id/button1" /></ConstraintSet>
<ConstraintSet android:id="@+id/end"><Constraintandroid:id="@+id/button1"android:layout_width="64dp"android:layout_height="64dp"android:layout_marginStart="8dp"android:layout_marginEnd="8dp"motion:layout_constraintBottom_toTopOf="@id/button2"motion:layout_constraintEnd_toEndOf="parent"motion:layout_constraintHorizontal_bias="1"motion:layout_constraintStart_toStartOf="parent"motion:layout_constraintTop_toTopOf="parent" />
<Constraintandroid:id="@+id/button2"android:layout_width="64dp"android:layout_height="64dp"android:layout_marginStart="8dp"android:layout_marginEnd="8dp"motion:layout_constraintBottom_toBottomOf="parent"motion:layout_constraintEnd_toEndOf="parent"motion:layout_constraintHorizontal_bias="1"motion:layout_constraintStart_toStartOf="parent"motion:layout_constraintTop_toBottomOf="@id/button1" /></ConstraintSet>
</MotionScene>
效果如下啦。
KeyPosition
还有很多属性,多数都是一看就懂的属性
keyPositionType
:定义如何计算关键帧的偏差。可以设置为deltaRelative
、pathRelative
或parentRelative
三种取值之一。使用deltaRelative
时,关键帧的偏差相对于线性路径计算;使用pathRelative
时,关键帧的偏差相对于路径计算;使用parentRelative
时,关键帧的偏差相对于父视图计算。percentX
、percentY
:定义关键帧在 X 和 Y 轴上的位置。可以设置为 0 到 1 之间的浮点数,表示从开始状态到结束状态之间的相对位置。percentWidth
、percentHeight
:定义宽度和高度的变化量。可以设置为 0 到 1 之间的浮点数,表示从开始状态到结束状态之间的相对变化量。注意,如果宽度或高度没有变化,则这些属性将没有任何效果。framePosition
:定义关键帧在动画中的位置。可以设置为 0 到 100 之间的整数,表示从动画开始到结束之间的相对位置。motionTarget
:定义应用此关键帧的运动目标。可以是一个视图或者一个运动场景。transitionEasing
:定义关键帧的过渡缓动效果。可以使用 Android 系统中提供的各种缓动函数,比如easeIn
,easeOut
等。pathMotionArc
:定义关键帧在路径上的运动方式。可以设置为none
、startVertical
、endVertical
、flip
或rotate
等五种取值之一。curveFit
:定义关键帧的插值方式。可以设置为linear
、spline
或discrete
等三种取值之一。drawPath
:定义是否在编辑器中绘制关键帧路径。sizePercent
:定义宽度和高度的百分比。可以设置为 0 到 1 之间的浮点数,表示相对于视图父级的百分比。
因为keyPositionType可以说很多,我会在后面的文章,继续展开。这里我们只使用pathRelative
KeyAttribute
还有很多属性,多数都是一看就懂的属性
framePosition
:定义关键帧在动画中的位置。可以设置为 0 到 100 之间的整数,表示从动画开始到结束之间的相对位置。motionTarget
:定义应用此关键帧的运动目标。可以是一个视图或者一个运动场景。transitionEasing
:定义关键帧的过渡缓动效果。可以使用 Android 系统中提供的各种缓动函数,比如easeIn
,easeOut
等。curveFit
:定义关键帧的插值方式。可以设置为linear
、spline
或discrete
等三种取值之一。motionProgress
:定义关键帧的运动进度,即从开始状态到结束状态之间的进度百分比。alpha
:定义视图的不透明度。可以设置为 0 到 1 之间的浮点数,表示视图的透明度。elevation
:定义视图的高度。可以设置为一个浮点数,表示视图的高度。rotation
、rotationX
、rotationY
:定义视图的旋转角度,可以分别设置 X、Y、Z 轴上的旋转角度。transformPivotX
、transformPivotY
:定义视图的变换中心点坐标。transformPivotTarget
:定义变换中心点的目标视图。transitionPathRotate
:定义视图在路径上的旋转角度。scaleX
、scaleY
:定义视图的缩放比例。translationX
、translationY
、translationZ
:定义视图的位置偏移量。
4、CustomAttribute
如果仅仅只能动画化我们之前提过的属性,那么MotionLayout
只会显得过于单调,而如果通过代码去动态修改某些状态,那就体现不出MotionLayout
的优势了。
所以CustomAttribute
一个自定义属性集合,用于将自定义属性应用于视图或运动场景。使用 CustomAttribute
,您可以将任何自定义属性与约束布局中的其他属性结合使用,从而实现更加复杂和灵活的布局效果。
CustomAttribute
自定义属性集合中的一些关键属性:
attributeName
:定义自定义属性的名称。。customColorValue
:定义自定义颜色值。可以设置为一个整数,表示颜色值。customDimension
:定义自定义尺寸值。可以设置为一个浮点数,表示尺寸值。customFloatValue
:定义自定义浮点数值。可以设置为任何浮点数。customIntegerValue
:定义自定义整数值。可以设置为任何整数。customStringValue
:定义自定义字符串值。可以设置为任何字符串。
我们来试一下
fragment_motion_05_basic.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:id="@+id/ml_container"android:layout_width="match_parent"android:layout_height="match_parent"app:layoutDescription="@xml/motion_layout_05_scene"tools:showPaths="true">
<Viewandroid:id="@+id/button1"android:layout_width="64dp"android:layout_height="64dp"android:layout_marginStart="8dp"android:background="@color/orange"android:text="Button"app:layout_constraintBottom_toTopOf="@id/button2"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.utils.widget.ImageFilterViewandroid:id="@+id/button2"android:layout_width="64dp"android:layout_height="64dp"android:layout_marginStart="8dp"android:src="@mipmap/icon_menu_emoticon1"app:altSrc="@mipmap/icon_menu_emoticon3"android:text="Button"app:layout_constraintBottom_toTopOf="@id/button3"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/button1" />
<androidx.constraintlayout.utils.widget.ImageFilterViewandroid:id="@+id/button3"android:layout_width="64dp"android:layout_height="64dp"android:layout_marginStart="8dp"android:src="@mipmap/icon_menu_emoticon2"android:text="Button"app:layout_constraintBottom_toTopOf="@id/button4"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/button2" />
</androidx.constraintlayout.motion.widget.MotionLayout>
motion_layout_05_scene.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">
<Transitionmotion:constraintSetEnd="@+id/end"motion:constraintSetStart="@+id/start"motion:duration="1000"><!--OnClick 用于处理用户点击事件 --><!--targetId 设置触发点击事件的组件 --><!--clickAction 设置点击操作的响应行为,这里是使动画过渡到结束状态 --><OnSwipemotion:dragDirection="dragEnd"motion:touchAnchorId="@+id/button1"motion:touchAnchorSide="end" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraintandroid:id="@+id/button1"android:layout_width="64dp"android:layout_height="64dp"android:layout_marginStart="8dp"motion:layout_constraintBottom_toTopOf="@id/button2"motion:layout_constraintStart_toStartOf="parent"motion:layout_constraintTop_toTopOf="parent"><CustomAttributemotion:attributeName="backgroundColor"motion:customColorValue="#D81B60" /></Constraint>
<Constraintandroid:id="@+id/button2"android:layout_width="64dp"android:layout_height="64dp"android:layout_marginStart="8dp"motion:layout_constraintBottom_toTopOf="@id/button3"motion:layout_constraintStart_toStartOf="parent"motion:layout_constraintTop_toBottomOf="@id/button1"><CustomAttributemotion:attributeName="crossfade"motion:customFloatValue="0" /></Constraint><Constraintandroid:id="@+id/button3"android:layout_width="64dp"android:layout_height="64dp"android:layout_marginStart="8dp"motion:layout_constraintBottom_toTopOf="@id/button4"motion:layout_constraintStart_toStartOf="parent"motion:layout_constraintTop_toBottomOf="@id/button2" ><CustomAttributemotion:attributeName="saturation"motion:customFloatValue="1" /></Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end"><Constraintandroid:id="@+id/button1"android:layout_width="64dp"android:layout_height="64dp"android:layout_marginEnd="8dp"motion:layout_constraintBottom_toTopOf="@id/button2"motion:layout_constraintEnd_toEndOf="parent"motion:layout_constraintHorizontal_bias="1"motion:layout_constraintStart_toStartOf="parent"motion:layout_constraintTop_toTopOf="parent"><CustomAttributemotion:attributeName="backgroundColor"motion:customColorValue="#9999FF" /></Constraint>
<Constraintandroid:id="@+id/button2"android:layout_width="64dp"android:layout_height="64dp"android:layout_marginStart="8dp"motion:layout_constraintBottom_toTopOf="@id/button3"motion:layout_constraintEnd_toEndOf="parent"motion:layout_constraintHorizontal_bias="1"motion:layout_constraintStart_toStartOf="parent"motion:layout_constraintTop_toBottomOf="@id/button1"><CustomAttributemotion:attributeName="crossfade"motion:customFloatValue="1" /></Constraint>
<Constraintandroid:id="@+id/button3"android:layout_width="64dp"android:layout_height="64dp"android:layout_marginStart="8dp"motion:layout_constraintBottom_toTopOf="@id/button4"motion:layout_constraintEnd_toEndOf="parent"motion:layout_constraintHorizontal_bias="1"motion:layout_constraintStart_toStartOf="parent"motion:layout_constraintTop_toBottomOf="@id/button2" ><CustomAttributemotion:attributeName="saturation"motion:customFloatValue="0" />
</Constraint></ConstraintSet>
</MotionScene>
值得注意的是
自定义属性的名称必须与View具有相应的setter方法名称相匹配,才能在MotionLayout中使用。例如,如果您想要定义一个名为"customText"的CustomAttribute来控制TextView的文本内容,那么TextView中需要有一个名为为"setCustomText"的方法,以便MotionLayout能够找到相应的setter方法。
因此,只有View具有相应的setter方法的自定义属性才能被使用。如果您想要使用一个View没有的属性,您需要创建一个新的自定义View并实现相应的setter和getter方法。
5、下个篇章
因为篇幅原因,我们先到这,下一篇我们将会深入了解各种使用案例~。
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏
6、感谢
- 校稿:ChatGpt
- 文笔优化:ChatGpt
作者:AlbertZein
链接:https://juejin.cn/post/7221425945238978615
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。