無圖無真像:
在我們畫一個圖形之前,我們需要思考,我們先要解析他的步驟,然后根據(jù)步驟一步一步來完成。
思考:我們繪制
View
的一般基本流程為:
1.根據(jù)我們的需要,是需要
new
出來還是在布局文件里面添加,從而得到相應(yīng)的構(gòu)造方法。
2.我們需要什么屬性?
3.我們是否需要測量?
4.如果是viewGroup
我們需要是否要通過onLayout
方法來擺放childView
的位置?
5.調(diào)用onDraw()
的時候先畫什么,在畫什么?然后調(diào)用相應(yīng)的API
完成相應(yīng)的步驟即可。
6.性能優(yōu)化。
我們從這個基本流程出發(fā)
- 我們希望這個
view
能夠new
出來,也能夠在布局文件里面添加使用,所以構(gòu)造函數(shù)如下:
public QQSportStepView(Context context) {
this(context, null);
}
public QQSportStepView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public QQSportStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
2.我們需要什么屬性?在這個view
里面,我想實現(xiàn)的是希望其他人可以自己設(shè)置文字顏色,線條寬度,弧度大小等等,所以我們在att
里面定義:
<declare-styleable name="QQSportStep">
<attr name="bottomColor" format="color" />
<attr name="topColor" format="color" />
<attr name="textColor" format="color" />
<attr name="maxStepNum" format="integer" />
<attr name="currentStepNum" format="integer" />
<attr name="textSize" format="dimension" />
<attr name="circleRadio" format="dimension" />
<attr name="circleStrokeWidth" format="dimension" />
<attr name="arcAngle" format="float" />
</declare-styleable>
并且在初始化的時候解析出來:
private int mBottomColor;//底層圓的顏色
private int mTopColor;//頂層圓的顏色
private int mTextColor;//文字顏色
private int mMaxStepNum;//最大步數(shù)
private int mCurrentStepNum;//當前步數(shù)
private int mTextSize;//文字大小
private int mCircleRadio;//圓的半徑
private int mCircleStrokeWidth;//圓線條的寬度
private float mArcAngle;//弧度大小
private float mStartAngle;//通過幅度計算出開始的角度位置
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.QQSportStep);
mBottomColor = ta.getColor(R.styleable.QQSportStep_bottomColor, Color.BLUE);
mTopColor = ta.getColor(R.styleable.QQSportStep_topColor, Color.RED);
mTextColor = ta.getColor(R.styleable.QQSportStep_textColor, Color.RED);
mMaxStepNum = ta.getInteger(R.styleable.QQSportStep_maxStepNum, 0);
mCurrentStepNum = ta.getInteger(R.styleable.QQSportStep_currentStepNum, 0);
mTextSize = ta.getDimensionPixelSize(R.styleable.QQSportStep_textSize, DisplayUtil.sp2px(context, 17));
mCircleRadio = ta.getDimensionPixelSize(R.styleable.QQSportStep_circleRadio, DisplayUtil.dip2px(context, 100));
mArcAngle = ta.getFloat(R.styleable.QQSportStep_arcAngle, 270.0f);
if (mArcAngle > 360.0f || mArcAngle < -360.0f)
mArcAngle = 360.0f;
mCircleStrokeWidth = ta.getDimensionPixelOffset(R.styleable.QQSportStep_circleStrokeWidth, DisplayUtil.dip2px(context, 5));
ta.recycle();
}
3.我們是否需要測量?因為這個view
使用的時候一般是寫好的固定的大小,所以不必要測量,所以我就沒重寫onMeasure
方法
4.因為這是個view
,沒有childView
,所以不用重寫onLayout
方法
5.解析步驟:我將這個view
解析為三個步驟
1.畫底部的圓弧
2.畫頂部的圓弧
3.畫文字
然后重寫onDraw
畫底部的圓弧:
if (mRectF == null) {
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
mRectF = new RectF(centerX - mCircleRadio, centerY mCircleRadio, centerX + mCircleRadio, centerY + mCircleRadio);
}
//1.畫底部的圓
float gapAngle = mArcAngle - 180.0f;
if (mArcAngle >= 0) {//大于0表示在上方
mStartAngle = 180.0f - gapAngle / 2;
} else {//小于0表示在下方
mStartAngle = -gapAngle / 2;
}
canvas.drawArc(mRectF, mStartAngle, mArcAngle, false, mBottomPaint);
畫頂部的圓弧
//2.畫頂部的圓弧
float currentAngle = (float) mCurrentStepNum / mMaxStepNum * mArcAngle;
canvas.drawArc(mRectF, mStartAngle, currentAngle, false, mTopPaint);
if (mMaxStepNum <= 0)
return;
畫文字
String step = String.valueOf(mCurrentStepNum);
int dx = (getWidth() - DisplayUtil.getTextWidth(step, mTextPaint)) / 2;
int baseLine = getHeight() / 2 + DisplayUtil.getTextBaseLine(mTextPaint);
// 繪制步數(shù)文字
canvas.drawText(step, dx, baseLine, mTextPaint);
5.性能優(yōu)化(這一步很重要,請不要忽略它)
我們現(xiàn)在已經(jīng)可以在手機屏幕上呈現(xiàn)一個靜態(tài)的view
了,但是我們要讓他動起來,這里有兩種方式,一種是在view
內(nèi)部寫實現(xiàn),一種是在view
外部實現(xiàn),為了降低耦合,我們最好是將動畫實現(xiàn)的效果從外部實現(xiàn)。在動畫的時候,一定是View
不停的調(diào)用onDraw
方法重繪,所以我們將重復的操作提取出來,一次就行了,不用每一次都在執(zhí)行,比如:畫筆初始化、一些計算等,這樣能夠降低gpu
的消耗,當然如果實現(xiàn)的效果比較復雜的畫,可以使用雙緩沖
的繪圖方式來犧牲內(nèi)存來換取時間,這樣gpu
就不會起伏太大。
最后我們在外部使用ValueAnimator
來實現(xiàn)動畫:
int maxStepNun = 100000;
qqSportStepView.setMaxStepNum(maxStepNun);
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setDuration(2000);
valueAnimator.setIntValues(0, maxStepNun);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
qqSportStepView.setCurrentStepNum(value);
}
});
valueAnimator.start();
總結(jié):我們在做自定義一個view
的時候,一定要先理清楚思路,我們要實現(xiàn)什么效果?我們要達到什么目的?然后在解析相應(yīng)的步驟,最后調(diào)用相關(guān)的api
一步步完成即可。
參考:自定義View - 仿QQ運動步數(shù)進度效果
本文源碼下載地址:
https://github.com/ChinaZeng/CustomView