Android 关于绘制的一个小细节分享
Posted 冬天的毛毛雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 关于绘制的一个小细节分享相关的知识,希望对你有一定的参考价值。
作者:互联网程序员
很多时候我们在自定义 View 的需要做动画的时候,我们可以依赖属性动画的回调周期性修改 自定义的属性值,然后调用 invalidate 方法实现。
不过我还见过一个比较野的路子,它在 onDraw 里面直接修改属性,然后调用 invalidate() 方法。
运行起来好像也没问题。
那么问题来了:
1. 在 onDraw 里面调用 修改绘制相关属性(例如画圆,修改半径) invalidate() ,这种与属性动画的回调调用 invalidate()源码分析有什么区别?
2. 在 onDraw 里面调用 invalidate() 会存在什么问题?
在View.onDraw方法里调用View.invalidate和在ValueAnimator.AnimatorUpdateListener中调用View.invalidate,有区别吗?
了解ValueAnimator的同学会知道,它播放动画的实现原理并不是直接使用线程来不断计算并回调AnimatorUpdateListener,而是。。。来写代码测试下就知道了:
ValueAnimator.ofInt(1).apply
addUpdateListener
// 因为animatedFraction=0时是直接回调的
if (it.animatedFraction > 0)
throw RuntimeException()
start()
代码很简单,随便创建一个ValueAnimator然后在它的UpdateListener里面去抛出一个异常。
看看堆栈信息:
E/androidRuntime: FATAL EXCEPTION: main
Process: com.wuyr.wanandroidqa, PID: 16027
java.lang.RuntimeException
at com.wuyr.wanandroidqa.activities.main.TestActivity$start$1$1.onAnimationUpdate(TestActivity.kt:112)
at android.animation.ValueAnimator.animateValue(ValueAnimator.java:1566)
at android.animation.ValueAnimator.animateBasedOnTime(ValueAnimator.java:1357)
at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1489)
at android.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:146)
at android.animation.AnimationHandler.access$100(AnimationHandler.java:37)
//
// 3
//
at android.animation.AnimationHandler$1.doFrame(AnimationHandler.java:54)
//
// 2
//
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:970)
at android.view.Choreographer.doCallbacks(Choreographer.java:796)
//
// 1
//
at android.view.Choreographer.doFrame(Choreographer.java:727)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
看标记1处,来到了Choreographer.doFrame方法,View的绘制,各种输入/触摸事件等也是在这里开始处理的。
接着看2,Choreographer.java第970行:
private static final class CallbackRecord
public Object action; // Runnable or FrameCallback
public void run(long frameTimeNanos)
......
((FrameCallback)action).doFrame(frameTimeNanos); // 970行
......
这里把action强转为FrameCallback,而标记3处:
public class AnimationHandler
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback()
@Override
public void doFrame(long frameTimeNanos)
doAnimationFrame(getProvider().getFrameTime()); // 54行
if (mAnimationCallbacks.size() > 0)
getProvider().postFrameCallback(this);
;
可以看到它回调的正是AnimationHandler的mFrameCallback。
但是ValueAnimator怎么会跟AnimationHandler扯上关系呢?
其实在我们调用start方法播放动画的时候,它就已经把一个Callback添加到AnimationHandler里面去了:
public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback
@Override
public void start()
start(false);
private void start(boolean playBackwards)
......
addAnimationCallback(0);
......
private void addAnimationCallback(long delay)
AnimationHandler.getInstance().addAnimationFrameCallback(this, delay);
/*
这里实现AnimationHandler.AnimationFrameCallback接口的方法
*/
@Override
public final boolean doAnimationFrame(long frameTime)
......
这个Callback正是ValueAnimator自身。
那它最终会被传到哪里呢?
代码套那么多层就不全贴了,它最终会通过Choreographer.postFrameCallback方法:
public final class Choreographer
public void postFrameCallback(FrameCallback callback)
postFrameCallbackDelayed(callback, 0);
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis)
......
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
传到了Choreographer中。
可以看到它最后调用的是postCallbackDelayedInternal方法,记住!这个很重要!
好,回到主题。
通过刚刚一段分析,可以知道,ValueAnimator.AnimatorUpdateListener,是在Choreographer.doFrame回调时才回调的。也就是说,ValueAnimator开始后,AnimatorUpdateListener会在每一次屏幕刷新的时候回调!
还有一个区别就是,动画进度计算方式不同,ValueAnimator是根据记录的开始时间来计算的,所以它不会受到Activity生命周期或其他因素影响。而直接在onDraw里回调的就不同了,如果动画在播放过程中Activity Stopped了,onDraw就会暂停回调,那么下一次的invalidate时间,也就无从确定了。不过,可能刚好有这样的需求,需要在Activity不可见时暂停动画呢?
在View.onDraw中直接调用invalidate方法会有什么问题?
看了*@xujiafeng*同学的回答,他说这样做的话,IdleHandler不会被回调。公众号文章链接在这里:
Android 避坑指南:实际经历来说说IdleHandler的坑
emmmm,其实我觉得这不应该是一个问题,因为Handler的机制就是这样的啊,MessageQueue还有事情没处理完,肯定不会告诉你说它有空啦。
等动画播放完毕,IdleHandler还是会正常回调的。
不过你说是要无限循环播放的话,让MessageQueue一直忙碌,导致IdleHandler一直没能被回调的话,那确实是个问题,就拿常见的场景来说:每日一问 | Activity 调用了finish()方法会立即调用onDestory()吗? ,Activity的Destory也是依赖IdleHandler来完成的(虽然有超时机制)。(以后会跟大家一起debug AMS来详细分析这个问题)
https://www.wanandroid.com/wenda/show/13244
如果真的有这样的需求,除了改用ValueAnimator之外,就没其他方法了吗?
肯定有啦,你想想ScrollView、RecyclerView、ViewPager等等这些View的惯性滚动动画效果是怎么做的?
它们其实是通过一个叫postInvalidateOnAnimation的方法来invalidate的,关于这个方法,我记得在前面好几个回答都提到过了。
来看下它原理是怎么样的吧:
长话短说,它最终是调用Choreographer.postCallback方法来把一个会调用View.invalidate的Runnable传进去:
public final class Choreographer
public void postCallback(int callbackType, Runnable action, Object token)
postCallbackDelayed(callbackType, action, token, 0);
public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis)
......
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
!!!!!看到了没?!它最终调用的是postCallbackDelayedInternal方法!还记得刚刚分析ValueAnimator的时候,叫记住的那个方法吗?就是它啊!
这就说明了,使用View.postInvalidateOnAnimation方法,跟在ValueAnimator.AnimatorUpdateListener中调用invalidate,效果是一样的!
同样是调用invalidate方法,为什么在AnimatorUpdateListener.onAnimationUpdate里面调用,就不会阻止IdleHandler回调呢?
看图就明白了,这是在onDraw里调用invalidate的流程:
就算MQ里没有其他的msg,在每次Traversal任务即将处理完毕时又向MQ塞入了新的msg,所谓一波未平,一波又起,这样的话,IdleHandler肯定没机会回调了。
来看下在AnimatorUpdateListener中调用invalidate的流程:
因为AnimatorUpdateListener的onAnimationUpdate方法是每次屏幕刷新时才回调的,也就是大概16ms左右,在这16ms的间隔内,Looper可能已经把MQ里剩下的msg都取出来了,所以如果在AnimatorUpdateListener里调用invalidate的话,会看到这样的log:
onAnimationUpdate: invoked
onAnimationUpdate: invoked
onDraw: invoked
queueIdle: invoked
onAnimationUpdate: invoked
onDraw: invoked
queueIdle: invoked
onAnimationUpdate: invoked
onDraw: invoked
queueIdle: invoked
onAnimationUpdate: invoked
onDraw: invoked
queueIdle: invoked
onAnimationUpdate: invoked
onDraw: invoked
queueIdle: invoked
以上是关于Android 关于绘制的一个小细节分享的主要内容,如果未能解决你的问题,请参考以下文章
Android -- 自定义View小Demo,关于Rect绘制Android机器人
关于Android Framework源码阅读——阿里大佬分享的核心经验
10 个案例分享几个 Python 可视化小技巧,助你绘制高质量图表