在开发移动应用程序的时候用到动画是家常便饭的事,但是你有没有想过它是怎么实现的呢?今天小弟就在此分析一下。

1 startAnimation 方法。

 设置好animation变量,刷新父视图绘画缓存。
    /**     * Start the specified animation now.     *     * @param animation the animation to start now     */    public void startAnimation(Animation animation) {        animation.setStartTime(Animation.START_ON_FIRST_FRAME);        setAnimation(animation);        invalidateParentCaches();        invalidate(true);    }

2 invalidate

 void invalidate(boolean invalidateCache) {        if (skipInvalidate()) {            return;        }        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||                (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) ||                (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) {            mLastIsOpaque = isOpaque();            mPrivateFlags &= ~PFLAG_DRAWN;            mPrivateFlags |= PFLAG_DIRTY;            if (invalidateCache) {                mPrivateFlags |= PFLAG_INVALIDATED;                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;            }            final AttachInfo ai = mAttachInfo;            final ViewParent p = mParent;            //noinspection PointlessBooleanExpression,ConstantConditions            if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {                if (p != null && ai != null && ai.mHardwareAccelerated) {                    // fast-track for GL-enabled applications; just invalidate the whole hierarchy                    // with a null dirty rect, which tells the ViewAncestor to redraw everything                    p.invalidateChild(this, null);                    return;                }            }            if (p != null && ai != null) {                final Rect r = ai.mTmpInvalRect;                r.set(0, 0, mRight - mLeft, mBottom - mTop);                // Don't call invalidate -- we don't want to internally scroll                // our own bounds                p.invalidateChild(this, r);            }        }    }
由分析可知,将进入invalidateChild 方法。

3 invalidateChild

 /**     * Don't call or override this method. It is used for the implementation of     * the view hierarchy.     */    public final void invalidateChild(View child, final Rect dirty) {        ViewParent parent = this;        final AttachInfo attachInfo = mAttachInfo;        if (attachInfo != null) {            // If the child is drawing an animation, we want to copy this flag onto            // ourselves and the parent to make sure the invalidate request goes            // through            final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION)                    == PFLAG_DRAW_ANIMATION;            // Check whether the child that requests the invalidate is fully opaque            // Views being animated or transformed are not considered opaque because we may            // be invalidating their old position and need the parent to paint behind them.            Matrix childMatrix = child.getMatrix();            final boolean isOpaque = child.isOpaque() && !drawAnimation &&                    child.getAnimation() == null && childMatrix.isIdentity();            // Mark the child as dirty, using the appropriate flag            // Make sure we do not set both flags at the same time            int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;            if (child.mLayerType != LAYER_TYPE_NONE) {                mPrivateFlags |= PFLAG_INVALIDATED;                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;                child.mLocalDirtyRect.union(dirty);            }            final int[] location = attachInfo.mInvalidateChildLocation;            location[CHILD_LEFT_INDEX] = child.mLeft;            location[CHILD_TOP_INDEX] = child.mTop;            if (!childMatrix.isIdentity() ||                    (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {                RectF boundingRect = attachInfo.mTmpTransformRect;                boundingRect.set(dirty);                Matrix transformMatrix;                if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {                    Transformation t = attachInfo.mTmpTransformation;                    boolean transformed = getChildStaticTransformation(child, t);                    if (transformed) {                        transformMatrix = attachInfo.mTmpMatrix;                        transformMatrix.set(t.getMatrix());                        if (!childMatrix.isIdentity()) {                            transformMatrix.preConcat(childMatrix);                        }                    } else {                        transformMatrix = childMatrix;                    }                } else {                    transformMatrix = childMatrix;                }                transformMatrix.mapRect(boundingRect);                dirty.set((int) (boundingRect.left - 0.5f),                        (int) (boundingRect.top - 0.5f),                        (int) (boundingRect.right + 0.5f),                        (int) (boundingRect.bottom + 0.5f));            }            do {                View view = null;                if (parent instanceof View) {                    view = (View) parent;                }                if (drawAnimation) {                    if (view != null) {                        view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;                    } else if (parent instanceof ViewRootImpl) {                        ((ViewRootImpl) parent).mIsAnimating = true;                    }                }                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque                // flag coming from the child that initiated the invalidate                if (view != null) {                    if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&                            view.getSolidColor() == 0) {                        opaqueFlag = PFLAG_DIRTY;                    }                    if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {                        view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;                    }                }                parent = parent.invalidateChildInParent(location, dirty);                if (view != null) {                    // Account for transform on current parent                    Matrix m = view.getMatrix();                    if (!m.isIdentity()) {                        RectF boundingRect = attachInfo.mTmpTransformRect;                        boundingRect.set(dirty);                        m.mapRect(boundingRect);                        dirty.set((int) (boundingRect.left - 0.5f),                                (int) (boundingRect.top - 0.5f),                                (int) (boundingRect.right + 0.5f),                                (int) (boundingRect.bottom + 0.5f));                    }                }            } while (parent != null);        }    }

(首先来看这个方法是不允许重载的,为什么呢?在个人看来,这是出于对Android API的一种保护机制。这是视图刷新的人口。)
分析可知:view.mPrivateFlags|PFLAG_DRAW_ANIMATION; 中加上了动画状态。(google用这种方式记录view的私有状态,是一种很好的方式,既提高了效率,也减少了不必要的变量)。通过这个函数之后,我们到了ViewRootImpl.

4  ViewRootImpl  invalidateChildInParent

    @Override    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {        checkThread();        if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);        if (dirty == null) {            invalidate();            return null;        } else if (dirty.isEmpty() && !mIsAnimating) {            return null;        }        if (mCurScrollY != 0 || mTranslator != null) {            mTempRect.set(dirty);            dirty = mTempRect;            if (mCurScrollY != 0) {                dirty.offset(0, -mCurScrollY);            }            if (mTranslator != null) {                mTranslator.translateRectInAppWindowToScreen(dirty);            }            if (mAttachInfo.mScalingRequired) {                dirty.inset(-1, -1);            }        }        final Rect localDirty = mDirty;        if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {            mAttachInfo.mSetIgnoreDirtyState = true;            mAttachInfo.mIgnoreDirtyState = true;        }        // Add the new dirty rect to the current one        localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);        // Intersect with the bounds of the window to skip        // updates that lie outside of the visible region        final float appScale = mAttachInfo.mApplicationScale;        final boolean intersected = localDirty.intersect(0, 0,                (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));        if (!intersected) {            localDirty.setEmpty();        }        if (!mWillDrawSoon && (intersected || mIsAnimating)) {            scheduleTraversals();        }        return null;    }

这个方法主要作用是先确定哪些区域重新绘画,然后遍历绘画。接下来分析scheduleTraversals.
    void scheduleTraversals() {        if (!mTraversalScheduled) {            mTraversalScheduled = true;            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();            mChoreographer.postCallback(                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);            scheduleConsumeBatchedInput();        }    }

接下来进入Choreographer 中的posCallback。对于Choreographe这个类非常的重要,它简单来说是一个消息处理器,包括用户输入,动画,绘图。
    private void postCallbackDelayedInternal(int callbackType,            Object action, Object token, long delayMillis) {        if (DEBUG) {            Log.d(TAG, "PostCallback: type=" + callbackType                    + ", action=" + action + ", token=" + token                    + ", delayMillis=" + delayMillis);        }        synchronized (mLock) {            final long now = SystemClock.uptimeMillis();            final long dueTime = now + delayMillis;            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);            if (dueTime <= now) {                scheduleFrameLocked(now);            } else {                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);                msg.arg1 = callbackType;                msg.setAsynchronous(true);                mHandler.sendMessageAtTime(msg, dueTime);            }        }    }

这个方法主要是把所要做的刷新动作加入到列表中。
private void scheduleFrameLocked(long now) {        if (!mFrameScheduled) {            mFrameScheduled = true;            if (USE_VSYNC) {                if (DEBUG) {                    Log.d(TAG, "Scheduling next frame on vsync.");                }                // If running on the Looper thread, then schedule the vsync immediately,                // otherwise post a message to schedule the vsync from the UI thread                // as soon as possible.                if (isRunningOnLooperThreadLocked()) {                    scheduleVsyncLocked();                } else {                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);                    msg.setAsynchronous(true);                    mHandler.sendMessageAtFrontOfQueue(msg);                }            } else {                final long nextFrameTime = Math.max(                        mLastFrameTimeNanos / NANOS_PER_MS + sFrameDelay, now);                if (DEBUG) {                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");                }                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);                msg.setAsynchronous(true);                mHandler.sendMessageAtTime(msg, nextFrameTime);            }        }
这个 USE_VSYN是4.1之后才引入了,采用了三重缓存,大大提高了绘画效率。具体的可以查看官方资料对其描述。这个字段默认为true。一般情况下isRunningOnLooperThreadLocked()==true.  接下来分析

5  scheduleVsyncLocked();

    private void scheduleVsyncLocked() {        mDisplayEventReceiver.scheduleVsync();    }
这个方法是一个native方法会回调onVsync()方法。鉴于篇幅的原因,在此补贴其具体源码。大概意思就是把它自身的runnable加入到队列中。 接下来执行调用run()。run调用doFrame方法。

6 doFrame

void doFrame(long frameTimeNanos, int frame) {        final long startNanos;        synchronized (mLock) {            if (!mFrameScheduled) {                return; // no work to do            }            startNanos = System.nanoTime();            final long jitterNanos = startNanos - frameTimeNanos;            if (jitterNanos >= mFrameIntervalNanos) {                final long skippedFrames = jitterNanos / mFrameIntervalNanos;//mFrameIntervalNanos是屏幕刷新周期                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { //如果跳过的帧数大于30,就存在跳帧现象,说明主线程做了太多的工作                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "                            + "The application may be doing too much work on its main thread.");                }                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;//                if (DEBUG) {                    Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "                            + "which is more than the frame interval of "                            + (mFrameIntervalNanos * 0.000001f) + " ms!  "                            + "Skipping " + skippedFrames + " frames and setting frame "                            + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");                }                frameTimeNanos = startNanos - lastFrameOffset;            }            //跳帧情况判断            if (frameTimeNanos < mLastFrameTimeNanos) {                if (DEBUG) {                    Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "                            + "previously skipped frame.  Waiting for next vsync.");                }                scheduleVsyncLocked();                return;            }            mFrameScheduled = false;            mLastFrameTimeNanos = frameTimeNanos;        }        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
j接下来就是一系列的调用回到ViewRootImpl performTraversals().  然后开始一系列的调用。performMeasure ,persormLayout等等调用,。接下来重点关注performDraw(). 由于篇幅的关系我也不贴出源码了,接下里调用到dispatchDraw.接下来我跟踪到ViewGroup 找到这个方法。
    /**     * {@inheritDoc}     */    @Override    protected void dispatchDraw(Canvas canvas) {        final int count = mChildrenCount;        final View[] children = mChildren;        int flags = mGroupFlags;        if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {            final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;            final boolean buildCache = !isHardwareAccelerated();            for (int i = 0; i < count; i++) {                final View child = children[i];                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {                    final LayoutParams params = child.getLayoutParams();                    attachLayoutAnimationParameters(child, params, i, count);                    bindLayoutAnimation(child);                    if (cache) {                        child.setDrawingCacheEnabled(true);                        if (buildCache) {                                                    child.buildDrawingCache(true);                        }                    }                }            }            final LayoutAnimationController controller = mLayoutAnimationController;            if (controller.willOverlap()) {                mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;            }            controller.start();            mGroupFlags &= ~FLAG_RUN_ANIMATION;            mGroupFlags &= ~FLAG_ANIMATION_DONE;            if (cache) {                mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;            }            if (mAnimationListener != null) {                mAnimationListener.onAnimationStart(controller.getAnimation());            }        }        int saveCount = 0;        final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;        if (clipToPadding) {            saveCount = canvas.save();            canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,                    mScrollX + mRight - mLeft - mPaddingRight,                    mScrollY + mBottom - mTop - mPaddingBottom);        }        // We will draw our child's animation, let's reset the flag        mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;        mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;        boolean more = false;        final long drawingTime = getDrawingTime();//获得当前的绘画时间        if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {            for (int i = 0; i < count; i++) {                final View child = children[i];                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {                    more |= drawChild(canvas, child, drawingTime);                }            }        } else {            for (int i = 0; i < count; i++) {                final View child = children[getChildDrawingOrder(count, i)];                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {                    more |= drawChild(canvas, child, drawingTime);                }            }        }        // Draw any disappearing views that have animations        if (mDisappearingChildren != null) {            final ArrayList disappearingChildren = mDisappearingChildren;            final int disappearingCount = disappearingChildren.size() - 1;            // Go backwards -- we may delete as animations finish            for (int i = disappearingCount; i >= 0; i--) {                final View child = disappearingChildren.get(i);                more |= drawChild(canvas, child, drawingTime);            }        }        if (debugDraw()) {            onDebugDraw(canvas);        }        if (clipToPadding) {            canvas.restoreToCount(saveCount);        }        // mGroupFlags might have been updated by drawChild()        flags = mGroupFlags;         //判断动画是否结束           if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {            invalidate(true);        }        if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&                mLayoutAnimationController.isDone() && !more) {            // We want to erase the drawing cache and notify the listener after the            // next frame is drawn because one extra invalidate() is caused by            // drawChild() after the animation is over            mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;            final Runnable end = new Runnable() {               public void run() {                   notifyAnimationListener(); //通知动画结束监听者               }            };            post(end);        }    }
最终会调用到protected void applyTransformation(float interpolatedTime, Transformation t)。这个方法。所以如果想自定义动画的话,就重写这个方法。好了本次分析就到此为止。下片博客将会更加细致的分析,以及android 系统为什么要采取这种方式来进行动画。


更多相关文章

  1. 引擎设计跟踪(九.14.2i) Android(安卓)GLES 3.0 完善
  2. android中 Button 安装监听的三种形式
  3. Android——Log,Toast提示框,Intent跳转
  4. 设备的search按钮调用自己程序的search模块
  5. android和js互相调用的问题
  6. Android两种不同的方法去实现图像的放大与缩小(很有帮助)
  7. Android调用微信登陆、分享、支付(第二版本)
  8. Android(安卓)Studio 找不到R文件解决方法汇总
  9. android 的skia 图形引擎库

随机推荐

  1. 210402 JavaScript 作用域 闭包 原型 构
  2. Vue:实例演示,v-if,v-for,v-model,v-bind
  3. 优盘文件或目录损坏且无法读取怎么办 ,文
  4. 快递100支持快递公司编码案例
  5. 电商快递物流电子面单复打API接口案例
  6. 快递物流接口API如何获取第三方电商平台
  7. 电商快递物流电子面单HTML接口API案例代
  8. 电商快递物流电子面单图片接口API案例代
  9. 电商快递物流的电子面单打印接口API案例
  10. python学习随笔-数据类型