仿qq記步效果及invalidate原始碼分析
public class MyStepView extends View{ //中間文字大小 private int MyStepTextSize=30; //圓環邊框大小 private int MyStepWidth=20; //中間文字顏色 private int MyStepTextColor= Color.parseColor("#000000"); //內環顏色 private int MyStepInnerColor=Color.parseColor("#000000"); //外環顏色 private int MyStepOuterColor=Color.parseColor("#000000"); //外環畫筆 private Paint outPaint; //內環畫筆 private Paint innerPaint; //繪製文字畫筆 private Paint textPaint; //總步數 private int mMaxStep=0; //當前步數 private int mCurrentStep=0; public MyStepView(Context context) { this(context,null); } public MyStepView(Context context, AttributeSet attrs) { this(context, attrs,0); } public MyStepView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyStepView); MyStepTextSize=a.getDimensionPixelSize(R.styleable.MyStepView_myStepTextSize,sp2px(MyStepTextSize)); MyStepWidth= (int) a.getDimension(R.styleable.MyStepView_myStepWidth,dip2px(MyStepWidth)); MyStepTextColor=a.getColor(R.styleable.MyStepView_myStepTextColor,MyStepTextColor); MyStepInnerColor=a.getColor(R.styleable.MyStepView_myStepInnerColor,MyStepInnerColor); MyStepOuterColor=a.getColor(R.styleable.MyStepView_myStepOuterColor,MyStepOuterColor); a.recycle(); initPaint(); } private void initPaint() { //初始化外環畫筆 outPaint=new Paint(); outPaint.setAntiAlias(true); outPaint.setStrokeWidth(MyStepWidth); outPaint.setColor(MyStepOuterColor); outPaint.setStyle(Paint.Style.STROKE); outPaint.setStrokeCap(Paint.Cap.ROUND); //初始化內環畫筆 innerPaint=new Paint(); innerPaint.setAntiAlias(true); innerPaint.setStrokeWidth(MyStepWidth); innerPaint.setColor(MyStepInnerColor); innerPaint.setStyle(Paint.Style.STROKE); innerPaint.setStrokeCap(Paint.Cap.ROUND); //初始化文字畫筆 textPaint=new Paint(); textPaint.setAntiAlias(true); textPaint.setTextSize(MyStepTextSize); textPaint.setColor(MyStepTextColor); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //獲取寬高模式 int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode=MeasureSpec.getMode(heightMeasureSpec); //獲取寬高 int width=MeasureSpec.getSize(widthMeasureSpec); int height=MeasureSpec.getSize(heightMeasureSpec); if(widthMode==MeasureSpec.AT_MOST){ width= (int) dip2px(300); } if(heightMode==MeasureSpec.AT_MOST){ height= (int) dip2px(300); } width=height=Math.max(width,height); setMeasuredDimension(width,height); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //中心點座標 int center=getWidth()/2; int widthCount=center-MyStepWidth/2; //繪製外環 RectF rectf=new RectF(center-widthCount,center-widthCount,center+widthCount,center+widthCount); canvas.drawArc(rectf,135,270,false,outPaint); if(mMaxStep==0){ return; } float sweepAngle=(float) mCurrentStep/(float)mMaxStep*270; //繪製內環 canvas.drawArc(rectf,135,sweepAngle,false,innerPaint); //繪製文字 String text=mCurrentStep+""; //計算文字起始位置 Rect bounds=new Rect(); textPaint.getTextBounds(text,0,text.length(),bounds); float x=getWidth()/2-bounds.width()/2; //計算文字基線 Paint.FontMetricsInt metricsInt = textPaint.getFontMetricsInt(); int dy=(metricsInt.bottom-metricsInt.top)/2-metricsInt.bottom; float baseline=getHeight()/2+dy; canvas.drawText(text,x,baseline,textPaint); } /** * 設定最大步數 * @param maxStep */ public void setMaxStep(int maxStep){ this.mMaxStep=maxStep; } /** * 設定當前步數 * @param currentStep */ public void setCurrentStep(int currentStep){ this.mCurrentStep=currentStep; //進行重繪 invalidate(); } private float dip2px(int dip) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dip,getResources().getDisplayMetrics()); } private int sp2px(int sp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,getResources().getDisplayMetrics()); } }
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final MyStepView stepView = (MyStepView) findViewById(R.id.step_view); stepView.setMaxStep(5000); //設定屬性動畫 ValueAnimator animator = ObjectAnimator.ofFloat(0, 3000); animator.setDuration(5000); animator.setInterpolator(new DecelerateInterpolator()); //監聽動畫進度 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float animatedValue = (float) animation.getAnimatedValue(); stepView.setCurrentStep((int)animatedValue); } }); //開啟動畫 animator.start(); } }
這個就是一個實現的大致效果,在setCurrentStep()方法中呼叫了invalidate()方法對其每次步數的變化進行繪製,在呼叫invalidate()方法後就會去View中的invalidate()方法,接著回去呼叫View中的invalidateInternal()方法,
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { if (mGhostView != null) { mGhostView.invalidate(true); return; } //在skipInvalidate方法中會去判斷是否可,當前是否有動畫 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 || (fullInvalidate && isOpaque() != mLastIsOpaque)) { if (fullInvalidate) { mLastIsOpaque = isOpaque(); mPrivateFlags &= ~PFLAG_DRAWN; } mPrivateFlags |= PFLAG_DIRTY; if (invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } // Propagate the damage rectangle to the parent view. final AttachInfo ai = mAttachInfo; //將mParent賦值給ViewParent final ViewParent p = mParent; if (p != null && ai != null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); //呼叫ViewParent中的invalidateChild方法,而ViewParent是一個介面, //最終呼叫的是實現了中的invalidateChild方法,而ViewGroup和ViewRootImpl都是其實現類,所以 //應該呼叫了ViewGroup中的invalidateChild方法 p.invalidateChild(this, damage); } // Damage the entire projection receiver, if necessary. if (mBackground != null && mBackground.isProjected()) { final View receiver = getProjectionReceiver(); if (receiver != null) { receiver.damageInParent(); } } // Damage the entire IsolatedZVolume receiving this view's shadow. if (isHardwareAccelerated() && getZ() != 0) { damageShadowReceiver(); } } }
最終呼叫了ViewParent中的invalidateChild方法,但是ViewParent是一個介面,就要去找其實現類,ViewGroup和ViewRootImpl是其實現類,這裡首先呼叫的是ViewGoup中的invalidateChild方法;
//ViewGoup中invalidateChild方法原始碼
@Override
public final void invalidateChild(View child, final Rect dirty) {
//首先將當前ViewGroup物件賦值給父類ViewParent
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;
}
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) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
//這裡是一個do while迴圈,當parent不為null的時候就會跳出迴圈
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;
}
}
//這裡會一直遍歷迴圈呼叫invalidateChildInParent方法,會一直往外傳遞,一直到最外層,
//到最外層的時候,呼叫的就是ViewRootImpl中的invalidateChildInParent方法
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) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
}
} while (parent != null);
}
}
在invalidateChild方法中會有一個do while的迴圈,會一直遍歷呼叫invalidateChildInParent方法,並往外傳遞,一直傳遞到最外成,在最外成時就會去呼叫ViewRootImpl中的invalidateChildInParent方法;
//ViewRootImpl中invalidateChildInParent方法原始碼
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
//呼叫checkThread方法去檢測當前執行緒是否是主執行緒,如果不是主執行緒就會拋異常,
//這也就是不能在子執行緒中更新ui的原因
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "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);
}
}
//會呼叫invalidateRectOnScreen方法
invalidateRectOnScreen(dirty);
return null;
}
在invalidateRectOnScreen()方法中接著呼叫了scheduleTraversals();
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//這裡呼叫postCallback並傳入一個TraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
在傳入的TraversalRunnable類中就會去呼叫doTraversal()方法;
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//呼叫preformTraversals方法
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
在performTraversals方法中就會呼叫performMeasure、performLayout、performDraw等方法進行測量、擺放、繪製,不過需要注意的是在invalidate方法流程中不會呼叫performMeasure和performLayout方法,只會呼叫performDraw方法,所有這裡就先看performDraw方法;在performDraw方法中就會去呼叫draw方法,接著會去呼叫draw方法中的drawSoftware方法;在drawSoftware方法中會呼叫View中的draw方法並傳入一個canvas畫布,到這裡終於回到了View中的draw方法了;
//View中draw方法原始碼
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
//下面會根據dirtyOpaque 標識來判斷onDraw()等方法的呼叫
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
* 整個繪製工作大致可以分為6步
第一步:繪製背景
* 1. Draw the background
第二步:必要時,儲存畫布的層以準備漸變
* 2. If necessary, save the canvas' layers to prepare for fading
第三步:繪製內容
* 3. Draw view's content
第四步:繪製子view
* 4. Draw children
第五步:如果需要,繪製漸變邊緣並恢復圖層
* 5. If necessary, draw the fading edges and restore layers
第六步:繪畫裝飾
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
//dirtyOpaque標識來控制是否需要繪製背景,如果需要才會呼叫drawBackground方法繪製背景,
//否則不會呼叫drawBackground方法
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
//在繪製內容的時候也會根據dirtyOpaque標識來決定是否呼叫onDraw方法,
//在正常情況下ViewGoup是不會呼叫onDraw方法的,就是說extend ViewGroup的自定義view或者容器都不會呼叫onDraw方法
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
//繪製子view不管是extend View的還是extend ViewGroup的都會呼叫,
//所以在extend ViewGroup的自定義view或者容器中其實可以重寫dispatchDraw()方法
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
//繪製裝飾
onDrawForeground(canvas);
// we're done...
return;
}
/*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
這個時候就會去執行自定義中onDraw方法進行繪製了。