ConstraintLayout2.x

一、简介

Constraint Layout 是最受欢迎的 Jetpack 库之一,ConstraintLayout2.x不仅包含 1.x 版本中的所有功能,还在 Android Studio(4.0+) 中集成了可以直接预览 XML 的工具,甚至可以直接在预览界面中对布局进行编辑。

二、使用

在项目的build.gradle引入constraint-layout(因为google已经弃用support库,建议迁移到androidx下):

implementation 'androidx.constraintlayout:constraintlayout:2.0.4'

三、约束详解

本文只介绍2.x新增部分内容,1.x部分参考:ConstraintLayout1.x

3.1、Flow(2.0中添加)

Flow(androidx.constraintlayout.helper.widget.Flow)允许将一组控件水平或垂直放置,类似于链(Chains),一组控件的引用使用constraint_referenced_ids来设置,id中间使用逗号隔开。

例如,实现一组控件水平展开放置,超出屏幕,自动换行展示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"><Buttonandroid:id="@+id/bt_flow_a"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="A" /><Buttonandroid:id="@+id/bt_flow_b"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="B" /><Buttonandroid:id="@+id/bt_flow_c"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="C" /><Buttonandroid:id="@+id/bt_flow_d"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="D" /><Buttonandroid:id="@+id/bt_flow_e"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="E" /><androidx.constraintlayout.helper.widget.Flowandroid:layout_width="match_parent"android:layout_height="wrap_content"app:constraint_referenced_ids="bt_flow_a,bt_flow_b,bt_flow_c,bt_flow_d,bt_flow_e"app:flow_horizontalGap="20dp"app:flow_verticalGap="20dip"app:flow_wrapMode="aligned"app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

共有配置如下:

  • flow_horizontalStyle = “spread|spread_inside|packed”

    约束所有水平链,下方效果模式使用了 app:flow_wrapMode="aligned"

    • spread效果:
    • spread_inside效果:
    • packed效果:
  • flow_verticalStyle = “spread|spread_inside|packed”

    同横向flow_horizontalStyle

  • flow_horizontalBias = “float

    设置控件所有链的水平偏移,需要app:flow_horizontalStyle="packed"才生效,取值为【0-1】

  • flow_verticalBias = “float

    同flow_horizontalBias

  • flow_horizontalGap = “dimension

    横向间隔

  • flow_verticalGap = “dimension

    纵向间隔

  • flow_horizontalAlign = “start|end”

    水平方向对齐

  • flow_verticalAlign = "top|bottom|center|baseline

    垂直方向对齐.在app:flow_wrapMode="aligned"模式下好像不生效,其他两种模式生效。

flow_wrapMode属性值有三种:

none

这是flow默认模式,创建一个水平或者垂直链,所有引用的视图以一条链的方式进行布局,如果内容溢出则溢出内容不可见:

chain

当出现溢出时,溢出的内容会自动换行,以新的一条链的方式进行布局:

​ 更多配置:

  • flow_firstHorizontalStyle = “spread|spread_inside|packed”

    约束第一条水平链,当有多条链(多行)时,只约束第一条链(第一行),其他链(其他行)不约束

  • flow_lastHorizontalStyle = “spread|spread_inside|packed”
    约束最后一条水平链,当有多条链(多行)时,只约束最后一条链(最后一行),其他链(其他行)不约束

  • flow_firstVerticalStyle = “spread|spread_inside|packed”
    同flow_firstHorizontalStyle

  • flow_lastVerticalStyle = “spread|spread_inside|packed”

    同flow_lastHorizontalStyle

  • flow_firstHorizontalBias = “float

    约束第一条水平链偏移,当有多条链(多行)时,只约束第一条链(第一行),其他链(其他行)不约束

  • flow_lastHorizontalBias = “float

    约束最后一条水平链偏移,当有多条链(多行)时,只约束最后一条链(最后一行),其他链(其他行)不约束

  • flow_firstVerticalBias = “float

    同flow_firstHorizontalBias

  • flow_lastVerticalBias = “float

    同flow_lastHorizontalBiaschain

  • flow_maxElementsWrap

    设置每条链的最大控件的个数

aligned

chain 类似,但是不以行而是以列的方式进行布局:

​ 更多配置同chain

3.2、ImageFilterButton(2.0中添加)、ImageFilterView(2.0中添加)

ImageFilterButton(androidx.constraintlayout.utils.widget.ImageFilterButton)、ImageFilterView(androidx.constraintlayout.utils.widget.ImageFilterView)两个控件基本相同,同ImageView与ImageButton之间的关系

现拿ImageFilterView做简单使用说明:

​ ImageFilterView可对设置的背景图片进行相关的过滤效果设置。

例如:想给一个方形图片设置圆角

<androidx.constraintlayout.utils.widget.ImageFilterViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="20dip"app:round="20dip"android:background="@mipmap/ic_launcher"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" />

相关属性解释:

  • round=“dimension”

    设置图片圆角大小

  • roundPercent=“float”

    设置图片圆角率【0-1】,如果图片是一个正方形,此时roundPercent设置为1,则是显示一个圆。

  • altSrc=“reference”

    通过altSrc设置的资源,允许交叉淡入淡出

  • saturation=“float”

    设置拖的饱和度。0 =灰度,1 =原始,2 =超饱和

  • brightness=“float”

    设置图片的亮度 0 =黑色,1 =原始,2 =两倍的亮度

  • warmth=“float”

    设置图片表观色温。

  • contrast=“float”

    设置图片对比度。1 =不变,0 =灰色,2 =高对比度

  • crossfade=“float”

    改变使用altSrc设置的资源透明度【0-1】,使得底层图片的显示程度

  • overlay=“boolean”

    设置altSrc设置的资源图像是在原始图像上淡入淡出还是与它交叉淡入淡出。默认为true。

3.3、Layer(2.0中添加)

Layer(androidx.constraintlayout.helper.widget.Layer)可以将一些空间设置为一个图层,可对该图层进行背景色、可见性、elevation、padding、补间动画等一些操作

例:将一个Imageview和TextView同时设置一个背景色

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"><androidx.constraintlayout.helper.widget.Layerandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#ff00ff"app:constraint_referenced_ids="iv_layer_img,tv_layer_text"app:layout_constraintBottom_toBottomOf="@id/tv_layer_text"app:layout_constraintLeft_toLeftOf="@id/iv_layer_img"app:layout_constraintRight_toRightOf="@id/iv_layer_img"app:layout_constraintTop_toTopOf="@id/iv_layer_img" /><ImageViewandroid:id="@+id/iv_layer_img"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="8dip"android:src="@mipmap/ic_launcher"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/tv_layer_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="测试"app:layout_constraintLeft_toLeftOf="@id/iv_layer_img"app:layout_constraintRight_toRightOf="@id/iv_layer_img"app:layout_constraintTop_toBottomOf="@id/iv_layer_img" />
</androidx.constraintlayout.widget.ConstraintLayout>

