上一节课,我们用一个将Animation动画综合在一起的例子分析了一下Animation动画框架的实现,这节课也是我们Android动画全解析的最后一课了,讲完这节课,动画分析的章节就完全结束了,下面我们还要继续研究View的实现,我们会以ListView、ViewPager等系统组件为原型,展开我们View全解析的课程。
好了,这节课我们使用的例子是从github上找到的一个例子,写的也是非常好,从这个例子的效果就很明显可以看出Animator框架是比Animation框架更细致的,Animator能实现对动画细节更好的控制,甚至有些绚丽的动画使用Animator能够实现,而使用Animation还无法实现。那么我们就来看一下Animator到底是如何实现一个动画的,这节课我们使用的Demo是,源码下载地址:
我们先来看一下这个动画的效果,点击Overlay with hover按钮,会产生一个类似涟漪的效果,放开之后,涟漪不断扩大,最后消失,下面的Ripples static init按钮的实现原理是一样的,我们就不重复了,只分析一下Overlay with hover的涟漪效果的实现过程就可以了。这个动画的触发是通过onTouch事件来触发的,当我们按下按钮时,会调用MaterialRippleLayout类的onTouchEvent方法,我们来看一下它的实现:
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean superOnTouchEvent = super.onTouchEvent(event);
if (!isEnabled() || !childView.isEnabled()) return superOnTouchEvent;
boolean isEventInBounds = bounds.contains((int) event.getX(), (int) event.getY());
if (isEventInBounds) {
previousCoords.set(currentCoords.x, currentCoords.y);
currentCoords.set((int) event.getX(), (int) event.getY());
}
Log.i(TAG, "ontouchevent, " + event.getAction() + ", " + childView.isEnabled() + ", "
+ hasPerformedLongPress + ", " + isInScrollingContainer());
boolean gestureResult = gestureDetector.onTouchEvent(event);
if (gestureResult || hasPerformedLongPress) {
Log.i(TAG, "if case, " + gestureResult + ", " + hasPerformedLongPress);
return true;
} else {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_UP:
pendingClickEvent = new PerformClickEvent();
if (prepressed) {
childView.setPressed(true);
postDelayed(
new Runnable() {
@Override
public void run() {
childView.setPressed(false);
}
}, ViewConfiguration.getPressedStateDuration());
}
if (isEventInBounds) {
startRipple(pendingClickEvent);
} else if (!rippleHover) {
setRadius(0);
}
if (!rippleDelayClick && isEventInBounds) {
pendingClickEvent.run();
}
cancelPressedEvent();
break;
case MotionEvent.ACTION_DOWN:
setPositionInAdapter();
eventCancelled = false;
pendingPressEvent = new PressedEvent(event);
if (isInScrollingContainer()) {
cancelPressedEvent();
prepressed = true;
postDelayed(pendingPressEvent, ViewConfiguration.getTapTimeout());
} else {
pendingPressEvent.run();
}
break;
case MotionEvent.ACTION_CANCEL:
if (rippleInAdapter) {
// dont use current coords in adapter since they tend to jump drastically on scroll
currentCoords.set(previousCoords.x, previousCoords.y);
previousCoords = new Point();
}
childView.onTouchEvent(event);
if (rippleHover) {
if (!prepressed) {
startRipple(null);
}
} else {
childView.setPressed(false);
}
cancelPressedEvent();
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "action move, " + rippleHover + ", " + isEventInBounds + ", " + eventCancelled);
if (rippleHover) {
if (isEventInBounds && !eventCancelled) {
// invalidate();
} else if (!isEventInBounds) {
startRipple(null);
}
}
if (!isEventInBounds) {
cancelPressedEvent();
if (hoverAnimator != null) {
hoverAnimator.cancel();
}
childView.onTouchEvent(event);
eventCancelled = true;
}
break;
}
return true;
}
}
这里需要说一下,MaterialRippleLayout类是别人封装好的一个view,是继承FrameLayout,Overlay with hover文字是一个Button,是添加到MaterialRippleLayout当中的,这里有父布局,所以重写了一下onInterceptTouchEvent方法,将事件截获下来,因为后边的动画效果就是针对MaterialRippleLayout产生的,如果不截获触摸事件,onInterceptTouchEvent方法默认是返回false的,那么默认就分发到Button上去了,那么onTouchEvent事件无法执行,动画效果也就产生不了了,关于onInterceptTouchEvent方法,可以参考上一篇博客:
Android-onInterceptTouchEvent()和onTouchEvent()总结
在onTouchEvent方法中,我们的触摸会产生三种效果:开始按下MotionEvent.ACTION_DOWN、移动手指或者不动MotionEvent.ACTION_MOVE、松开手指MotionEvent.ACTION_UP,一定要搞清楚,MotionEvent.ACTION_DOWN和MotionEvent.ACTION_UP只会执行一次,哪怕你手指一直按着,DOWN事件也只产生一次。那么在当前动画中,分别使用PressedEvent、PerformClickEvent来产生按下的触摸动画和Click点一直的动画,接收到ACTION_DOWN事件,就构造一个PressedEvent对象,然后直接调用它的run方法,run方法当中是调用startHover()为生产动画的,startHover()方法的实现如下:
private void startHover() {
if (eventCancelled) return;
if (hoverAnimator != null) {
hoverAnimator.cancel();
}
// final float radius = (float) (Math.sqrt(Math.pow(getWidth(), 2) + Math.pow(getHeight(), 2)) * 1.2f);
final float radius = getHeight();
hoverAnimator = ObjectAnimator.ofFloat(this, radiusProperty, rippleDiameter, radius)
.setDuration(HOVER_DURATION);
hoverAnimator.setInterpolator(new LinearInterpolator());
hoverAnimator.start();
Log.i(TAG, "start hover, " + radius);
}
1、ObjectAnimator.ofFloat(this, radiusProperty, rippleDiameter, radius).setDuration(HOVER_DURATION)
先来看一下传入的几个参数,第一个this就代表当前的MaterialRippleLayout了,radiusProperty是作者自己定义的一个Property<MaterialRippleLayout, Float>对象,rippleDiameter和radius是两个整数。我们具体来看一下radiusProperty,它是一个Property对象,Property的类代码在frameworks/base/core/java/android/util包下面:
public abstract class Property
{
private final String mName;
private final Class
mType;
/**
* This factory method creates and returns a Property given the
class
and
*
name
parameters, where the
"name"
paramete