700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > android 动画原理源码分析之Animation

android 动画原理源码分析之Animation

时间:2021-04-02 02:37:32

相关推荐

android  动画原理源码分析之Animation

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

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,ConstantConditionsif (!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 everythingp.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 boundsp.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// throughfinal 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 timeint 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 invalidateif (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 parentMatrix 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

@Overridepublic 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 onelocalDirty.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 regionfinal 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接下来就是一系列的调用回到ViewRootImplperformTraversals(). 然后开始一系列的调用。performMeasure ,persormLayout等等调用,。接下来重点关注performDraw(). 由于篇幅的关系我也不贴出源码了,接下里调用到dispatchDraw.接下来我跟踪到ViewGroup 找到这个方法。

/*** {@inheritDoc}*/@Overrideprotected 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 flagmPrivateFlags &= ~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 animationsif (mDisappearingChildren != null) {final ArrayList<View> disappearingChildren = mDisappearingChildren;final int disappearingCount = disappearingChildren.size() - 1;// Go backwards -- we may delete as animations finishfor (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 overmGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;final Runnable end = new Runnable() {public void run() {notifyAnimationListener(); //通知动画结束监听者}};post(end);}}

最终会调用到protected void applyTransformation(float interpolatedTime, Transformation t)。这个方法。所以如果想自定义动画的话,就重写这个方法。好了本次分析就到此为止。下片博客将会更加细致的分析,以及android 系统为什么要采取这种方式来进行动画。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。