Android提供了各种功能强大的应用动画的用户界面元素和绘制自定义的二维和三维图形的应用。我们可以大致按照下面分类来学习理解
Animation
安卓框架提供了两个动画系统:属性动画和视图动画。属性动画在一般情况下,是首选的方法来使用,因为它更灵活,并提供更多的功能。除了这两中动画,你可以使用Drawable动画,它允许你加载drawable资源显示一帧接一个。(5.0以后有场景动画,有闲时再提)
Property Animation
Android3.0(11级)上使用,属性动画是可扩展的,并且可以让您的自定义类型的动画属性。
View Animation
视图动画
Drawable Animation
可显示动画涉及drawable资源一个接一个,像一卷胶卷,比如一个进度转圈
2D、3D 图形
当编写一个应用程序时,重要的是要考虑你的图形需求将是什么。不同的图形任务是最好的完成与不同的技术。
Canvas and Drawables
安卓提供了一组视图窗口小部件,为广泛的用户界面提供了通用的功能。您还可以扩展这些小部件来修改他们的外观或行为方式。此外,你可以做你自己的自定义的2D使用各种绘图方法包含在画布上绘制或创建诸如纹理、按钮等可画的对象
Hardware Acceleration
从3.0开始,您可以使用硬件加速功能,大多数绘图画布api都会采用,可以进一步提高其性能。
OpenGL
openGL开发暂不涉及。
属性动画系是一个强大的框架,允许你做几乎任何事情。你可以定义一个动画在时间的任何对象的属性变化,无论绘制屏幕或属性动画更改属性的(一个对象中的字段)在指定的时间值。做某事,你指定你希望动画化的对象属性。属性动画可以定义以下几个动画的特点:
持续时间
你可以指定动画的持续时间。默认长度为300毫秒。
时间插值
您可以指定该属性的值计算为一个动画的当前时间功能。
重复计数和行为
你可以指定是否有动画重复当它达到一个持续时间结束,多少次重复的动画。您还可以指定是否希望动画反向回放。设置为反向播放动画向前然后向后地重复,直到到达重复次数。
动画设置
你可以动画成逻辑组,一起玩或顺序或在指定的延迟。
帧刷新延迟
可以指定多长时间刷新你的动画帧。默认设置为每10毫秒刷新,但速度在你的应用程序可以刷新帧最终依赖于系统的整体是如何忙碌,如何快速的系统服务基础计时器
属性动画是怎么工作的呢?首先,我们通过一个动画一个简单的例子。下图描述了一个假想的对象,动画以其X属性,这代表它在屏幕上的水平位置。动画的持续时间设置为40毫秒和移动的距离是40像素。每10毫秒,这是默认的帧刷新率,物体水平移动10像素。在40ms,动画停止,和物体两端在水平位置40。这是一个带线性插值的动画例子:在一个恒定的速度运动的目标。
你也可以指定有一个非线性插值动画。下图说明了一个假设的对象,在动画开始时加速,然后减速动画结束时。对象还是移动40像素在40毫秒,但非线性。在开始的时候,这个动画加速到中途点,然后从中途点减速直到动画结束。如图2所示,距离在开始和结束的动画是小于在中间。
以上内容属于补脑,并没多少暖用,下面正式进入主题。如果你对下面内容看的头晕目眩,亲你该出去走两步,吹吹风然后回来继续战斗了。
public interface Interpolator extends TimeInterpolator {
}
TimeInterpolator (插值器)是 android.animation包下的一个接口
public interface TimeInterpolator {
float getInterpolation(float input);
}
参数input是一个0到1之间的数。但是返回的值是可以大于1,也是可以小于0的
Interpolator的实现基类抽象类BaseInterpolator,该抽象类的mChangingConfiguration变量,来自Resource资源配置的value,通过TypeArray 获取value值对它进行了赋值
/**
* An abstract class which is extended by default interpolators.
*/
abstract public class BaseInterpolator implements Interpolator {
private int mChangingConfiguration;
/**
* @hide
*/
public int getChangingConfiguration() {
return mChangingConfiguration;
}
/**
* @hide
*/
void setChangingConfiguration(int changingConfiguration) {
mChangingConfiguration = changingConfiguration;
}
}
...............................................................
TypedArray a;
//....略.....
setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
系统定义了BaseInterpolator众多子类
这些子类的区别在于算法getInterpolation方法和AttributeSet的属性解析,这里就不一一理解,找个简单点的看看吧
/**
* An interpolator where the rate of change is constant
*/
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createLinearInterpolator();
}
}
@HasNativeInterpolator 、NativeInterpolatorFactory 、NativeInterpolatorFactoryHelper这些源码可以在https://github.com/android/platform_frameworks_base/tree/master/core/java/com/android/internal/view/animation这里查看,NativeInterpolatorFactoryHelper工厂类定义了多种创建Interpolator native方法
public final class NativeInterpolatorFactoryHelper {
private NativeInterpolatorFactoryHelper() {}
public static native long createAccelerateDecelerateInterpolator();
public static native long createAccelerateInterpolator(float factor);
public static native long createAnticipateInterpolator(float tension);
public static native long createAnticipateOvershootInterpolator(float tension);
public static native long createBounceInterpolator();
public static native long createCycleInterpolator(float cycles);
public static native long createDecelerateInterpolator(float factor);
public static native long createLinearInterpolator();
public static native long createOvershootInterpolator(float tension);
public static native long createLutInterpolator(float[] values);
}
注解解释器和native代码暂不涉及了,博主不懂native ,解释器略懂。这里推荐一个blog关于插值器的:
http://blog.csdn.net/hpu_zyh/article/details/46995371
上面已经大概了解过Interpolator,下面再来看看Animation抽象类。
public abstract class Animation implements Cloneable {
}
实现了Cloneable具有了原型拷贝功能
@Override
protected Animation clone() throws CloneNotSupportedException {
final Animation animation = (Animation) super.clone();
animation.mPreviousRegion = new RectF();
animation.mRegion = new RectF();
animation.mTransformation = new Transformation();
animation.mPreviousTransformation = new Transformation();
return animation;
}
两个构造函数,如果有AttributeSet参数,会多一步操作读取Animation相关的自定义属性值,例如:Duration、startOffset、fillAfter等属性。最后都会检查Interpolator,如果interpolator为空,则会默认一个加速减速插值器
/**
* Gurantees that this animation has an interpolator. Will use
* a AccelerateDecelerateInterpolator is nothing else was specified.
*/
protected void ensureInterpolator() {
if (mInterpolator == null) {
mInterpolator = new AccelerateDecelerateInterpolator();
}
}
上面提到的Animation自定义的AttributeSet,让我们可以在Resource下资源文件里面定义,还可以通过代码设置他们(当然一般情况不会用到它们)
Animation里面还有个核心玩意儿AnimationListener
public static interface AnimationListener {
void onAnimationStart(Animation animation);
void onAnimationEnd(Animation animation);
void onAnimationRepeat(Animation animation);
}
见名知其意,就不过多解释方法含义了,这些接口定义的函数是怎么回调的呢,这里就让人有点凌乱了,先补脑View 动画流程:
图形变换通过矩阵实现,每种变换都是一次矩阵运算。Canvas类包含当前矩阵,当前调用Canvas.drawBitmap(bm,x,y,Paint)绘制时,android会先把bitmap做一次矩阵运算,然后将运算结果显示在Canvas上。这样只需不断修改Canvas的矩阵并刷新屏幕,View里对象就会不停的做图形变换,动画就形成了。
1.View创建动画对象,设置动画属性,调用invalidate刷新屏幕,启动动画
2.invalidate方法触发了onDraw函数
在onDraw函数中:
调用Animation的getTransformation方法,得到当前时间点的矩阵
将该矩阵设置成Canvas的当前矩阵
调用canvas的drawBitmap方法,绘制屏幕。
判断getTransformation的返回值,若为真,调用invalidate方法,刷新屏幕进入下一帧;若为假,说明动画完成。
以上补脑只是来自博客:
http://blog.csdn.net/startfromweb/article/details/7644405
通过上门知识点了解到调用Animation的getTransformation函数,跟进函数发现一下三个方法调用:
fireAnimationStart
fireAnimationEnd
fireAnimationRepeat
public boolean getTransformation(long currentTime, Transformation outTransformation) {
//...........略过.......................
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
if (!mStarted) {
fireAnimationStart();
mStarted = true;
if (USE_CLOSEGUARD) {
guard.open("cancel or detach or getTransformation");
}
}
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
applyTransformation(interpolatedTime, outTransformation);
}
if (expired) {
if (mRepeatCount == mRepeated) {
if (!mEnded) {
mEnded = true;
guard.close();
fireAnimationEnd();
}
} else {
if (mRepeatCount > 0) {
mRepeated++;
}
if (mRepeatMode == REVERSE) {
mCycleFlip = !mCycleFlip;
}
mStartTime = -1;
mMore = true;
fireAnimationRepeat();
}
}
//...............略...................
return mMore;
}
这个方法关于计算动画相关的理解如有所需要请参考这两篇blog:
// "http://www.jianshu.com/p/fcd9c7e9937e"
// "http://www.w2bc.com/Article/10394"
上面提到的三个方法都类似,这里以fireAnimationStart()为例,来看看源代码块
private void fireAnimationStart() {
if (mListener != null) {
if (mListenerHandler == null) mListener.onAnimationStart(this);
else mListenerHandler.postAtFrontOfQueue(mOnStart);
}
}
如果AnimationListener不为空,就直接回调相对应的函数,否则通过handler.postAtFrontOfQueue(Runnable)实现,这里再次补脑:
handler post 将一个动作入队安排在非当前线程执行。调度消息是通过一系列的post方法和sendMessage方法
而post方法允许你向消息队列中放入一些Runnable对象,在它们被接收到的时候会被调用
(实际上post方法也就是将runnable对象包装在消息里,然后再通过sendMessage方法实现)
接着看mOnStart Runnable初始化的地方吧
public void setListenerHandler(Handler handler) {
if (mListenerHandler == null) {
mOnStart = new Runnable() {
public void run() {
if (mListener != null) {
mListener.onAnimationStart(Animation.this);
}
}
};
mOnRepeat = new Runnable() {
public void run() {
if (mListener != null) {
mListener.onAnimationRepeat(Animation.this);
}
}
};
mOnEnd = new Runnable() {
public void run() {
if (mListener != null) {
mListener.onAnimationEnd(Animation.this);
}
}
};
}
mListenerHandler = handler;
}
找到这些后问题又来了,setListenerHandler方法在Animation没发现主动调用相关子类也没发现,这又是 在哪里初始化的呢?带着疑问来到了View类与动画相关的draw相关方法
public boolean draw(...){
//............略..............
final Animation a = getAnimation();
if (a != null) {
more = drawAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
} else {
//...........略...............
}
继续跟进drawAnimation方法
private boolean drawAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
Transformation invalidationTransform;
final int flags = parent.mGroupFlags;
final boolean initialized = a.isInitialized();
if (!initialized) {
a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
onAnimationStart();
}
final Transformation t = parent.getChildTransformation();
boolean more = a.getTransformation(drawingTime, t, 1f);
//.......略..............
}
终于发现是三个函数调用:setListenerHandler 、getTransformation、initialize,一下都明了了,也明白了getTransformation函数是在onAnimationStart后调用。
在了解了Animation与View相关的调用流程之后,我们再来看看它的子类,主要区别在语TypeArray解析Attribute、initialize、applyTransformation三个函数,关于applyTransformation函数在哪里有调用到请补脑上面的getTransformation方法块。
protected void applyTransformation(float interpolatedTime, Transformation t) {
}
ScaleAnimation
AlphaAnimation
TranslateAnimation
RotateAnimation
以上四类动画什么意思就不用多说了吧,简单看看ScaleAnimation吧
public class ScaleAnimation extends Animation {
private final Resources mResources;
private float mFromX;
private float mToX;
private float mFromY;
private float mToY;
private int mFromXType = TypedValue.TYPE_NULL;
private int mToXType = TypedValue.TYPE_NULL;
private int mFromYType = TypedValue.TYPE_NULL;
private int mToYType = TypedValue.TYPE_NULL;
private int mFromXData = 0;
private int mToXData = 0;
private int mFromYData = 0;
private int mToYData = 0;
private int mPivotXType = ABSOLUTE;
private int mPivotYType = ABSOLUTE;
private float mPivotXValue = 0.0f;
private float mPivotYValue = 0.0f;
private float mPivotX;
private float mPivotY;
/**
* Constructor used when a ScaleAnimation is loaded from a resource.
*
* @param context Application context to use
* @param attrs Attribute set from which to read values
*/
public ScaleAnimation(Context context, AttributeSet attrs) {
super(context, attrs);
mResources = context.getResources();
//..........略.............
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.ScaleAnimation);
TypedValue tv = a.peekValue(
com.android.internal.R.styleable.ScaleAnimation_fromXScale);
mFromX = 0.0f;
tv = a.peekValue(
com.android.internal.R.styleable.ScaleAnimation_fromYScale);
mFromY = 0.0f;
if (tv != null) {
if (tv.type == TypedValue.TYPE_FLOAT) {
// This is a scaling factor.
mFromY = tv.getFloat();
} else {
mFromYType = tv.type;
mFromYData = tv.data;
}
}
//..........略.............
a.recycle();
initializePivotPoint();
}
public ScaleAnimation(float fromX, float toX, float fromY, float toY,
int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) {
mResources = null;
mFromX = fromX;
//..........略.............
}
private void initializePivotPoint() {
//..........略.............
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float sx = 1.0f;
float sy = 1.0f;
float scale = getScaleFactor();
if (mFromX != 1.0f || mToX != 1.0f) {
sx = mFromX + ((mToX - mFromX) * interpolatedTime);
}
if (mFromY != 1.0f || mToY != 1.0f) {
sy = mFromY + ((mToY - mFromY) * interpolatedTime);
}
if (mPivotX == 0 && mPivotY == 0) {
t.getMatrix().setScale(sx, sy);
} else {
t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
}
}
//..........略.............
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mFromX = resolveScale(mFromX, mFromXType, mFromXData, width, parentWidth);
//............略............
}
}
通过上面这个类applyTransformation()方法再此证明了View动画本质就是Matrix变化,刷新视图。
AnimationSet 就是动画集,里面可以放上面提到的四种动画。提供addAnimation方法添加到集合
AnimationSet的getTransformation方法则遍历集合的每个Animation,调用Animation自身的
getTransformation方法,applyTransformation方法自然也就各自调用自己的实现。
走马观花看过Interpolator和Animation,我们还剩下四各类,Transformation就忽略吧,暂时楼主道不出什么有价值的东西。
LayoutAnimationController和它子类是应用于ViewGroup,可以让viewGroup的childView按照一定的规则显示动画。该类自定义属性有:
delay
动画播放的延迟时间
animationOrder
子view播放动画的顺序
interpolator
插值器
animation
view动画
animationOrder定义的播放顺序有三种:顺序、倒叙、随机,默认顺序播放。
public static final int ORDER_NORMAL = 0;
public static final int ORDER_RANDOM = 2;
public static final int ORDER_REVERSE = 1;
基本的属性解析和set get 方法,以及判断动画是否结束
public boolean isDone() {
return AnimationUtils.currentAnimationTimeMillis() >
mAnimation.getStartTime() + mMaxDelay + mDuration;
}
isDone方法在ViewGroup内会调用到用于更新flag、刷新视图、AnimationListener回调
if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
mLayoutAnimationController.isDone() && !more) {
mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
final Runnable end = new Runnable() {
public void run() {
notifyAnimationListener();
}
};
post(end);
}
这里呢不对LayoutAnimationControllerh和他子类GridLayoutAnimationController的源码做过多理解,关于xml布局调用和代码调用这个动画的用法更详细资料请参考这篇优质博客:
http://blog.csdn.net/crazy1235/article/details/50612827
“ android:layoutAnimation ”这个属性只能作用于ViewGroup,这是ViewGroup自定义的属性
case R.styleable.ViewGroup_layoutAnimation:
int id = a.getResourceId(attr, -1);
if (id > 0) {
setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id));
}
break;
这里通过AnimationUtils解析得到Animation,但是Animation怎么关联到childView的呢,带着疑问我们来到ViewGroup的一个方法块
private void bindLayoutAnimation(View child) {
Animation a = mLayoutAnimationController.getAnimationForView(child);
child.setAnimation(a);
}
view绑定Animation又是在哪里触发的呢?这里又涉及到draw的绘制流程,这里知识补脑来自于
http://blog.csdn.net/qinjuning/article/details/7110211/
由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不
会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该
视图需要重绘时,就会为该View添加该标志位。
调用流程 :
mView.draw()开始绘制,draw()方法实现的功能如下:
1 、绘制该View的背景
2 、为显示渐变框做一些准备操作,大多数情况下,不需要改渐变框
3、调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)
4.1 dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法
于是乎我们来到dispatchDraw方法,果不其然发现了bindLayoutAnimation()方法的调用
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
final boolean buildCache = !isHardwareAccelerated();
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
}
//..................略...........................
}
view视图动画源码基本告一段落,就剩下AnimationUtils类了,上面提到的Interpolator和animation都可以在Resource anim 定义动画,具体怎么玩就不细说了,自行参考链接
http://blog.csdn.net/crazy1235/article/details/50612827
上图所示前面四个方法,最终都会调用到createAnimationFromXml,下面来看源代码块
private static Animation createAnimationFromXml(Context c, XmlPullParser parser,
AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {
Animation anim = null;
// Make sure we are on a start tag.
int type;
int depth = parser.getDepth();
while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
&& type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
if (name.equals("set")) {
anim = new AnimationSet(c, attrs);
createAnimationFromXml(c, parser, (AnimationSet)anim, attrs);
} else if (name.equals("alpha")) {
anim = new AlphaAnimation(c, attrs);
} else if (name.equals("scale")) {
anim = new ScaleAnimation(c, attrs);
} else if (name.equals("rotate")) {
anim = new RotateAnimation(c, attrs);
} else if (name.equals("translate")) {
anim = new TranslateAnimation(c, attrs);
} else {
throw new RuntimeException("Unknown animation name: " + parser.getName());
}
if (parent != null) {
parent.addAnimation(anim);
}
}
return anim;
}
根据解析出来的标签名,创建对应的动画,如果是动画集AnimationSet不为空,则动画放入集合。loadLayoutAnimation()、loadInterpolator()方法同理可知,都是根据标签创建对象。
AnimationUtils还为我们预定义了显示隐藏左右上下移动的动画
Drawable Animation 的使用相信大伙儿都比较熟悉了,这里仅仅简单的了解一下实现原理。
不过是xml布局绑定还是代码调用的方式都会调用 到 setBackgroundDrawable方法,都会调用 AnimationDrawable .start()方法,跟进AnimationDrawable
继续跟进start方法
public void start() {
mAnimating = true;
if (!isRunning()) {
// Start from 0th frame.
setFrame(0, false, mAnimationState.getChildCount() > 1
|| !mAnimationState.mOneShot);
}
}
发现调用setFrame方法,而setFrame方法有调用到Drawable内部定义四个方法
public Callback getCallback() {
if (mCallback != null) {
return mCallback.get();
}
return null;
}
public void invalidateSelf() {
final Callback callback = getCallback();
if (callback != null) {
callback.invalidateDrawable(this);
}
}
public void scheduleSelf(Runnable what, long when) {
final Callback callback = getCallback();
if (callback != null) {
callback.scheduleDrawable(this, what, when);
}
}
public void unscheduleSelf(Runnable what) {
final Callback callback = getCallback();
if (callback != null) {
callback.unscheduleDrawable(this, what);
}
}
这几个方法无疑都与Drawable内部定义的Callback有这不清不楚的关系,我们来看看Callback源码
/*如果你想实现一个扩展子Drawable的动画drawable,那么你可以通过setCallBack(android.graphics.drawable.Drawable.Callback)来把你实现的该接口注册到动画drawable
*中。可以实现对动画的调度和执行
*/
public static interface Callback {
/**
* 当drawable重画时触发,这个点上drawable将被置为不可用(起码drawable展示部分不可用)
* @param 要求重画的drawable
*/
public void invalidateDrawable(Drawable who);
/**
* drawable可以通过该方法来安排动画的下一帧。可以仅仅简单的调用postAtTime(Runnable, Object, long)来实现该方法。参数分别与方法的参数对
*应
* @param who The drawable being scheduled.
* @param what The action to execute.
* @param when The time (in milliseconds) to run
*/
public void scheduleDrawable(Drawable who, Runnable what, long when);
/**
*可以用于取消先前通过scheduleDrawable(Drawable who, Runnable what, long when)调度的某一帧。可以通过调用removeCallbacks(Runnable,Object)来实现
* @param who The drawable being unscheduled.
* @param what The action being unscheduled.
*/
public void unscheduleDrawable(Drawable who, Runnable what);
}
了解了上门Callback函数定义以后,我们再回过头来看setFrame就不难理解了,通过参数控制切换Drawable绘制,start 、stop方法也就容易理解了
public void start() {
mAnimating = true;
if (!isRunning()) {
// Start from 0th frame.
setFrame(0, false, mAnimationState.getChildCount() > 1
|| !mAnimationState.mOneShot);
}
}
public void stop() {
mAnimating = false;
if (isRunning()) {
unscheduleSelf(this);
}
}
Drawable 源码解读相关资料推荐一篇blog:http://blog.csdn.net/crazy__chen/article/details/47374631
Drawable.Callback的实现位置我们可以继续跟进,对Callback 与Drawable 、View之前的关系一探究竟。
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource{
}
找到drawable 设置Callback调用的方法setCallback(this),让Drawable 缓存 Callback的引用。结果找到几个方法:
setForeground
setBackgroundDrawable
这两个方法设置了Callback后都有调用到重绘invalidate。
在View初始化解析Attribute,如果有background属性会调用到setBackgroundDrawable,在这里会为Drawable设置Callback(代码调用设置Background ResId /drawable ResColo 同样会调用到setBackgroundDrawable).
invalidate方法调用又是怎么一回事呢?invalidate会引起重绘,我们进入View draw方法一观
@CallSuper
public void draw(Canvas canvas) {
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
//.........略.................
}
请仔细查看注释
“// Step 1, draw the background, if needed”
google工程师把绘制不凑写的非常清晰的,现在我们基本可以用一幅图来小结Drawable Animation
博主学到的就这么多了,在深入了解比较吃力了,暂时就这样吧。以上内容可能存在误差,欢迎交流指出,谢谢!