3.4、MockView(2.0中添加)

MockView(androidx.constraintlayout.utils.widget.MockView),可用于对布局进行原型制作,可以绘制标签(默认为视图ID)以及对角线的基本视图,在构建UI时可用作临时模拟视图。

例如:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"><androidx.constraintlayout.utils.widget.MockViewandroid:id="@+id/mv_first"android:layout_width="80dp"android:layout_height="80dp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent" /><androidx.constraintlayout.utils.widget.MockViewandroid:id="@+id/mv_second"android:layout_width="80dp"android:layout_height="80dp"app:layout_constraintLeft_toRightOf="@id/mv_first"app:layout_constraintTop_toBottomOf="@id/mv_first" /></androidx.constraintlayout.widget.ConstraintLayout>

参数详解:

  • mock_label=“string”

    设置中间label文字,默认为控件id

  • mock_labelColor=“color”

    设置label文字颜色

  • mock_labelBackgroundColor=“color”

    设置label背景颜色

  • mock_diagonalsColor=“color”

    设置辅助线颜色

  • mock_showDiagonals=“boolean”

    设置是否显示辅助线

  • mock_showLabel=“boolean”

    设置是否显示label

3.5、MotionLayout(2.0中添加)

MotionLayout(androidx.constraintlayout.motion.widget.MotionLayout)是ConstraintLayout的子类,所以它具有ConstraintLayout所有功能,它能够让开发者给自己的控件更快速的添加动画效果。

MotionLayout布局必须要有一个MotionScene文件,需要在MotionLayout标签中使用app:layoutDescription配置,如果你忘记配置,AndroidSudio会提示你配置,按照提示会自动创建MotionScene文件并配置。

例如:

<?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/activity_motion_layout_scene"app:showPaths="true"><Viewandroid:id="@+id/view_start_status"android:layout_width="50dip"android:layout_height="50dip"android:background="@color/black" /></androidx.constraintlayout.motion.widget.MotionLayout>

activity_motion_layout_scene放置在res/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"><ConstraintSet android:id="@+id/start"><Constraint android:id="@+id/view_start_status" /></ConstraintSet><ConstraintSet android:id="@+id/end"><Constraint android:id="@id/view_start_status" /></ConstraintSet><Transitionapp:constraintSetEnd="@id/end"app:constraintSetStart="@+id/start" />
</MotionScene>

上面代码是按照AndroidStudio提示自动创建的。

app:showPaths="true" true是显示动画运动轨迹,测试使用。

元素描述:

<ConstraintSet>

描述约束集

<Transition>

描述两个状态或约束集之间的过渡

元素属性:

  • id=“reference”

    Transition描述的ID

  • constraintSetStart=“reference”

    使用ConstraintSet描述的用作开始约束或布局文件作为开始约束【例如:@id/start(ConstraintSet描述)或者@layout/start(布局文件)】

  • constraintSetEnd=“reference”

    使用ConstraintSet描述的用作最终约束或布局文件作为最终约束【例如:@id/end(ConstraintSet描述)或者@layout/end(布局文件)】

  • motionInterpolator=“easeInOut|easeIn|easeOut|linear|bounce”

    设置差值器【例如:】

    • linear:线性效果
    • easeIn:缓入效果
    • easeOut:缓出效果
    • easeInOut:缓入缓出效果
    • bounce:弹簧效果
  • duration=“float”

    执行过渡动画所需的时间

  • staggered=“float”

    float类型,交错移动物体的快速方法

  • autoTransition=“none|jumpToStart|jumpToEnd|animateToStart|animateToEnd”

    设置动画自动执行效果,无需用户触发(点击或移动)动画

    • none:默认效果
    • jumpToStart:直接到开始约束效果
    • jumpToEnd:直接到最终约束效果
    • animateToStart:执行过度动画到开始约束的效果
    • animateToEnd:执行过度动画到最终约束的效果
<OnSwipe>

可选参数,增加了对触摸处理的支持,一个<Transition> 标签下可以包含多个<OnSwipe>

  • touchAnchorId=“reference”

    视图的Id,滑动此视图外的区域,能响应滑动效果

  • touchRegionId=“reference”

    视图的Id,滑动此视图内的区域,能响应滑动效果

  • touchAnchorSide=“top|left|right|bottom|middle|start|end”

    用户滑动界面时MotionLayout将尝试在touchAnchorId指定的视图和手指之间保持一个恒定的距离,而此属性指定的是手指和View的哪一侧保持恒定的距离(left、right、top、buttom)

  • maxVelocity=“float”

    目标视图的最大速度,当滑动一定速度,目标视图会按照惯性继续运作,进行先加速后减速运行(默认情况)。如果视图运动过程中加速到了我们设置的最大值,那么就是先加速,然后按最大速度匀速运动,最后再减速运动。

  • dragDirection=“dragUp|dragDown|dragLeft|dragRight|dragStart|dragEnd”

    滑动的方向: dragUp(手指从下往上拖动(↑))、 dragDown(手指从上往下拖动(↓)) 、dragLeft(手指从右往左拖动(←))、 dragRight(手指从左往右拖动(→))

  • maxAcceleration=“float”

    目标视图的最大加速度,如果想让视图运动更快,则加大其值。默认值1.2

  • dragScale=“float”

    控制视图相对于滑动长度的移动距离。默认值1.0(视图移动的距离应与滑动距离一致),当值小于1时,视图移动的距离会远远小于滑动距离(例如dragScale值为0.5 , 如果滑动了2dp,目标视图会移动1dp),当值大于1时,视图移动的距离会大于滑动距离(例如dragScale值为1.5 , 如果滑动了2dp,目标视图会移动3dp)

  • moveWhenScrollAtTop=“boolean”

    boolean类型,如果滑动是滚动的,并且View(例如RecyclerView或NestedScrollView)同时滚动和过渡

<OnClick>

可选参数,增加了对触发处理的支持

元素属性:

  • targetId=“reference”

    设置用来触发过渡动画的那个 View 的 Id

  • clickAction=“toggle|transitionToEnd|transitionToStart|jumpToEnd|jumpToStart”

    点击时视图执行的动作

    • toggle:在 Start场景和 End 场景之间循环的切换。
    • transitionToEnd:过渡到 End 场景
    • transitionToStart:过渡到 Start场景
    • jumpToEnd:不执行过渡动画跳到 End 场景
    • jumpToStart:不执行过渡动画跳到 Start 场景

<KeyFrameSet>

描述一组Key对象,这些对象可以修改约束集之间的动画。

<KeyPosition>

