5分钟带你学会MotionLayout 第二篇

1、前言

最近在开发中,同事居然对MontionLayout一知半解,那怎么行!百里偷闲写出此文章,一起学习、一起进步。如果写的不好,或者有错误之处,恳请在评论、私信、邮箱指出,万分感谢🙏

希望你在阅读这篇文章的时候,已经对下面的内容熟练掌握了

对了还有ConstraintLayout务必熟练掌握

对了,如果可以,请跟随敲代码,毕竟你脑补的代码,没有编译器。

当然你也可以阅读我的上一篇文章

2、OnSwipe、OnClick

同学们可能发现了,在上篇文章中,我们使用了两个交互动作OnSwipeOnClick

   <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@+id/start"
        motion:duration="1000">
​
        <OnSwipe
            motion:dragDirection="dragEnd"
            motion:touchAnchorId="@+id/button1"
            motion:touchAnchorSide="end" />
        <!--OnClick 用于处理用户点击事件 -->
        <!--targetId 设置触发点击事件的组件 -->
        <!--clickAction 设置点击操作的响应行为,这里是使动画过渡到结束状态 -->
        <OnClick
            motion: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 对象定义了中间状态的属性值。您可以将多个 KeyPositionKeyAttribute 对象组合成一个 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">
​
    <View
        android:id="@+id/button1"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_marginStart="8dp"
        android:background="@color/orange"
        android:text="Button"/>
​
    <View
        android: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">
​
    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@+id/start"
        motion:duration="1000"
        motion:motionInterpolator="linear">
        <OnSwipe
            motion:dragDirection="dragRight"
            motion:touchAnchorId="@+id/button1"
            motion:touchAnchorSide="right" />
​
        <KeyFrameSet>
            <KeyPosition
                motion:framePosition="25"
                motion:keyPositionType="pathRelative"
                motion:motionTarget="@+id/button1"
                motion:percentY="0.1" />
​
            <KeyPosition
                motion:framePosition="75"
                motion:keyPositionType="pathRelative"
                motion:motionTarget="@+id/button1"
                motion:percentY="-0.1" />
        </KeyFrameSet>
​
        <KeyFrameSet>
            <KeyPosition
                motion:framePosition="25"
                motion:keyPositionType="pathRelative"
                motion:motionTarget="@+id/button2"
                motion:percentY="0.3" />
​
            <KeyPosition
                motion:framePosition="75"
                motion:keyPositionType="pathRelative"
                motion:motionTarget="@+id/button2"
                motion:percentY="-0.3" />
            <KeyAttribute
                android:scaleX="2"
                android:scaleY="2"
                android:rotation="-45"
                motion:framePosition="50"
                motion:motionTarget="@id/button2" />
        </KeyFrameSet>
    </Transition>
​
    <ConstraintSet android:id="@+id/start">
        <Constraint
            android: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" />
​
        <Constraint
            android: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">
        <Constraint
            android: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" />
​
​
        <Constraint
            android: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:定义如何计算关键帧的偏差。可以设置为 deltaRelativepathRelativeparentRelative 三种取值之一。使用 deltaRelative 时,关键帧的偏差相对于线性路径计算;使用 pathRelative 时,关键帧的偏差相对于路径计算;使用 parentRelative 时,关键帧的偏差相对于父视图计算。
  • percentXpercentY:定义关键帧在 X 和 Y 轴上的位置。可以设置为 0 到 1 之间的浮点数,表示从开始状态到结束状态之间的相对位置。
  • percentWidthpercentHeight:定义宽度和高度的变化量。可以设置为 0 到 1 之间的浮点数,表示从开始状态到结束状态之间的相对变化量。注意,如果宽度或高度没有变化,则这些属性将没有任何效果。
  • framePosition:定义关键帧在动画中的位置。可以设置为 0 到 100 之间的整数,表示从动画开始到结束之间的相对位置。
  • motionTarget:定义应用此关键帧的运动目标。可以是一个视图或者一个运动场景。
  • transitionEasing:定义关键帧的过渡缓动效果。可以使用 Android 系统中提供的各种缓动函数,比如 easeIn, easeOut 等。
  • pathMotionArc:定义关键帧在路径上的运动方式。可以设置为 nonestartVerticalendVerticalfliprotate 等五种取值之一。
  • curveFit:定义关键帧的插值方式。可以设置为 linearsplinediscrete 等三种取值之一。
  • drawPath:定义是否在编辑器中绘制关键帧路径。
  • sizePercent:定义宽度和高度的百分比。可以设置为 0 到 1 之间的浮点数,表示相对于视图父级的百分比。

