MotionLayout简单理解使用

简介: MotionLayout简单理解使用

简单实现效果


模拟器录制的,比较卡顿,建议耐心看完

image.png


使用概述


MotionLayout是一个布局,ConstraintLayout的子布局。专门用来实现运动过程中的控件动画,

举例:我们可以通过在布局文件layoutAMotionLayout进行布局,在布局中设置两个子控件TextViewImageView。比如我们想在布局中手指触碰向上滑动的时候让ImageView向上滑动,那么我们需要在xml文件夹下创建一个xml布局文件scenceA,在scenceA中处理滑动。我们会在scenceA xml文件中创建 两个ConstraintSet/Constraint节点,一个节点是动画开始节点表示动画开始前布局的状态start,另一个节点表示动画结束后布局的状态end。当动画完成的时候ImageView会从start状态转换为end状态。


以结束状态进行举例:

<ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@id/iv_0"
            android:layout_width="100dp"
            android:layout_height="100dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:rotation="720"
            app:layout_constraintTop_toTopOf="parent" >
            <CustomAttribute
                app:customDimension="100dp"
                app:attributeName="end"
                />
        </Constraint>
    </ConstraintSet>
复制代码


常用api介绍


可以通过看我的解释入门,更详细细致的文档参加官方文档:

https://developer.android.google.cn/training/constraint-layout/motionlayout/ref?hl=zh_cn


MotionScence

作为动画布局中的根节点


ConstraintSet

可以存放Constraint节点,一般一个动画xml文件中可以有两个ConstraintSet节点A和B,A节点作为动画开始前的状态,B节点作为动画结束时的状态。

例如A节点中控件的宽高是100dp,B节点中动画的宽高是300dp。当动画开始执行后会是一个放大动画,控件的宽高会以动画的形式从100dp放大到300dp。

代码举例:

<ConstraintSet android:id="@+id/start">
        //节点A
        <Constraint
            android:id="@+id/iv_0"
            android:layout_width="10dp"
            android:layout_height="10dp"/>
    </ConstraintSet>
    <ConstraintSet android:id="@+id/end">
        //节点B
        <Constraint
            android:id="@id/iv_0"
            android:layout_width="100dp"
            android:layout_height="100dp"/>
    </ConstraintSet>
复制代码


属性一览

Constraint

每一个Constraint可以为一个控件设置动画效果,例如如下代码:

我们会为我们布局中控件id为iv_0的控件设置宽高和布局约束属性。


Constraint属性

Constraint的属性完全等同于ConstraintLayout的属性,Constraint的属性完全等同于ConstraintLayout的属性,

<Constraint
            android:id="@+id/iv_0"
            android:layout_width="50dp"
            android:layout_height="50dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" >
复制代码


Constraint属性

Constraint的属性完全等同于ConstraintLayout的属性,


Constraint属性

Constraint的属性完全等同于ConstraintLayout的属性,


Transition、onClick、onSwipe

Transition可以搭配多个onClick和onSwpie进行使用

Transition所有属性

image.png


Transition、onSwipe

OnSwipe用于控制滑动中的动画,例如如下代码中,

dragDirection表示控件拖动的方法

touchAnchorId:表示所触碰的控件id是iv_0

touchAnchorSide:表示触碰的方向是从左向右

<Transition
        app:constraintSetEnd="@id/end"
        app:constraintSetStart="@id/start"
        app:duration="2000">
        <OnSwipe
            app:dragDirection="dragRight"
            app:touchAnchorId="@id/iv_0"
            app:touchAnchorSide="right" />
    </Transition>
复制代码


onSwipe所有属性

image.png


Transition、onClick

onClick与onSwipe类似,用法也相仿,表示当我们点击控件的时候动画开始。

不再赘述这里


KeyFrameSet、KeyPosition、KeyAttribute

KeyFrameSet必须位于Transition节点内

KeyFrameSet可以搭配KeyPositionKeyAttribute实现更精确的动画控制

例如我们可以再KeyFrameSet中配置多个KeyPosition来控制动画执行某个百分比对应的运动状态

