前言
大家在网上查看关于Lottie使用时,其中在讲述它的优点时,就会提到“绑定事件”,例如:Lottie的使用和源码详解,但是从网上并未找到相关的文章。一次我在和同事分享关于Lottie使用时,也被问到了这个问题,于是乎秉着自己动手丰衣足食的原则,查看了源码,写下了这篇文章。
一、试问世间事件是何物?
作为一名Android开发攻城狮,一提到事件首先会想到的可能就是OnClickListener、OnLongClickListener事件等等。所以也就跟着这个思路到Lottie中找相关的函数,结果老天不负有心人,找了好久什么都没有找到。
就在要放弃的时候,突然想到了Lottie官方Demo,git clone后发现其中有一个“Bullseye”的示例。
你通过手势对浅蓝色小圆进行控制,从而达到对图形的控制,看到这个效果,突然恍然大悟,这就是Lottie中口口相传的绑定事件。
它本质上不是View本身提供对动画操作的事件,而是通过对动画中的KeyPath进行操作,从而达到对动画的操作。也就是说需要通过其他的View事件来绑定KeyPath的操作,而从达到绑定事件的效果。
二、绑定事件
为了更好帮助大家对绑定事件的理解,下面我会分解“Bullseye”示例代码,以便更好的讲解。
1、布局代码
<com.airbnb.lottie.samples.views.InterceptingFrameLayout //这是自定义布局主要就是通过它的事件对动画效果的控制
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/containerView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.airbnb.lottie.LottieAnimationView //这个就不用讲了吧,它是Lottie提供的自定义View
android:id="@+id/animationView"
android:layout_width="256dp"
android:layout_height="256dp"
app:lottie_url="https://raw.githubusercontent.com/airbnb/lottie-android/master/LottieSample/src/main/res/raw/bullseye.json"
app:lottie_autoPlay="true"
app:lottie_loop="true"
android:layout_gravity="center"/>
<com.airbnb.lottie.samples.views.BackgroundColorView //这个是浅蓝色的小圆圈
android:id="@+id/targetView"
android:layout_width="16dp"
android:layout_height="16dp"
android:background="#00ddff"
android:layout_gravity="center"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="8dp"
android:text="@string/bullseye_drag_dot"
android:textColor="@color/text_color"/>
</com.airbnb.lottie.samples.views.InterceptingFrameLayout>
其中InterceptingFrameLayout主要是通过ViewDragHelper进行手势识别控制,而BackgroundColorView只是绘制了一个圆,并未做其他操作。
2、关键技术点
Lottie在解析json文件时,最终会把json属性解析成对应的类型Layer,比如说TextLayer、SolidLayer等,以及Layer包含的TransformKeyframeAnimation(关键帧动画),具体请查看:Lottie的使用和源码详解
Lottie对外提供了可以修改某一个属性的回调方法,代码如下:
/**
* Add a property callback for the specified {@link KeyPath}. This {@link KeyPath} can resolve
* to multiple contents. In that case, the callback's value will apply to all of them.
*
* Internally, this will check if the {@link KeyPath} has already been resolved with
* {@link #resolveKeyPath(KeyPath)} and will resolve it if it hasn't.
*/
public <T> void addValueCallback(KeyPath keyPath, T property, LottieValueCallback<T> callback) {
lottieDrawable.addValueCallback(keyPath, property, callback);
}
看到这个方法,大家可能会疑惑,对其中的两个参数产生疑问(property和callback),而且官方并未做解释。我在查看源码得知:
property含义:
它的含义是指想要设置的属性,比如说COLOR、POSITION等等啦,具体请看下面代码块:
public interface LottieProperty {
/** ColorInt **/
Integer COLOR = 1;
Integer STROKE_COLOR = 2;
/** Opacity value are 0-100 to match after effects **/
Integer TRANSFORM_OPACITY = 3;
/** [0,100] */
Integer OPACITY = 4;
/** In Px */
PointF TRANSFORM_ANCHOR_POINT = new PointF();
/** In Px */
PointF TRANSFORM_POSITION = new PointF();
/** In Px */
PointF ELLIPSE_SIZE = new PointF();
/** In Px */
PointF POSITION = new PointF();
ScaleXY TRANSFORM_SCALE = new ScaleXY();
...
}
它的使用就像我们使用的属性动画效果是一样的,设置不同属性的值,达到不同的效果。不过有一点需要注意的,不是所有的属性都可以在不同的Layer使用的。比如说:TextLayer支持的为(LottieProperty.COLOR、LottieProperty.STROKE_COLOR、LottieProperty.STROKE_WIDTH、LottieProperty.TEXT_TRACKING),而SolidLayer支持的为LottieProperty.COLOR_FILTER。所以大家在设置属性的时候一定要分清楚。
callback含义:
它的含义是指设置属性值的回调类,也就是说只需要给它设置相关的值,它会自动调用刷新。它包含了两个方法:
public class LottieValueCallback<T> {
...
@Nullable
public T getValue(LottieFrameInfo<T> frameInfo) {
return value;
}
public final void setValue(@Nullable T value) {
this.value = value;
if (animation != null) {
animation.notifyListeners();
}
}
...
我们只需要调用setValue函数,即可更改属性值。它的子类中包含常用的三个类:LottieRelativeFloatValueCallback,LottieRelativeIntegerValueCallback,LottieRelativePointValueCallback。我们可以使用这些类,对属性进行修改,当然也可以直接使用LottieValueCallback类进行修改。
3、示例讲解:
下面我们需要修改三个关键路径,所以需要新增是三个Callback。json源文件
代码如下:
val largeValueCallback = LottieRelativePointValueCallback(PointF(0f, 0f))
animationView.addValueCallback(KeyPath("First"), LottieProperty.TRANSFORM_POSITION, largeValueCallback)
val mediumValueCallback = LottieRelativePointValueCallback(PointF(0f, 0f))
animationView.addValueCallback(KeyPath("Fourth"), LottieProperty.TRANSFORM_POSITION, mediumValueCallback)
val smallValueCallback = LottieRelativePointValueCallback(PointF(0f, 0f))
animationView.addValueCallback(KeyPath("Seventh"), LottieProperty.TRANSFORM_POSITION, smallValueCallback)
我们通过KeyPath找到名为"First"关键帧,设置LottieProperty.TRANSFORM_POSITION,并通过largeValueCallback对它的属性值进行设置,具体代码如下:
val viewDragHelper = ViewDragHelper.create(containerView, object : ViewDragHelper.Callback() {
override fun tryCaptureView(child: View, pointerId: Int) = child == targetView
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
return top
}
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
return left
}
override fun onViewPositionChanged(changedView: View, left: Int, top: Int, dx: Int, dy: Int) {
totalDx += dx
totalDy += dy
smallValueCallback.setValue(getPoint(totalDx, totalDy, 1.2f))
mediumValueCallback.setValue(getPoint(totalDx, totalDy, 1f))
largeValueCallback.setValue(getPoint(totalDx, totalDy, 0.75f))
}
})
至此整个Lottie的绑定事件讲解完毕,如果大家有任何问题,可以在评论区留言