因为keyPositionType可以说很多,我会在后面的文章,继续展开。这里我们只使用pathRelative

KeyAttribute还有很多属性,多数都是一看就懂的属性

  • framePosition:定义关键帧在动画中的位置。可以设置为 0 到 100 之间的整数,表示从动画开始到结束之间的相对位置。
  • motionTarget:定义应用此关键帧的运动目标。可以是一个视图或者一个运动场景。
  • transitionEasing:定义关键帧的过渡缓动效果。可以使用 Android 系统中提供的各种缓动函数,比如 easeIn, easeOut 等。
  • curveFit:定义关键帧的插值方式。可以设置为 linearsplinediscrete 等三种取值之一。
  • motionProgress:定义关键帧的运动进度,即从开始状态到结束状态之间的进度百分比。
  • alpha:定义视图的不透明度。可以设置为 0 到 1 之间的浮点数,表示视图的透明度。
  • elevation:定义视图的高度。可以设置为一个浮点数,表示视图的高度。
  • rotationrotationXrotationY:定义视图的旋转角度,可以分别设置 X、Y、Z 轴上的旋转角度。
  • transformPivotXtransformPivotY:定义视图的变换中心点坐标。
  • transformPivotTarget:定义变换中心点的目标视图。
  • transitionPathRotate:定义视图在路径上的旋转角度。
  • scaleXscaleY:定义视图的缩放比例。
  • translationXtranslationYtranslationZ:定义视图的位置偏移量。

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">
​
    <View
        android: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.ImageFilterView
        android: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.ImageFilterView
        android: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">
​
    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@+id/start"
        motion:duration="1000">
        <!--OnClick 用于处理用户点击事件 -->
        <!--targetId 设置触发点击事件的组件 -->
        <!--clickAction 设置点击操作的响应行为,这里是使动画过渡到结束状态 -->
        <OnSwipe
            motion:dragDirection="dragEnd"
            motion:touchAnchorId="@+id/button1"
            motion:touchAnchorSide="end" />
​
    </Transition>
​
    <ConstraintSet android:id="@+id/start">
​
        <Constraint
            android: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">
            <CustomAttribute
                motion:attributeName="backgroundColor"
                motion:customColorValue="#D81B60" />
        </Constraint>
​
        <Constraint
            android: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">
            <CustomAttribute
                motion:attributeName="crossfade"
                motion:customFloatValue="0" />
        </Constraint>
        <Constraint
            android: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" >
            <CustomAttribute
                motion:attributeName="saturation"
                motion:customFloatValue="1" />
        </Constraint>
​
    </ConstraintSet>
​
    <ConstraintSet android:id="@+id/end">
        <Constraint
            android: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">
            <CustomAttribute
                motion:attributeName="backgroundColor"
                motion:customColorValue="#9999FF" />
        </Constraint>
​
        <Constraint
            android: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">
            <CustomAttribute
                motion:attributeName="crossfade"
                motion:customFloatValue="1" />
        </Constraint>
​
        <Constraint
            android: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" >
            <CustomAttribute
                motion: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、感谢

  1. 校稿:ChatGpt
  2. 文笔优化:ChatGpt

作者:AlbertZein
链接:https://juejin.cn/post/7221425945238978615
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值