在开发移动应用程序的时候用到动画是家常便饭的事,但是你有没有想过它是怎么实现的呢?今天小弟就在此分析一下。
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 找到这个方法。
/** * [email protected]} */ @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<View> 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 系统为什么要采取这种方式来进行动画。
- 3楼liu47036850017分钟前
- 点赞!老周
- 2楼hncc201221分钟前
- 19的老家伙你好
- 1楼qxf577740423分钟前
- Mark 学习