前言:
看到別人寫的那些個酷炫的動畫,心裏癢癢的,於是,自己就開始了自定義View的探索之路。如果對自定義View還不是很熟,在看我這篇文章之前,我覺得你最好先看這篇文章:
自定義View,有這一篇就夠了。
正好公司有這樣一個需求:
與普通進度條區別是,它沒有浮標。從圖中我們可以知道,重要的有3部分:
1.畫進度圓圈。
2.畫數字。
3.畫漢字。
畫出這3部分。按照自定義View的步驟一步一步來吧。
一、創建類OvalProgressView.java繼承View類,定義、初始化屬性
根據上圖可以在res/values/attrs.xml文件中定義以下屬性方便用戶擴展:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="OvalProgressView">
<attr name="ovalColor" format="color" />
<attr name="fixTextColor" format="color" />
<attr name="progressextColor" format="color" />
<attr name="ovalWidth" format="dimension" />
<attr name="progressText" format="string" />
<attr name="progressTextSize" format="dimension" />
<attr name="fixText" format="string" />
<attr name="fixTextSize" format="dimension" />
</declare-styleable>
</resources>
圓圈的顏色,寬度,最終進度,進度描述內容以及他們的字體大小,顏色。當然在.java文件中也要定義相同的屬性了。
private static final int DEFAULT_RADIUS = 150;
/**
* 圓圈顏色
*
* @param context
*/
private int mOvalColor;
/**
* 進度顏色
*
* @param context
*/
private int mProgressColor;
/**
* 進度描述內容顏色
*
* @param context
*/
private int mfixTextColor;
/**
* 圓圈寬度
*
* @param context
*/
private int mOvalWidth;
/**
* 圓圈最終進度
*
* @param context
*/
private float mOvalProgress;
/**
* 圓圈中間變動進度文字大小
*
* @param context
*/
private int mProgressTextSize = sp2px(30);
/**
* 圓圈中間固定文字尺寸大小
*
* @param context
*/
private int mFixTextSize = sp2px(20);
/**
* 圓圈中間變動進度
*
* @param context
*/
private String mProgressText = "0.0";
/**
* 圓圈中間固定文字
*
* @param context
*/
private String mFixText = "盈利指數";
/**
* 圓圈當前進度
*
* @param context
*/
private float mProgress;
/**
* 畫筆
*/
private Paint mPaint;
/**
* 是否已到達最終進度
*
* @param context
*/
private boolean isStop = false;
/**
* 格式化小數點
*/
private DecimalFormat dff;
/**
* 圓心
*/
private int centreX,centreY;
/**
半徑
*
*/
private int radius = DEFAULT_RADIUS;
在佈局文件中將屬性值設置好,接下來就要在構造方法中獲取屬性了:
public OvalProgrossView(Context context) {
this(context, null);
}
public OvalProgrossView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public OvalProgrossView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.OvalProgressView, defStyleAttr, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.OvalProgressView_ovalColor:
mOvalColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.OvalProgressView_fixTextColor:
mfixTextColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.OvalProgressView_progressTextColor:
mProgressColor = a.getColor(attr, Color.RED);
break;
case R.styleable.OvalProgressView_progressTextSize:
mProgressTextSize = a.getDimensionPixelSize(attr, sp2px(30));
break;
case R.styleable.OvalProgressView_fixTextSize:
mFixTextSize = a.getDimensionPixelSize(attr, sp2px(20));
break;
case R.styleable.OvalProgressView_ovalWidth:
mOvalWidth = a.getDimensionPixelSize(attr, sp2px(5));
break;
case R.styleable.OvalProgressView_progressText:
mProgressText = a.getString(attr);
mOvalProgress = Float.valueOf(mProgressText);
break;
case R.styleable.OvalProgressView_fixText:
mFixText = a.getString(attr);
break;
}
}
a.recycle();
mPaint = new Paint();
dff = new DecimalFormat("0.0");
}
在這個時候呢,可以做一些初始化操作。畫筆,格式化小數點。千萬不要忘了,還有屬性值的回收(a.recycle())。
二、重寫onMeasure()方法;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wideSize = MeasureSpec.getSize(widthMeasureSpec);
int wideMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width, height;
if (wideMode == MeasureSpec.EXACTLY) { //精確值 或matchParent
width = wideSize;
} else {
width = radius * 2 + getPaddingLeft() + getPaddingRight();
if (wideMode == MeasureSpec.AT_MOST) {
width = Math.min(width, wideSize);
}
}
if (heightMode == MeasureSpec.EXACTLY) { //精確值 或matchParent
height = heightSize;
} else {
height = radius * 2 + getPaddingLeft() + getPaddingRight();
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(height, heightSize);
}
}
setMeasuredDimension(width, height);
centreX = width / 2; // 獲取圓心的x座標
centreY = height / 2; // 獲取圓心的y座標
radius = (int) (Math.min(width - getPaddingLeft() - getPaddingRight(),
height - getPaddingTop() - getPaddingBottom()) * 1.0f / 2) - mOvalWidth;
}
我推薦的這一篇文章已經具體介紹了3種模式,我就不具體解說了。
三、畫圓圈和文字
private void paintText(Canvas canvas) {
mPaint.setTextSize(mProgressTextSize);
mPaint.setColor(mProgressColor);
mPaint.setStyle(Paint.Style.FILL); // 設置實心
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(mProgressText, centreX, centreY, mPaint);
Rect rect = new Rect();
mPaint.getTextBounds(mFixText, 0, mFixText.length(), rect);
mPaint.setTextSize(mFixTextSize);
mPaint.setColor(mfixTextColor);
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(mFixText, centreX, centreY + rect.height(), mPaint);
}
public void paintOval(Canvas canvas) {
mPaint.setStrokeWidth(mOvalWidth); // 設置圓環的寬度
mPaint.setAntiAlias(true); // 消除鋸齒
mPaint.setStyle(Paint.Style.STROKE); // 設置空心
mPaint.setColor(mOvalColor); // 設置圓環的顏色
RectF oval = new RectF(centreX - radius, centreY - radius, centreX + radius, centreY + radius);
canvas.drawArc(oval, 270, (float) (-mProgress * 3.6), false, mPaint); // 根據進度畫圓弧
}
四、重寫onDraw()方法;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mOvalProgress == 0) {
isStop = true;
mProgressText = dff.format(mProgress);
} else if (mOvalProgress < 0) {
mProgressText = dff.format(-mProgress);
mProgress = mProgress + 1.1f;
if (mProgress >= -mOvalProgress || getVisibility() == View.INVISIBLE || mProgress >= 100) {
isStop = true;
mProgressText = dff.format(mOvalProgress);//當最後一次畫的時候,顯示最終指定的值.
mProgress = mOvalProgress;
}
} else {
mProgressText = dff.format(mProgress);
mProgress = mProgress + 1.1f;
if (mProgress >= mOvalProgress || getVisibility() == View.INVISIBLE || mProgress >= 100) {
isStop = true;
mProgressText = dff.format(mOvalProgress);//當最後一次畫的時候,顯示最終指定的值.
mProgress = mOvalProgress;
}
}
paintOval(canvas);
paintText(canvas);
if (!isStop) {
postInvalidate();
}
}
在畫圓圈和文字之前要設置好對應的屬性值。由於我們的需求是,如果進度爲負數,圓圈顏色就爲綠色。所以在畫的時候要判斷下最終進度是整數還是負數。
五、根據需求具體情況,添加一些getter和setter方法以及一些工具方法。
public void setmOvalProgress(float mOvalProgress) {
this.mOvalProgress = mOvalProgress;
isStop = false;
postInvalidate();
mProgress = 0 ;
if (mOvalProgress >= 0) {
mOvalColor = WPBApp.getInstance().getResources().getColor(R.color.red);
} else {
mOvalColor = WPBApp.getInstance().getResources().getColor(R.color.green);
}
}
/**
* dp 2 px
*
* @param dpVal
*/
protected int dp2px(int dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, getResources().getDisplayMetrics());
}
/**
* sp 2 px
*
* @param spVal
* @return
*/
protected int sp2px(int spVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
spVal, getResources().getDisplayMetrics());
}
調用 setmOvalProgress()方法,就會像上圖那樣有動畫效果了。一個自定義View就誕生了。
————————————我是分割線—————————————
完成這個View,不可能像我上面說的那樣,非常順利。也遇到了一些坑。
第一、我最開始想到的是繼承View,後來一想繼承progressBar會不會簡化一些,這樣我就可以很方便的去設置進度了。然而,事情並沒有我想像的那樣。整個listView列表的數據會錯亂。而且它的進度是int類型,我的帶有小數。我還需要做轉換,於是還是繼承View吧。
第二,開始畫View的時候,我不知道ondraw()方法可以調用多次,居然使用了線程。所以在onDraw()方法中new了好多線程,導致listView列表卡頓。
第三、開始完全不理解onMeasuer().我連這個方法都沒重寫。
學習是一個積累的過程。在填坑的過程中進步!