在动画期间控制布局位置

  • motionTarget=“reference”

    修改路径的视图id

  • framePosition=“integer”

    表示动画的进度,取值范围为[0,100]。30就表示动画进度执行30%的地方

  • transitionEasing=“standard|accelerate|decelerate|linear”

    使用的插值器

    standard:标准

    accelerate:加速

    decelerate:减速

    linear:线性的

  • pathMotionArc=“none|startVertical|startHorizontal|flip”

    动画以弧形运行,需要在起始的 ConstraintSet 也要加入pathMotionArc属性,此时关键帧处加入pathMotionArc也能生效

    none:直线运行

    startVertical:纵向弧形

    startHorizontal:横向弧形

    flip:当前弧形翻转

    例如:正常不添加关键帧,只是控制View的约束位置,不设置其他参数,执行效果如下直线效果

此时,在起始的 ConstraintSet 也要加入pathMotionArc属性效果如下:

pathMotionArc="startHorizontal"

pathMotionArc="startVertical

此时加入一个关键帧的效果

<KeyFrameSet><KeyPositionapp:motionTarget="@id/view_start_status"app:framePosition="50"app:keyPositionType="parentRelative"app:percentX="0.5"app:percentY="0.3" />
</KeyFrameSet>

在关键帧处设置pathMotionArc="startHorizontal"

<KeyFrameSet><KeyPositionapp:motionTarget="@id/view_start_status"app:framePosition="50"app:pathMotionArc="startHorizontal"app:keyPositionType="parentRelative"app:percentX="0.5"app:percentY="0.3" />
</KeyFrameSet>

在关键帧处设置pathMotionArc="none"

<KeyFrameSet><KeyPositionapp:motionTarget="@id/view_start_status"app:framePosition="50"app:pathMotionArc="none"app:keyPositionType="parentRelative"app:percentX="0.5"app:percentY="0.3" />
</KeyFrameSet>

在关键帧处设置pathMotionArc="flip

<KeyFrameSet><KeyPositionapp:motionTarget="@id/view_start_status"app:framePosition="50"app:pathMotionArc="flip"app:keyPositionType="parentRelative"app:percentX="0.5"app:percentY="0.3" />
</KeyFrameSet>

  • keyPositionType=“deltaRelative|pathRelative|parentRelative”

    关键点的依赖坐标系坐标系

    详细说明:

    parentRelative:相对父容器

    <KeyFrameSet><KeyPositionapp:motionTarget="@id/view_start_status"app:framePosition="20"app:keyPositionType="parentRelative"app:percentX="0.3"app:percentY="0.1" />
    </KeyFrameSet>
    

    左图为相对与MotionLayout布局建立坐标系,可以看出实际的P点与坐标系中坐标相交点略微偏差。那是因为建立坐标系需要依赖视图本身的中心点为依据,如果你的视图足够小,小到为一个点,此时这种坐标系就是左图情况。而实际上视图不可能那样小,实际状况如右图,依赖控件本身的中心点建立坐标系,此时的P点正好为坐标系中的位置。

    deltaRelative:三角定位

    <KeyFrameSet><KeyPositionapp:motionTarget="@id/view_start_status"app:framePosition="20"app:keyPositionType="deltaRelative"app:percentX="0.3"app:percentY="0.1" />
    </KeyFrameSet>
    

    此方式是以动画起始状态视图中心点为坐标系(0,0)点,动画结束状态为(1,1)建立坐标系,p点为设置的percentX、percentY具体坐标

    pathRelative:相对路径

    <KeyFrameSet><KeyPositionapp:motionTarget="@id/view_start_status"app:framePosition="20"app:keyPositionType="pathRelative"app:percentX="0.3"app:percentY="0.1" />
    </KeyFrameSet>
    

    此方式为以动画起始状态视图中心点(0,0),动画结束状态视图中心点(1,0)建立x轴,x顺时针90度建立y轴,y轴1.0长度与x轴相同。p点为设置的percentX、percentY具体坐标。

  • percentX=“float”

    相对参考系的横向的比例【0-1】

  • percentY=“float”

    相对参考系的纵向的比例【0-1】

  • sizePercent=“float”

    如果视图更改大小,则这将控制大小的增长方式。(对于固定大小的对象,请使用<KeyAttributes> scaleX / Y)

    例如设置一个约束起始宽度、高度为50dip ,终止宽度、高度为100dip,如果不设置sizePercent比例效果如下:

    <?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"><ConstraintSet android:id="@+id/start"><Constraintandroid:id="@+id/view_start_status"android:layout_width="50dip"android:layout_height="50dip"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"/></ConstraintSet><ConstraintSet android:id="@+id/end"><Constraintandroid:id="@id/view_start_status"android:layout_width="100dip"android:layout_height="100dip"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"/></ConstraintSet><Transitionapp:constraintSetEnd="@id/end"app:constraintSetStart="@+id/start"><KeyFrameSet><KeyPositionapp:framePosition="50"app:keyPositionType="deltaRelative"app:motionTarget="@+id/view_start_status"app:percentX="0.9"app:percentY="0.5" /></KeyFrameSet></Transition>
    </MotionScene>
    

​ 如果设置sizePercent为0.3,效果如下:

<KeyPositionapp:framePosition="50"app:sizePercent="0.3"app:keyPositionType="deltaRelative"app:motionTarget="@+id/view_start_status"app:percentX="0.9"app:percentY="0.5" />

​ 如果设置sizePercent为0.9,效果如下:

<KeyPositionapp:framePosition="50"app:sizePercent="0.3"app:keyPositionType="deltaRelative"app:motionTarget="@+id/view_start_status"app:percentX="0.9"app:percentY="0.5" />

  • percentWidth=“float”

    宽度变化的百分比,如果宽度没有变化,则此属性无效。将会覆盖sizePercent。

    例如设置一个约束起始宽度为50dip ,终止宽度为100dip,如果不设置percentWidth比例效果如下:

    <?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"><ConstraintSet android:id="@+id/start"><Constraintandroid:id="@+id/view_start_status"android:layout_width="50dip"android:layout_height="50dip"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"/></ConstraintSet><ConstraintSet android:id="@+id/end"><Constraintandroid:id="@id/view_start_status"android:layout_width="100dip"android:layout_height="50dip"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"/></ConstraintSet><Transitionapp:constraintSetEnd="@id/end"app:constraintSetStart="@+id/start"><KeyFrameSet><KeyPositionapp:framePosition="20"app:keyPositionType="deltaRelative"app:motionTarget="@+id/view_start_status"app:percentX="0.3"app:percentY="0.1" /></KeyFrameSet></Transition>
    </MotionScene>
    

​ 如果设置percentWidth为0.3,效果如下:

