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方法进行绘制了。