最近在跟大牛在學習自定義view的時候,跟著他們寫了一個步數計數,效果如下:
device-2017-06-25-094806.gif
效果實現的大致一個思路:
1:寫一個類繼承自view
2:自定義一些屬性,這樣可以在布局文件中直接使用
3:在onMeasure方法中進行測量
4:在onDraw方法中進行繪制
5:提供參數設置的方法
在繼承view的時候需要實現一些構造方法:
public QQStepView(Context context) {
this(context, null);
}
public QQStepView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public QQStepView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
initPaint();
}
這里說下這三個構造方法會在什么情況下調用,第一構造方法會在java文件中直接new的時候調用,第二個構造方法是在布局文件中使用的時候調用,第三個構造方法是在布局文件中使用并設有style樣式的時候調用,將第一個和第二個改為this,這樣不管在什么情況下使用都會統一調用第三個構造方法,接下來在第三個構造方法中初始化自定義屬性和畫筆;
初始化自定義屬性:
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepView);
mOuterColor = array.getColor(R.styleable.QQStepView_outerColor, mOuterColor);
mInnerColor = array.getColor(R.styleable.QQStepView_innerColor, mInnerColor);
mStepTextColor = array.getColor(R.styleable.QQStepView_stepTextColor, mStepTextColor);
mBorderWidth = (int) array.getDimension(R.styleable.QQStepView_borderWidth, mBorderWidth);
mStepTextSize = array.getDimensionPixelSize(R.styleable.QQStepView_stepTextSize, mStepTextSize);
//回收
array.recycle();
}
在初始化屬性后要記得回收;
初始化畫筆:
private void initPaint(){
outPaint = new Paint();
//抗鋸齒
outPaint.setAntiAlias(true);
outPaint.setStrokeWidth(mBorderWidth);
outPaint.setColor(mOuterColor);
//畫筆空心
outPaint.setStyle(Paint.Style.STROKE);
outPaint.setStrokeCap(Paint.Cap.ROUND);
innerPaint = new Paint();
innerPaint.setAntiAlias(true);
innerPaint.setStrokeWidth(mBorderWidth);
innerPaint.setColor(mInnerColor);
//畫筆空心
innerPaint.setStyle(Paint.Style.STROKE);
innerPaint.setStrokeCap(Paint.Cap.ROUND);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(mStepTextColor);
mTextPaint.setTextSize(mStepTextSize);
}
接著就是在onMeasure中進行測量了:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲取寬高模式模式
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
//獲取寬高值
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
//判斷模式是否是wrap_content
if (modeWidth == MeasureSpec.AT_MOST) {
width = dp2px(80);
}
if (modeHeight == MeasureSpec.AT_MOST) {
height = dp2px(80);
}
//寬度和高度不一致的時候取最小值
width = width > height ? height : width;
height = width > height ? height : width;
setMeasuredDimension(width, height);
}
在這里的做了一個寬高模式的判斷,如果是MeasureSpec.AT_MOST也就是wrap_content的時候就將寬和高設置為固定大小,同時如果寬高設置的不一致的時候,取其中的最小值進行測量繪制;
現在可以在onDraw方法中進行繪制了:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪制外弧 描邊問題
RectF rectf = new RectF(mBorderWidth / 2, mBorderWidth / 2, getWidth() - mBorderWidth / 2, getHeight() - mBorderWidth / 2);
canvas.drawArc(rectf, 135, 270, false, outPaint);
//繪制內弧
if (mStepMax == 0) {
return;
}
float swweepAngle = (float) mCurrentStep / mStepMax;
canvas.drawArc(rectf, 135, swweepAngle * 270, false, innerPaint);
//繪制文字
String stepText = mCurrentStep + "";
Rect rect = new Rect();
mTextPaint.getTextBounds(stepText, 0, stepText.length(), rect);
int dx = getWidth() / 2 - rect.width() / 2;
//基線
Paint.FontMetricsInt metricsInt = mTextPaint.getFontMetricsInt();
int dy = (metricsInt.bottom - metricsInt.top) / 2 - metricsInt.bottom;
int baseLine = getHeight() / 2 + dy;
canvas.drawText(stepText, dx, baseLine, mTextPaint);
}
因為在布局文件中時候的時候就會調用onDraw方法但是這個時候還沒有設置mStepMax 值還為0,在計算
float swweepAngle = (float) mCurrentStep / mStepMax;
就會出錯,所以做了下判斷,下面還提供了兩個方法;
設置最大步數:
public synchronized void setStepMax(int stepMax){
this.mStepMax=stepMax;
}
設置當前步數:
public synchronized void setCurrentStep(int currentStep){
this.mCurrentStep=currentStep;
//不斷的去繪制
invalidate();
}
在設置當前步數里面調用invalidate方法就會不停的調用onDraw方法進行繪制,到這里效果就實現的差不多了,在java文件中調用時,設置一些屬性動畫就可以了;
final QQStepView stepView = (QQStepView) findViewById(R.id.stepView);
stepView.setStepMax(10000);
//屬性動畫
ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0, 6123);
valueAnimator.setDuration(2000);
//插值器
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedValue = (float) animation.getAnimatedValue();
stepView.setCurrentStep((int) animatedValue);
}
});
valueAnimator.start();
在使用屬性動畫的時候要注意animation.getAnimatedValue();返回的是一個Object,在這里強轉成float型,不能轉成int要不會報轉換異常的錯誤;
這樣就實現上面的效果了,上面寫的有什么問題,歡迎交流。
源碼地址:http://pan.baidu.com/s/1eSAFa4y