<KeyPositionapp:framePosition="20"app:percentWidth="0.3"app:keyPositionType="deltaRelative"app:motionTarget="@+id/view_start_status"app:percentX="0.3"app:percentY="0.1" />

​ 如果设置percentWidth为0.9,效果如下:

<KeyPositionapp:framePosition="20"app:percentWidth="0.3"app:keyPositionType="deltaRelative"app:motionTarget="@+id/view_start_status"app:percentX="0.9"app:percentY="0.1" />

  • percentHeight=“float”

    高度变化的百分比,如果高度没有变化,则此属性无效。将会覆盖sizePercent。具体效果同percentWidth

  • curveFit=“spline|linear”

    设置动画运动路径:spline(曲线,默认)、linear(直线)

    如果不设置curveFit属性或设置curveFit=“spline"(它将是曲线运行):

    <KeyPositionapp:framePosition="50"app:keyPositionType="deltaRelative"app:motionTarget="@+id/view_start_status"app:percentX="0.9"app:percentY="0.5" />
    

​ 如果设置curveFit=“linear”(它将是直线运行)

<KeyPosition
app:framePosition="50"
app:keyPositionType="deltaRelative"
app:curveFit="linear"
app:motionTarget="@+id/view_start_status"
app:percentX="0.9"
app:percentY="0.5" />

  • drawPath=“none|path|pathRelative|deltaRelative|asConfigured|rectangles”

    调试使用,绘制布局动画路径及参考线。

    例如设置drawPath=“pathRelative”

    <KeyPositionapp:framePosition="50"app:sizePercent="0.9"app:drawPath="pathRelative"app:keyPositionType="deltaRelative"app:motionTarget="@+id/view_start_status"app:percentX="0.9"app:percentY="0.5" />
    

​ 设置drawPath=“rectangles”

<KeyPositionapp:framePosition="50"app:sizePercent="0.9"app:drawPath="rectangles"app:keyPositionType="deltaRelative"app:motionTarget="@+id/view_start_status"app:percentX="0.9"app:percentY="0.5" />

​ 设置drawPath=“deltaRelative”

<KeyPositionapp:framePosition="50"app:sizePercent="0.9"app:drawPath="deltaRelative"app:keyPositionType="deltaRelative"app:motionTarget="@+id/view_start_status"app:percentX="0.9"app:percentY="0.5" />

<KeyAttribute>

在动画期间控制布局属性

例如:实现View在移动过程中增加自身的缩放、透明度、旋转等属性的改变

<KeyFrameSet><KeyAttributeandroid:scaleX="0.1"android:scaleY="0.1"app:framePosition="50"app:motionTarget="@id/view_start_status" /><KeyAttributeandroid:alpha="0.1"app:framePosition="50"app:motionTarget="@id/view_start_status" /><KeyAttributeandroid:rotation="-45"app:framePosition="50"app:motionTarget="@id/view_start_status" />
</KeyFrameSet>

  • motionTarget=“reference”

修改属性的视图id

  • framePosition=“integer”

表示动画的进度,取值范围为[0,100]。30就表示动画进度执行30%的地方

常用相关属性参数

  • scaleX=“float”

    视图的宽度变化比例

  • scaleY=“float”

    视图的高度变化比例

  • rotation=“float”

    视图旋转的角度(以度为单位)

  • rotationX

    视图绕x轴旋转的角度(以度为单位)

  • rotationY

    视图绕y轴旋转的角度(以度为单位)

  • alpha=“float”

    视图的透明度变化【0-1】

  • elevation=“dimension”

    视图基于Z轴的高度

  • translationX=“dimension”

    视图在X轴上位移的距离

  • translationY=“dimension”

    视图在Y轴上位移的距离

  • translationZ=“dimension”

    视图在Z轴上位移的距离

<CustomAttribute>

通过反射调用设置的“名称”方法

例如在关键帧处改变背景色

  <KeyAttributeapp:framePosition="0"app:motionTarget="@id/view_start_status"><CustomAttributeapp:attributeName="BackgroundColor"app:customColorValue="#00ffff" /></KeyAttribute><KeyAttributeapp:framePosition="50"app:motionTarget="@id/view_start_status"><CustomAttributeapp:attributeName="BackgroundColor"app:customColorValue="#00ff00" /></KeyAttribute>
