在上一篇文章中我们创建了独立的MotionScene,我们可以通过它来实现我们指定的属性转换,当然这种属性并不仅仅是指位置上的。
实际上,原始的ConstraintSet
只封装了layout布局规则;但是对于丰富的动画,我们经常需要转换其他(并非布局规则)的东西(举个例子,我需要改变背景颜色)。在ConstraintLayout 2.0
中,ConstraintSet
也可以存储自定义属性的状态。在接下来的运动情景中,我们的背景颜色是可以切换的。
很显然,在以前要实现这种动画,你必须在代码中实现。但是现在你在XML中指定相关的自定义直接指定就行了。
<Constraint
android:id="@+id/button" ...>
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#D81B60"/>
Constraint>
我们来看一下XML中的代码怎么写:
<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:interpolator="linear">
<OnSwipe
motion:dragDirection="dragRight"
motion:touchAnchorId="@+id/button"
motion:touchAnchorSide="right" />
Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/button"
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_toTopOf="parent">
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#D81B60" />
Constraint>
ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginEnd="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#9999FF" />
Constraint>
ConstraintSet>
MotionScene>
自定义属性可以通过属性名指定,但是它必须存在getter/setter方法,就像这种:
自定义属性的值也必须指定:(不同的类型的值需要不一样的赋值方式)
最终,当我们定义一个自定义属性时,我们需要在start ConstraintSet
和end Constraint
Set中同时定义自定义属性和自定义属性值。
当我们处理比较复杂的转换时,我们经常需要对图片做一些操作,然后将动画作用到它们图片身上。ConstraintLayout
2.0给我们带来了一个非常易用和实用的类:ImageFilterView (继承自AppCompatImageView)。
我们来做一个两张图片相互交换例子,就像下面:
首先我们需要定义一个有图片的MotionLayout文件:
<android.support.constraint.motion.MotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout"
app:layoutDescription="@xml/scene_04"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.utils.ImageFilterView
android:id="@+id/image"
android:background="@color/colorAccent"
android:src="@drawable/roard"
app:altSrc="@drawable/hoford"
android:layout_width="64dp"
android:layout_height="64dp"/>
android.support.constraint.motion.MotionLayout>
其中说明一下:roard.png图片是左侧人物图像,hoford.png图片是右侧人物图像。
与普通的ImageView相比,ImageFilterView多了一个属性:altSrc,(这个属性就是提供了淡入淡出的功能)
<android.support.constraint.image.ImageFilterView
android:id="@+id/image"
...
android:src="@drawable/roard"
app:altSrc="@drawable/hoford"/>
在MotionScene文件中,我们可以定义一个淡入淡出的属性:
<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:interpolator="linear">
<OnSwipe
motion:dragDirection="dragRight"
motion:touchAnchorId="@+id/image"
motion:touchAnchorSide="right" />
Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/image"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginStart="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="crossfade"
motion:customFloatValue="0" />
Constraint>
ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/image"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginEnd="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="crossfade"
motion:customFloatValue="1" />
Constraint>
ConstraintSet>
MotionScene>
这crossfade
是来自ImageFilterView的自身属性,可以参考源码:
ImageFilterView提供了很多功能:
saturation : 0 = grayscale, 1 = original, 2 = hyper saturated (饱和度 0:灰度 1:原始 2: 超饱和)
contrast : 1 = unchanged, 0 = gray, 2 = high contrast (对比度 1:无改变 0:灰色 2:高对比度)
warmth : 1 = neutral, 2 = warm (red tint), 0.5 = cold (blue tint) (暖色 1:正常的 2:暖色调(偏红色的) 0.5: 冷色调(偏蓝色的))
crossfade (with app:altSrc) 渐入渐出(上面已经说过)
这里有个例子大家展示一下:图片太大,上传不了,我只能压缩一下上传了:(
使用自定义属性来指定图片的饱和度信息,即可完成上面的骚操作:
<CustomAttribute
motion:attributeName="saturation"
motion:customFloatValue="1" />
完整的MotionLayout代码为:
<android.support.constraint.motion.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout"
app:layoutDescription="@xml/scene_05"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.utils.ImageFilterView
android:id="@+id/image"
android:src="@drawable/sunset2"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="300dp" />
android.support.constraint.motion.MotionLayout>
其中scene_05.xml文件为:
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<OnSwipe
motion:touchAnchorId="@+id/image"
motion:touchAnchorSide="top"
motion:dragDirection="dragUp" />
Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="300dp"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="saturation"
motion:customFloatValue="1" />
Constraint>
ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="300dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent">
<CustomAttribute
motion:attributeName="saturation"
motion:customFloatValue="0" />
Constraint>
ConstraintSet>
MotionScene>
MotionLayout的一个非常好的想法就是“静止的状态”,它是由ConstraintSets
实现的。我们由MotionLayout产生的布局将会适应不同的屏幕大小,本质上来说,MotionLayout
就像典型的ConstraintLayout
。
在有些情况在,你可能需要一个(转换过程中的)中间状态。这个状态不是一直保持的,而是某个过程中需要经历的(我的理解为瞬时状态)。我们可以通过轻量级的实现方案Keyframes来插入两个以上的ConstraintSets
。
Keyframes(关键帧)可以被应用到位置上或者自定义属性值上。它会让你在某转换的某一个点上指定这种变化的值。
举个例子,如果你想在转换的25%的过程中,组件变成红色;转换50%的时候,组件上移动,就可以使用Keyframes了。
这里有好几种方式(pathRelative, deltaRelative, parentRelative) 可以帮助你实现关键帧的位置,每种方式的不同,将会在后面的内容中讲到。
简单快速介绍一下关键帧的位置,这里我们指定一下在转换的50%时,目标view移动到屏幕的25%的位置。
<Transition ...>
<KeyFrameSet>
<KeyPosition
motion:keyPositionType="parentRelative"
motion:percentY="0.25"
motion:framePosition="50"
motion:target="@+id/button"/>
KeyFrameSet>
Transition>
<android.support.constraint.motion.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout"
app:layoutDescription="@xml/scene_06"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/button"
android:background="@color/colorAccent"
android:layout_width="64dp"
android:layout_height="64dp" />
android.support.constraint.motion.MotionLayout>
下面是MotionScene文件了,和以前的MotionScene文件差不多,只是区别在于KeyPosition
元素:
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000"
motion:interpolator="linear">
<OnSwipe
motion:touchAnchorId="@+id/button"
motion:touchAnchorSide="right"
motion:dragDirection="dragRight" />
<KeyFrameSet>
<KeyPosition
motion:keyPositionType="parentRelative"
motion:percentY="0.25"
motion:framePosition="50"
motion:target="@+id/button"/>
KeyFrameSet>
Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/button"
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_toTopOf="parent">
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#D81B60"/>
Constraint>
ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginEnd="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#9999FF"/>
Constraint>
ConstraintSet>
MotionScene>
和关键帧位置很相似,我们可以使用KeyAttribute在转换中来指定属性。
举个例子,我们想指定当前对象在转换的50%的时候,可以放大2倍和旋转45度,就像下面这种:
我们可以在keyFrameSet
中使用KeyAttribute
属性了:
<KeyFrameSet>
<KeyAttribute
android:scaleX="2"
android:scaleY="2"
android:rotation="-45"
motion:framePosition="50"
motion:target="@id/button" />
KeyFrameSet>
Motionlayout文件上面的例子中相同,在MotionScene文件中不同点就是KeyFrameSet
中多了一个KeyAttribute
:
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000"
motion:interpolator="linear">
<OnSwipe
motion:touchAnchorId="@+id/button"
motion:touchAnchorSide="right"
motion:dragDirection="dragRight" />
<KeyFrameSet>
<KeyAttribute
android:scaleX="2"
android:scaleY="2"
android:rotation="-45"
motion:framePosition="50"
motion:target="@id/button" />
<KeyPosition
motion:keyPositionType="screenRelative"
motion:percentY="0.2"
motion:framePosition="50"
motion:target="@id/button"/>
KeyFrameSet>
Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/button"
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_toTopOf="parent">
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#D81B60"/>
Constraint>
ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginEnd="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#9999FF"/>
Constraint>
ConstraintSet>
MotionScene>
这篇文章主要阐述了MotionLayout的比较高级的几个功能,使用自定义属性和关键帧可以创建更多引人注目的动画。
本篇源代码都在 https://github.com/googlesamples/android-ConstraintLayoutExamples。
翻译的不好,请多见谅:)
如果你喜欢,请给我一个赞。