这里的代码控制实在不能用文字的描述的很好,所以写一些代码和展示一些效果吧


scence代码

布局文件代码:

<?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/anim_3_scene">
    <ImageView
        android:id="@+id/iv_03"
        android:layout_width="50dp"
        android:src="@drawable/apple"
        android:layout_height="50dp"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
复制代码


scence代码:

<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <Transition
        app:constraintSetEnd="@id/end"
        app:duration="500"
        app:constraintSetStart="@+id/start">
        <OnSwipe
            app:dragDirection="dragRight"
            app:touchAnchorId="@id/iv_03"
            app:touchAnchorSide="right" />
        <KeyFrameSet>
            <KeyPosition
                app:framePosition="20"
                app:motionTarget="@id/iv_03"
                app:keyPositionType="pathRelative"
                app:percentY="-0.4"
                />
            <KeyPosition
                app:framePosition="50"
                app:motionTarget="@id/iv_03"
                app:keyPositionType="pathRelative"
                app:percentY="-0.2"
                />
            <KeyPosition
                app:framePosition="80"
                app:motionTarget="@id/iv_03"
                app:percentY="-0.4"
                app:keyPositionType="pathRelative"
                />
        </KeyFrameSet>
    </Transition>
    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@+id/iv_03"
            android:layout_width="50dp"
            android:layout_height="50dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>
    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@id/iv_03"
            android:layout_width="50dp"
            android:layout_height="50dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>
</MotionScene>
复制代码


实现效果

动画会按照如下的规矩运动

image.png


比较有用的单个属性使用


OnSwipe.touchRegionId

可以通过touchRegionId设置触碰被触碰的控件,比如本例中,我们设置app:touchRegionId="@id/iv"那么只有在我们手指触碰再ImaveView时才会有动画效果,否则如果不设置touchRegionId我们触碰全局都可以实现滑动效果。

touchRegionId有个缺点,当我们给某个MotionLayout设置touchRegionId时,它内部的所有子view都无法执行点击事件(down事件直接就在onInterceptTouchEvent中被拦截了,没有可处理的空间)。这个情况可以通过自定义MotionLayout解决,我目前并没有尝试过,项目中使用的时候再去处理。

示例:

<Transition
        app:constraintSetEnd="@id/end"
        app:constraintSetStart="@+id/start">
        <OnSwipe
            app:dragDirection="dragLeft"
            app:touchRegionId="@id/iv"
            app:touchAnchorId="@id/iv"
            app:touchAnchorSide="left" />
    </Transition>
复制代码

布局文件

scence文件


OnSwipe.touchAnchorSide

滑动所固定到的目标视图的一侧。MotionLayout 将尝试在该固定点与用户手指之间保持恒定的距离。可接受的值包括 "left"、"right"、"top" 和 "bottom"。

不过也不能太夸张,比如如果你滑动的方向是向右,但是touchAnchorSide设置top这样仍然会是水平滑动固定到视图的右侧


OnSwip.dragDirection

用户滑动的方向,有四个可选值:"dragLeft"、"dragRight"、"dragUp" 和 "dragDown"


OnClick的属性

比较好理解,所以我直接截图官网内容了

image.png


KeyPosition

大部分内容是copy官网的


motion:motionTarget

其运动由此  控制的视图。


motion:framePosition

1 到 99 之间的整数,用于指定运动序列中视图何时到达此  指定的点。例如,如果 framePosition 为 25,则视图在整个运动路径的四分之一处到达指定点。


motion:percentX、motion:percentY

指定视图应到达的位置。keyPositionType 属性指定如何解释这些值。


motion:keyPositionType

指定如何解释 percentX 和 percentY 值。可能的设置包括:

parentRelative

percentXpercentY 是相对于父视图指定的。X 为横轴,范围从 0(左端)到 1(右端)。Y 为纵轴,其中 0 为顶部,1 为底部。

例如,如果您希望目标视图到达父视图右端中间的某个点,可以将 percentX 设置为 1,将 percentY 设置为 0.5。

deltaRelative