</KeyFrameSet>

  • attributeName=“string”

    属性的名称。区分大小写。(BackgroundColor将寻找方法setBackgroundColor(…)

  • customColorValue=“color”

    反射的属性为一个颜色值的属性

  • customIntegerValue=“integer”

    反射的属性为一个integer值的属性

  • customFloatValue=“float”

    反射的属性为一个float值的属性

  • customStringValue=“string”

    反射的属性为一个string值的属性

  • customDimension=“dimension”

    反射的属性为一个dimension值的属性

  • customBoolean=“boolean”

    反射的属性为一个boolean(true/false)值的属性

  • <KeyCycle>

    https://github.com/googlearchive/android-ConstraintLayoutExamples/releases/download/1.0/CycleEditor.jar

    控制动画过程中做周期性

    例如,实现View在移动动画使用正弦函数模式进行属性改变

    <KeyFrameSet><KeyCycleandroid:rotation="45"app:framePosition="50"app:motionTarget="@id/view_start_status"app:waveOffset="0"app:wavePeriod="1"app:waveShape="sin" />
    </KeyFrameSet>
    

  • motionTarget=“reference”

    修改属性的视图id

  • framePosition=“integer”

    表示动画的进度,取值范围为[0,100]。30就表示动画进度执行30%的地方

  • waveOffset=“float”

    偏移值已添加到属性

  • wavePeriod=“float”

    在该区域附近循环的循环数

  • waveShape=“sin|square|triangle|sawtooth|reverseSawtooth|cos|bounce”

    产生设置的波的形状

    sin:正弦波

    <KeyFrameSet><KeyCycleandroid:rotation="45"app:framePosition="50"app:motionTarget="@id/view_start_status"app:waveOffset="0"app:wavePeriod="1"app:waveShape="sin" />
    </KeyFrameSet>
    

    square:方形波

    <KeyFrameSet><KeyCycleandroid:rotation="30"app:framePosition="50"app:motionTarget="@id/view_start_status"app:waveOffset="0"app:wavePeriod="1"app:waveShape="square" />
    </KeyFrameSet>
    

    triangle:三角波

    <KeyFrameSet><KeyCycleandroid:rotation="30"app:framePosition="50"app:motionTarget="@id/view_start_status"app:waveOffset="0"app:wavePeriod="1"app:waveShape="square" />
    </KeyFrameSet>
    

    sawtooth:锯齿波

    <KeyFrameSet><KeyCycleandroid:rotation="30"app:framePosition="50"app:motionTarget="@id/view_start_status"app:waveOffset="0"app:wavePeriod="1"app:waveShape="sawtooth" />
    </KeyFrameSet>
    

reverseSawtooth:反向锯齿波

<KeyFrameSet><KeyCycleandroid:rotation="30"app:framePosition="50"app:motionTarget="@id/view_start_status"app:waveOffset="0"app:wavePeriod="1"app:waveShape="reverseSawtooth" />
</KeyFrameSet>

cos:余弦波

<KeyFrameSet><KeyCycleandroid:rotation="30"app:framePosition="50"app:motionTarget="@id/view_start_status"app:waveOffset="0"app:wavePeriod="1"app:waveShape="cos" />
</KeyFrameSet>

bounce:反弹

<KeyFrameSet><KeyCycleandroid:rotation="30"app:framePosition="50"app:motionTarget="@id/view_start_status"app:waveOffset="0"app:wavePeriod="1"app:waveShape="bounce" />
</KeyFrameSet>

常用相关属性参数、及<CustomAttribute> 参考<KeyAttribute> 即可

<KeyTimeCycle>

控制动画在帧上做周期性

例如:在移动View未移动是也显示周期性动画,当View移动时同时也执行周期性动画

<KeyFrameSet><KeyTimeCycleandroid:rotation="30"app:wavePeriod="1"app:framePosition="50"app:motionTarget="@id/view_start_status"/>
</KeyFrameSet>

这里的参数解释:

  • rotation=“float”

​ 视图旋转的角度(以度为单位)

  • wavePeriod=“float”

    在该区域附近循环的循环数

  • motionTarget=“reference”

    修改属性的视图id

  • framePosition="integer“

    表示动画的进度,取值范围为[0,100]。50就表示动画进度执行50%的地方

​ 其他参数同<KeyCycle>

<KeyTrigger>

在动画过程中的固定点触发回调到代码中

例如: 在动画执行过程中,监听动画执行进度。

实现步骤:

1、定义KeyTrigger

<KeyFrameSet><KeyTriggerapp:framePosition="20"app:motionTarget="@id/tv_text"app:onCross="p0" /><KeyTriggerapp:framePosition="50"app:motionTarget="@id/tv_text"app:onCross="p1" /><KeyTriggerapp:framePosition="80"app:motionTarget="@id/tv_text"app:onCross="p2" />
</KeyFrameSet>

参数解释:

  • motionTarget=“reference”

    目标视图id(这里是自定义视图,因为方法写在了自定义视图里)

  • framePosition=“integer”

    表示动画的进度,取值范围为[0,100]。50就表示动画进度执行50%的地方

  • onCross=“string”

    方法名称,于自定义视图中方法名一一对应。不管动画是正向还是反向,只要到达设置的framePosition 就会执行函数

  • onPositiveCross=“string”

    方法名称,于自定义视图中方法名一一对应。只有正向执行动画是到达设置的framePosition 才会执行函数

  • onNegativeCross=“string”

    方法名称,于自定义视图中方法名一一对应。只有反向执行动画是到达设置的framePosition 才会执行函数

  • triggerSlack=“float”

    如果动画位置未离开framePosition触发点,则不会重复调用触发器(值越大 重复率越低)

  • triggerId=“reference”

    使用此ID回调TransitionListener监听中的onTransitionTrigger中方法

  • motion_postLayoutCollision=“boolean”

    Define motion pre or post layout. Post layout is more expensive but captures KeyAttributes or KeyCycle motions.

  • motion_triggerOnCollision=“reference”

    (id) Trigger if the motionTarget collides with the other motionTarget

2、自定义视图:

class MyText(context: Context, attrs: AttributeSet) : AppCompatTextView(context, attrs) {fun p0() {text = "20%"}fun p1() {text = "50%"}fun p2() {text = "80%"}
}

3、使用自定义视图

<?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:showPaths="true"app:layoutDescription="@xml/activity_motion_layout7_scene"><Viewandroid:id="@+id/view_start_status"android:layout_width="50dip"android:layout_height="50dip"android:background="@color/black" /><com.example.constraintlayout.MyTextandroid:id="@+id/tv_text"android:layout_width="100dip"android:layout_height="50dip"android:textColor="@android:color/white"android:layout_marginBottom="200dip"android:textSize="20sp"android:gravity="center"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintBottom_toBottomOf="parent"android:background="@color/black"/>
</androidx.constraintlayout.motion.widget.MotionLayout>

MotionLayout常用API

  • setDebugMode(int debugMode)

    设置是否运动进行时是否显示运动路径,用来调试动画,与MotionLayout xml中app:motionDebug对应,在xml 也可以使用app:showPaths="true"来控制是否显示运动路径。

    代码中设置可选参数如下:

    public static final int DEBUG_SHOW_NONE = 0;//不显示路径及进度
    public static final int DEBUG_SHOW_PROGRESS = 1;//只显示进度
    public static final int DEBUG_SHOW_PATH = 2;//只显示路径
    

    xml设置可选参数如下:

    <enum name="NO_DEBUG" value="0"/>//不显示路径及进度
    <enum name="SHOW_PROGRESS" value="1"/>//只显示进度
    <enum name="SHOW_PATH" value="2"/>//只显示路径
    <enum name="SHOW_ALL" value="3"/>//既显示路径页显示进度
    
  • loadLayoutDescription(int motionScene)

    通过代码加载MotionScene,对应的xml中属性为app:layoutDescription

  • transitionToStart()

    切换到动画start状态,默认有过渡效果,如果不需要过渡效果,可以通过setProgress(0)

  • transitionToEnd()

    切换到动画end状态,默认有过渡效果,如果不需要过渡效果,可以通过setProgress(1)

  • setProgress(float pos)

    设置动画运动进度【0-1】

  • transitionToState(int id)

    切换到动画某个状态,可以是start或end状态,参数id指的是ConstraintSet标签定义的id

  • setTransitionListener(MotionLayout.TransitionListener listener)

    监听MotionLayout动画执行过程

    public interface TransitionListener {//开始动画时回调void onTransitionStarted(MotionLayout motionLayout,//当前MotionLayout视图int startId,//开始状态的ID  如果未知,则为-1int endId //结束状态的ID  如果未知,则为-1);//动画改变状态时回调void onTransitionChange(MotionLayout motionLayout,//当前MotionLayout视图int startId,//开始状态的ID  如果未知,则为-1int endId,//结束状态的ID  如果未知,则为-1float progress //当前动画进度【0-1】);//完成动画时回调void onTransitionCompleted(MotionLayout motionLayout,//当前MotionLayout视图int currentId //到达状态的ID);//使用<KeyTrigger>中定义了 triggerId,会回调到这里void onTransitionTrigger(MotionLayout motionLayout,//当前MotionLayout视图int triggerId,//使用triggerId设置的IDboolean positive,//正向运动(start-->end)经过此处返回true,反向运动(end-->start)经过此处 返回falsefloat progress //当前动画进度【0-1】);
    }
    

使用AndroidStudio中Motion Editor

Motion Editor(Android Studio 4.0 +) 是一款专门针对 MotionLayout 布局类型所构建的可视化编辑器,通过它可以轻松地创建和预览动画效果。当你在一个包含 MotionLayout 的 XML 文件中选择 Design 或 Split 视图时,AndroidStudio 会自动打开 Motion Editor。你可以使用已在布局编辑器中所熟知的交互方式来编辑布局和 Motion Scene 文件,并可以直接在 Android Studio 预览界面中对动画效果进行预览。

预览面板

预览面板的加入使得在处理动画效果时,能够实现快速编辑并立即获取反馈,当你对动画进行细微调整之后,不用再去重新编译和部署,也能直接预览最终的动画效果。

概览面板

MotionLayout 可以对布局的变化做动画处理,在编辑器中该动画可被指定为 ConstraintSets 中的 Transition 效果,Motion Editor 可以通过 概览面板将这些状态的转变可视化(预览面板),要编辑 ConstraintSet 中的约束,点击 概览面板中相应的选项即可。

.

图中start、end是两个<ConstraintSet> 它们之间有一个 <Transition> 效果,可以通过选择start、end来修改视图的状态

选择面板

选择 面板会根据概览面板中的状态显示相应的控件信息,它有三种显示模式:

  • 选中 概览面板中 Motion Layout 时的模式

    Motion Editor 支持编辑基本的 MotionLayout,当在概览面板中选中 MotionLayout模式之后,你可以选择相应的组件来查看它的约束是否配置正确。

  • 选中概览面板中 ConstraintSet 时的模式

    当在概览面板中选中 ConstraintSet 时,选择面板会以列表的形式列出所有组件,组件旁边的选中图标意味着该组件被当前的 ConstraintSet 所约束(下图选中的是start,也可以选end)。

  • 选中 概览面板中 Transition 时的模式

    当在概览面板中选择 Transition 时,你可以通过动画工具栏来控制动画的播放。当选中某个动画后,点击时间轴上的 ▶按钮,可以预览动画效果。

    ​ 当在概览面板中选择 Transition 时,你可以通过工具栏中添加关键帧来添加关键帧约束

属性面板

这里的属性面板同 Layout Editor 类似的属性面板,可以在这里对Constraint 的可视化效果进行预览,对Motion Scene 文件中视图的所有属性效果进行修改和添加。

当选择概述面板的选中 ConstraintSet 时,选择面板会以列表的形式列出所有组件,你选择具体组件,这时属性面板会展示组件基础可选修改选项供修改或添加。

当选择概述面板的选中 Transition 时,此时属性面板展示<Transition>基础可选属性选项供修改或添加,下方的选择面板会以列表的形式列出所有动画,你选择具体动画,这时属性面板会展示动画基础可选属性选项供修改或添加。

四、案例

仿Android系统通知栏动画效果

  • 布局文件:
<?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"android:background="#EDEDED"app:layoutDescription="@xml/activity_motion_layout8_scene"app:showPaths="false"><androidx.constraintlayout.utils.widget.ImageFilterViewandroid:id="@+id/iv_bg"android:layout_width="0dp"android:layout_height="0dp"android:background="@android:color/white"app:round="8dip" /><androidx.constraintlayout.utils.widget.ImageFilterViewandroid:id="@+id/iv_01"android:layout_width="match_parent"android:layout_height="wrap_content"android:scaleType="center"android:src="@mipmap/p6"app:altSrc="@mipmap/p5" /><androidx.appcompat.widget.AppCompatImageViewandroid:id="@+id/iv_00"android:layout_width="match_parent"android:layout_height="wrap_content"android:scaleType="center"android:src="@mipmap/p3" /><androidx.appcompat.widget.AppCompatTextView1qazxsw21qazxandroid:id="@+id/tv_01"android:layout_width="wrap_content"android:layout_height="wrap_content"android:drawableTop="@mipmap/t1"android:drawablePadding="8dip"android:gravity="center"android:text="无线网络"android:textSize="10sp" /><androidx.appcompat.widget.AppCompatTextViewandroid:id="@+id/tv_02"android:layout_width="wrap_content"android:layout_height="wrap_content"android:drawableTop="@mipmap/t2"android:drawablePadding="8dip"android:gravity="center"android:text="蓝牙"android:textSize="10sp" /><androidx.appcompat.widget.AppCompatTextViewandroid:id="@+id/tv_03"android:layout_width="wrap_content"android:layout_height="wrap_content"android:drawableTop="@mipmap/t3"android:drawablePadding="8dip"android:gravity="center"android:text="勿扰"android:textSize="10sp" /><androidx.appcompat.widget.AppCompatTextViewandroid:id="@+id/tv_04"android:layout_width="wrap_content"android:layout_height="wrap_content"android:drawableTop="@mipmap/t4"android:drawablePadding="8dip"android:gravity="center"android:text="手电筒"android:textSize="10sp" /><androidx.appcompat.widget.AppCompatTextViewandroid:id="@+id/tv_05"android:layout_width="wrap_content"android:layout_height="wrap_content"android:drawableTop="@mipmap/t5"android:drawablePadding="8dip"android:gravity="center"android:text="旋转屏幕"android:textSize="10sp" /><androidx.appcompat.widget.AppCompatTextViewandroid:id="@+id/tv_06"android:layout_width="wrap_content"android:layout_height="wrap_content"android:drawableTop="@mipmap/t6"android:drawablePadding="8dip"android:gravity="center"android:text="省电模式"android:textSize="10sp" /><androidx.appcompat.widget.AppCompatImageViewandroid:id="@+id/iv_02"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@mipmap/p1" /><androidx.appcompat.widget.AppCompatImageViewandroid:id="@+id/iv_04"android:layout_width="wrap_content"android:layout_height="wrap_content"android:scaleType="center"android:src="@mipmap/p7" /><androidx.appcompat.widget.AppCompatImageViewandroid:id="@+id/iv_03"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@mipmap/p2" />
</androidx.constraintlayout.motion.widget.MotionLayout>
  • 动画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"><ConstraintSet android:id="@+id/start"><Constraintandroid:id="@+id/iv_bg"android:layout_width="0dp"android:layout_height="0dp"android:layout_marginLeft="5dip"android:layout_marginTop="5dip"android:layout_marginRight="5dip"app:layout_constraintBottom_toBottomOf="@id/iv_04"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"/><Constraintandroid:id="@+id/iv_00"android:layout_width="match_parent"android:layout_height="40dip"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toBottomOf="@id/iv_01" /><Constraintandroid:id="@+id/iv_01"android:layout_width="match_parent"android:layout_height="50dip"app:layout_constraintTop_toTopOf="parent"><CustomAttributeapp:attributeName="Crossfade"app:customFloatValue="0" /></Constraint><Constraintandroid:id="@+id/iv_02"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="20dip"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toBottomOf="@id/tv_03" /><Constraintandroid:id="@+id/iv_03"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginRight="20dip"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toBottomOf="@id/tv_06" /><Constraintandroid:id="@+id/iv_04"android:layout_width="wrap_content"android:layout_height="wrap_content"android:alpha="1"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toBottomOf="@id/iv_03" /><Constraintandroid:id="@+id/tv_01"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toLeftOf="@id/tv_02"app:layout_constraintTop_toBottomOf="@id/iv_01" /><Constraintandroid:id="@+id/tv_02"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintLeft_toRightOf="@id/tv_01"app:layout_constraintRight_toLeftOf="@id/tv_03"app:layout_constraintTop_toTopOf="@id/tv_01" /><Constraintandroid:id="@+id/tv_03"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintLeft_toRightOf="@id/tv_02"app:layout_constraintRight_toLeftOf="@id/tv_04"app:layout_constraintTop_toTopOf="@id/tv_01" /><Constraintandroid:id="@+id/tv_04"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintLeft_toRightOf="@id/tv_03"app:layout_constraintRight_toLeftOf="@id/tv_05"app:layout_constraintTop_toBottomOf="@id/iv_01" /><Constraintandroid:id="@+id/tv_05"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintLeft_toRightOf="@id/tv_04"app:layout_constraintRight_toLeftOf="@id/tv_06"app:layout_constraintTop_toTopOf="@id/tv_04" /><Constraintandroid:id="@+id/tv_06"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintLeft_toRightOf="@id/tv_05"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="@id/tv_04" /></ConstraintSet><ConstraintSet android:id="@+id/end"><Constraintandroid:id="@+id/iv_bg"android:layout_width="0dp"android:layout_height="0dp"android:layout_marginLeft="5dip"android:layout_marginTop="5dip"android:layout_marginRight="5dip"app:layout_constraintBottom_toBottomOf="@id/iv_04"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"></Constraint><Constraintandroid:id="@+id/iv_00"android:layout_width="match_parent"android:layout_height="40dip"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toBottomOf="@id/iv_01" /><Constraintandroid:id="@+id/iv_01"android:layout_width="match_parent"android:layout_height="50dip"app:layout_constraintTop_toTopOf="parent"><CustomAttributeapp:attributeName="Crossfade"app:customFloatValue="1" /></Constraint><Constraintandroid:id="@+id/iv_02"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="20dip"android:layout_marginTop="20dip"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toBottomOf="@id/tv_04" /><Constraintandroid:id="@+id/iv_03"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="20dip"android:layout_marginRight="20dip"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toBottomOf="@id/tv_06" /><Constraintandroid:id="@+id/iv_04"android:layout_width="wrap_content"android:layout_height="wrap_content"android:alpha="0"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toBottomOf="@id/iv_03" /><Constraintandroid:id="@+id/tv_01"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="20dip"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toLeftOf="@id/tv_02"app:layout_constraintTop_toBottomOf="@id/iv_00"></Constraint><Constraintandroid:id="@+id/tv_02"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintLeft_toRightOf="@id/tv_01"app:layout_constraintRight_toLeftOf="@id/tv_03"app:layout_constraintTop_toTopOf="@id/tv_01" /><Constraintandroid:id="@+id/tv_03"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintLeft_toRightOf="@id/tv_02"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="@id/tv_01" /><Constraintandroid:id="@+id/tv_04"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="30dip"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toLeftOf="@id/tv_05"app:layout_constraintTop_toBottomOf="@id/tv_01" /><Constraintandroid:id="@+id/tv_05"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintLeft_toRightOf="@id/tv_04"app:layout_constraintRight_toLeftOf="@id/tv_06"app:layout_constraintTop_toTopOf="@id/tv_04" /><Constraintandroid:id="@+id/tv_06"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintLeft_toRightOf="@id/tv_05"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="@id/tv_04" /></ConstraintSet><Transitionapp:constraintSetEnd="@id/end"app:constraintSetStart="@+id/start"><KeyFrameSet><KeyAttributeapp:framePosition="0"app:motionTarget="@id/tv_01"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/transparent" /></KeyAttribute><KeyAttributeapp:framePosition="50"app:motionTarget="@id/tv_01"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/transparent" /></KeyAttribute><KeyAttributeapp:framePosition="100"app:motionTarget="@id/tv_01"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/black" /></KeyAttribute><KeyAttributeapp:framePosition="0"app:motionTarget="@id/tv_02"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/transparent" /></KeyAttribute><KeyAttributeapp:framePosition="50"app:motionTarget="@id/tv_02"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/transparent" /></KeyAttribute><KeyAttributeapp:framePosition="100"app:motionTarget="@id/tv_02"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/black" /></KeyAttribute><KeyAttributeapp:framePosition="0"app:motionTarget="@id/tv_03"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/transparent" /></KeyAttribute><KeyAttributeapp:framePosition="50"app:motionTarget="@id/tv_03"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/transparent" /></KeyAttribute><KeyAttributeapp:framePosition="100"app:motionTarget="@id/tv_03"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/black" /></KeyAttribute><KeyAttributeapp:framePosition="0"app:motionTarget="@id/tv_04"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/transparent" /></KeyAttribute><KeyAttributeapp:framePosition="50"app:motionTarget="@id/tv_04"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/transparent" /></KeyAttribute><KeyAttributeapp:framePosition="100"app:motionTarget="@id/tv_04"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/black" /></KeyAttribute><KeyAttributeapp:framePosition="0"app:motionTarget="@id/tv_05"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/transparent" /></KeyAttribute><KeyAttributeapp:framePosition="50"app:motionTarget="@id/tv_05"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/transparent" /></KeyAttribute><KeyAttributeapp:framePosition="100"app:motionTarget="@id/tv_05"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/black" /></KeyAttribute><KeyAttributeapp:framePosition="0"app:motionTarget="@id/tv_06"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/transparent" /></KeyAttribute><KeyAttributeapp:framePosition="50"app:motionTarget="@id/tv_06"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/transparent" /></KeyAttribute><KeyAttributeapp:framePosition="100"app:motionTarget="@id/tv_06"><CustomAttributeapp:attributeName="TextColor"app:customColorValue="@android:color/black" /></KeyAttribute><KeyAttributeandroid:alpha="0"app:framePosition="0"app:motionTarget="@id/iv_00" /><KeyAttributeandroid:alpha="0"app:framePosition="70"app:motionTarget="@id/iv_00" /><KeyAttributeandroid:alpha="1"app:framePosition="100"app:motionTarget="@id/iv_00" /><KeyAttributeandroid:alpha="0"app:framePosition="0"app:motionTarget="@id/iv_02" /><KeyAttributeandroid:alpha="0"app:framePosition="50"app:motionTarget="@id/iv_02" /><KeyAttributeandroid:alpha="1"app:framePosition="100"app:motionTarget="@id/iv_02" /><KeyAttributeandroid:alpha="0"app:framePosition="0"app:motionTarget="@id/iv_03" /><KeyAttributeandroid:alpha="0"app:framePosition="50"app:motionTarget="@id/iv_03" /><KeyAttributeandroid:alpha="1"app:framePosition="100"app:motionTarget="@id/iv_03" /><KeyPositionapp:framePosition="50"app:keyPositionType="pathRelative"app:motionTarget="@id/tv_01"app:percentX="0.4"app:percentY="0" /><KeyPositionapp:framePosition="50"app:keyPositionType="pathRelative"app:motionTarget="@id/tv_02"app:percentX="0.4"app:percentY="0" /><KeyPositionapp:framePosition="50"app:keyPositionType="pathRelative"app:motionTarget="@id/tv_03"app:percentX="0.4"app:percentY="0" /><KeyPositionapp:framePosition="10"app:keyPositionType="deltaRelative"app:motionTarget="@id/tv_04"app:percentX="0.1"app:percentY="0.3" /><KeyPositionapp:framePosition="10"app:keyPositionType="deltaRelative"app:motionTarget="@id/tv_05"app:percentX="0.1"app:percentY="0.3" /><KeyPositionapp:framePosition="10"app:keyPositionType="deltaRelative"app:motionTarget="@id/tv_06"app:percentX="0.1"app:percentY="0.3" /><KeyCycleandroid:rotation="360"app:framePosition="0"app:motionTarget="@id/iv_03"app:waveOffset="0"app:wavePeriod="1"app:waveShape="sin" /></KeyFrameSet><OnSwipe app:dragDirection="dragDown" /></Transition>
</MotionScene>
  • MotionLayout官方文档
  • MotionLayout官方效果示例
  • MotionLayout 管理 motion 和 widget 的动画

ConstraintLayout2.x使用详解相关推荐

  1. 从命令行到IDE,版本管理工具Git详解(远程仓库创建+命令行讲解+IDEA集成使用)

    首先,Git已经并不只是GitHub,而是所有基于Git的平台,只要在你的电脑上面下载了Git,你就可以通过Git去管理"基于Git的平台"上的代码,常用的平台有GitHub.Gi ...

  2. JVM年轻代,老年代,永久代详解​​​​​​​

    秉承不重复造轮子的原则,查看印象笔记分享连接↓↓↓↓ 传送门:JVM年轻代,老年代,永久代详解 速读摘要 最近被问到了这个问题,解释的不是很清晰,有一些概念略微模糊,在此进行整理和记录,分享给大家.在 ...

  3. docker常用命令详解

    docker常用命令详解 本文只记录docker命令在大部分情境下的使用,如果想了解每一个选项的细节,请参考官方文档,这里只作为自己以后的备忘记录下来. 根据自己的理解,总的来说分为以下几种: Doc ...

  4. 通俗易懂word2vec详解词嵌入-深度学习

    https://blog.csdn.net/just_so_so_fnc/article/details/103304995 skip-gram 原理没看完 https://blog.csdn.net ...

  5. 深度学习优化函数详解(5)-- Nesterov accelerated gradient (NAG) 优化算法

    深度学习优化函数详解系列目录 深度学习优化函数详解(0)– 线性回归问题 深度学习优化函数详解(1)– Gradient Descent 梯度下降法 深度学习优化函数详解(2)– SGD 随机梯度下降 ...

  6. CUDA之nvidia-smi命令详解---gpu

    nvidia-smi是用来查看GPU使用情况的.我常用这个命令判断哪几块GPU空闲,但是最近的GPU使用状态让我很困惑,于是把nvidia-smi命令显示的GPU使用表中各个内容的具体含义解释一下. ...

  7. Bert代码详解(一)重点详细

    这是bert的pytorch版本(与tensorflow一样的,这个更简单些,这个看懂了,tf也能看懂),地址:https://github.com/huggingface/pytorch-pretr ...

  8. CRF(条件随机场)与Viterbi(维特比)算法原理详解

    摘自:https://mp.weixin.qq.com/s/GXbFxlExDtjtQe-OPwfokA https://www.cnblogs.com/zhibei/p/9391014.html C ...

  9. pytorch nn.LSTM()参数详解

    输入数据格式: input(seq_len, batch, input_size) h0(num_layers * num_directions, batch, hidden_size) c0(num ...

最新文章

  1. 我的超级大魔王Cookie
  2. Python 学习之中的一个:在Mac OS X下基于Sublime Text搭建开发平台包括numpy,scipy
  3. 【计算机网络】深入浅出网络层(看不懂你来打我.上)
  4. JVM_06 垃圾回收相关概念[ 二 ]
  5. [Debugging]分析博客园提交评论的校验规则
  6. linux系统下安装jdk教程
  7. 系统调用和库函数的区别
  8. windows10配置make命令
  9. 一个传统媒体人转型创业的真实故事
  10. Java嵌入式数据库H2学习总结(二)——在Web应用程序中使用H2数据库
  11. 【报告分享】2022年中国商业十大热点展望.pdf(附下载链接)
  12. Java中间MD5加密算法完整版
  13. 山寨版学子商城——成功上线!
  14. HIT软件构造 软件生命周期 配置管理
  15. 21个免费学习编程的网站
  16. vector的底层实现!(万字长文详解!)
  17. 在Win10的Linux子系统下搭建ESP32的开发环境
  18. 状态压缩:对动态规划进行降维打击
  19. 大数据分析学习Python需要多长时间
  20. 为什么大家要支持微信和支付宝

热门文章

  1. 如何阅读一本专业书?
  2. 李德毅院士:大数据认知
  3. UVa 10603 - Fille
  4. 记录一个在latex中使文章段落中每行两端对齐的方法
  5. 青少年qsnctf [登录试试] 攻略
  6. OPA1612AIDR IC AUDIO 2 CIRCUIT 8SOIC
  7. 微信小程序——用户登录模块服务器搭建
  8. MobaXterm Xwindows打开应用程序模糊、缩放比例不对
  9. SAP中ABAP的生产订单调用BAPI进行投料BAPI_GOODSMVT_CREATE(根据生产订单号进行投料,可以修改物料数量)
  10. 5g网速究竟有多快?比4g快多少?