13336904776 / MotionLayoutSample

MotionLayout的使用

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

关注公众号学习更多知识

简单实现效果

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

使用概述

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所有属性

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所有属性

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>

实现效果

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

比较有用的单个属性使用

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的属性

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

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实现嵌套滑动的效果

示例代码

代码示例

设计图

本例要实现的效果如下:

实现思路

View的结构如上图,实现这个效果需要两个View,绿色的View是一个MotionLayout viewA,红色的View是一个NestScrollView viewBviewA和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会保持相同的动画进度

示例中的代码如图:

实现效果

滑动效果

实现代码(可跳过)

  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>

About

MotionLayout的使用


Languages

Language:Kotlin 100.0%