percentXpercentY 是相对于视图在整个运动序列过程中移动的距离指定的。X 为横轴,Y 为纵轴;在这两种情况下,0 为视图在该轴上的起始位置,1 为最终位置。

例如,假设目标视图向上移动 100 dp,然后再向右移动 100 dp,但是您希望视图按以下方式移动:首先在运动的前四分之一部分向上移动 40 dp,然后向上呈弧形移动。为此,请将 framePosition 设置为 25,将 keyPositionType 设置为 deltaRelative,并将 percentY 设置为 -0.4。

pathRelative

X 轴是目标视图在路径范围内移动的方向,其中 0 为起始位置,1 为最终位置。Y 轴垂直于 X 轴,正值位于路径左侧,负值位于右侧;设置一个非零的 percentY 可使视图向一个方向或另一个方向呈弧形运动。因此,视图的初始位置为 (0,0),最终位置为 (1,0)。

例如,假设您希望视图按如下方式移动:在运动序列前半部分的移动距离占总距离的 10%,然后加速移动以覆盖剩余 90% 的距离。为此,请将 framePosition 设置为 50,将 keyPositionType 设置为 pathRelative,并将 percentX 设置为 0.1。


使用MotionLayout实现嵌套滑动的效果


示例代码

代码示例

设计图

本例要实现的效果如下:

image.png


实现思路

View的结构如上图,实现这个效果需要两个View,绿色的View是一个MotionLayoutviewA,红色的View是一个NestScrollViewviewBviewA和viewB同时存在于同一个MotionLayout viewP中。

实现滑动的效果我们需要两个scence,第一个scence  activity_nest_scroll_scene 挂载在viewP中,用来处理我们滑动中viewBviewA向上缩放,viewB向上滑动的实现。

我们的实现效果中还需要在向上滑动的时候将绿色部分的文字由垂直方向排列转换为水平排列。这需要我们同时再给viewA节点中的MotionLayout添加一个scence,这个scence用于处理文字的展示变化


实现代码的关键点

  1. 如果想让header内部嵌套的scence跟随外部的scence联动,则需要设置

app:motionProgress="1"

motionProgress这个属性是关键,

我们需要在外部scence的动画结束状态中将headermotion动画进度设置为1,这样在外部scence动画进行的过程中,内部scence会保持相同的动画进度

示例中的代码如图:

image.png


实现效果

image.png


实现代码(可跳过)

  1. 最外层布局viewP的代码

app:layoutDescription="@xml/activity_nest_scroll_scene"用来给设置header和NestScrollView之间的动画

文件名:activity_nest_scroll.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"
    tools:context=".NestScrollActivity"
    app:layoutDescription="@xml/activity_nest_scroll_scene">
    <include layout="@layout/motion_header"/>
    <androidx.core.widget.NestedScrollView
        android:id="@+id/scrollable"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
      >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/large_text" />
    </androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.motion.widget.MotionLayout>
复制代码


  1. viewP的scence代码(activity_nest_scroll_scene)

文件名:activity_nest_scroll_scene.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"
    xmlns:motion="http://schemas.android.com/apk/res-auto"
    >
    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@+id/start"
        motion:duration="250"
        motion:motionInterpolator="linear">
        <OnSwipe
            motion:dragDirection="dragUp"
            motion:touchAnchorId="@+id/motionLayout"
            motion:touchAnchorSide="bottom" />
    </Transition>
    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@+id/motionLayout"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            app:layout_constraintTop_toTopOf="parent" />
        <Constraint
            android:id="@+id/scrollable"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/motionLayout" />
    </ConstraintSet>
    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/motionLayout"
            android:layout_width="match_parent"
            android:layout_height="56dp"
            app:motionProgress="1"
            app:layout_constraintTop_toTopOf="parent" />
        <Constraint
            android:id="@+id/scrollable"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/motionLayout" />
    </ConstraintSet>
</MotionScene>
复制代码


  1. viewA的代码

这部分代码在第一部分中使用include标签添加到布局中

文件名:motion_header.xml

<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/motionLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#1e376b"
    app:layoutDescription="@xml/scene_17_header"
    app:showPaths="true">
    <TextView
        android:id="@+id/background"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="#0a3"
        android:scaleType="centerCrop"
        />
    <TextView
        android:id="@+id/label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="安安安安卓"
        android:textColor="#FFF"
        android:textSize="32dp"
        android:transformPivotX="0dp"
        android:transformPivotY="0dp" />
    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="200dp" />
</androidx.constraintlayout.motion.widget.MotionLayout>
复制代码


  1. viewA的scence代码

文件名:scene_17_header.xml

<?xml version="1.0" encoding="utf-8"?><!--
  Copyright (C) 2018 The Android Open Source Project
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
       http://www.apache.org/licenses/LICENSE-2.0
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
  -->
<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="dragUp"
            motion:touchAnchorId="@+id/background"
            motion:touchAnchorSide="bottom" />
    </Transition>
    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@id/background"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:alpha="1.0"
            android:scaleX="1.1"
            android:scaleY="1.1"
            motion:layout_constraintTop_toTopOf="parent" />
        <Constraint
            android:id="@id/label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:alpha="0.6"
            android:rotation="-90.0"
            android:translationY="8dp"
            motion:layout_constraintBottom_toBottomOf="@+id/guideline"
            motion:layout_constraintStart_toStartOf="parent" />
        <Constraint
            android:id="@id/guideline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            motion:layout_constraintGuide_begin="200dp" />
    </ConstraintSet>
    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@id/background"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:alpha="0"
            android:translationX="0dp"
            android:translationY="0dp"
            motion:layout_constraintTop_toTopOf="parent" />
        <Constraint
            android:id="@id/label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginBottom="8dp"
            android:rotation="0.0"
            motion:layout_constraintBottom_toBottomOf="@+id/guideline"
            motion:layout_constraintStart_toStartOf="parent" />
        <Constraint
            android:id="@id/guideline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            motion:layout_constraintGuide_begin="56dp" />
    </ConstraintSet>
</MotionScene>



相关文章
|
XML Android开发 数据格式
简单使用BottomSheetBehavior实现底部弹窗
这次带来的是BottomSheetBehavior的简单使用,BottomSheetBehavior是Android Support Library23.2中引入的,它可以轻松实现底部动作条功能。
4440 0
|
5月前
|
存储 机器学习/深度学习 缓存
一看就懂!图解 Kotlin SharedFlow 缓存系统
一看就懂!图解 Kotlin SharedFlow 缓存系统
174 2
|
存储 Android开发 索引
RecyclerView 折叠/展开功能的实现
最近这一两个周都没有怎么更新 QMUI。因为我一直在搞忙于搞微信读书的讲书界面。沉醉于写 bug 和改 bug 之中。
854 0
|
Android开发
Android绘制竖直虚线完美解决方案—自定义View
开发中我们经常会遇到绘制虚线的需求,一般我们使用一个drawable文件即可实现,下面我会先列举常规drawable文件的实现方式。
|
Android开发
玩转AppBarLayout,更酷炫的顶部栏
玩转AppBarLayout,更酷炫的顶部栏
玩转AppBarLayout,更酷炫的顶部栏
|
Android开发
Android 蓝牙状态获取
Android 蓝牙状态获取
512 0
|
算法 Java 编译器
Android NDK开发初试(基础)
首先,什么是NDK开发,听到这个词,我的第一感觉是高大上的,其实倒也没错,NDK在Android开发中属于偏底层的,需要与C++等进行联系,它没有像应用层开发那么灵活,但是作为开发者,我们必须了解其简单的使用,及为什么要使用它?
311 0
|
XML Android开发 数据格式
Android 折叠式布局
Android 折叠式布局
332 0
Android 折叠式布局
|
Java 数据格式 JSON
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to xxxxxx
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to xxxxxx 原因在于转换普通String字符...
8902 0
|
Linux Shell 开发工具
三招教你轻松扩展 git bash 命令(上)(一)
GitBash 是 Windows 系统安装 Git 时默认集成的命令行工具,提供运行 Git 命令的集成环境.
三招教你轻松扩展 git bash 命令(上)(一)