Android自定義View實(shí)現(xiàn)仿駕考寶典顯示分?jǐn)?shù)效果(收藏)
小編最近發(fā)現(xiàn),一些炫酷的view效果,通過(guò)需要自定義view和屬性動(dòng)畫(huà)結(jié)合在一起,才能更容易的實(shí)現(xiàn)。
實(shí)現(xiàn)的效果圖如下:

所用的知識(shí)有:
(1)自定義View中的 path ,主要用來(lái)繪制指示塊。
(2)屬性動(dòng)畫(huà)-ValueAnimator,并設(shè)置屬性動(dòng)畫(huà)的監(jiān)聽(tīng)器。
(3)根據(jù)屬性動(dòng)畫(huà)是否結(jié)束的標(biāo)志,決定是否繪制分?jǐn)?shù)對(duì)應(yīng)的描述文本內(nèi)容。
實(shí)現(xiàn)步驟:
繼承自View,在構(gòu)造函數(shù)中獲取自定義屬性和初始化操作(初始化畫(huà)筆)
private void obtainAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ScoreView);
lineLength = typedArray.getDimension(R.styleable.ScoreView_lineLength, dp2Px(10));
lineColor = typedArray.getColor(R.styleable.ScoreView_lineColor, Color.WHITE);
typedArray.recycle();
}
private void init() {
arrowPaint = createPaint(Color.WHITE, 0, Paint.Style.FILL, 0);
arcPaint = createPaint(lineColor, dp2Px(1), Paint.Style.STROKE, 0);
bgPaint = createPaint(lineColor, dp2Px(1), Paint.Style.FILL, 0);
reachProgressPaint = createPaint(Color.WHITE, dp2Px(1), Paint.Style.FILL, 0);
arcReachPaint = createPaint(Color.WHITE, dp2Px(1), Paint.Style.STROKE, 0);
scoreTextPaint = createPaint(Color.WHITE, 0, Paint.Style.STROKE, dp2Px(26));
descTextPaint = createPaint(Color.WHITE, 0, Paint.Style.STROKE, dp2Px(16));
}
其中初始化畫(huà)筆抽取到一個(gè)函數(shù)中:
private Paint createPaint(int color, int strokeWidth, Paint.Style style, float textSize) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(style);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setTextSize(textSize);
return paint;
}
覆蓋 onSizeChanged() ,得到控件的寬高,并決定要繪制區(qū)域的大?。丶J(rèn)設(shè)置了內(nèi)邊距)
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
viewWidth = w;
viewHeight = h;
halfView = Math.min(viewWidth, viewHeight) / 2; //寬度或高度中最小值的一半,即決定圓心的位置。
radius = (Math.min(viewWidth, viewHeight) - 2 * DEFAULT_PADDING) / 2; //繪制園的半徑是寬高除去默認(rèn)內(nèi)邊距
}
核心繪制代碼,覆蓋onDraw()方法,根據(jù)動(dòng)畫(huà)是否結(jié)束的標(biāo)志,決定是否繪制分?jǐn)?shù)對(duì)應(yīng)的文本。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawArcBackground(canvas);
drawArcProgress(canvas);
drawScoreText(canvas);
if (isAnimEnd) {
drawDescText(canvas);
}
}
(1)繪制圓弧背景和灰色刻度背景。
private void drawArcBackground(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
//繪制圓弧
RectF rectF = new RectF(dp2Px(5) - radius, dp2Px(5) - radius, radius - dp2Px(5), radius - dp2Px(5));
canvas.drawArc(rectF, 120, 300, false, arcPaint);
//繪制刻度線
canvas.rotate(30);
for (int i = 0; i < 100; i++) {
canvas.drawLine(0, radius - dp2Px(15), 0,
radius - dp2Px(15) - lineLength,
bgPaint);
canvas.rotate(degree);
}
canvas.restore();
}
(2) 繪制刻度,根據(jù)ValueAnimator進(jìn)行動(dòng)畫(huà)的當(dāng)前值curValue,來(lái)動(dòng)態(tài)改變繪制指示塊和已達(dá)進(jìn)度圓弧,從而實(shí)現(xiàn)從0開(kāi)始移動(dòng)到設(shè)定值的動(dòng)畫(huà)效果。
private void drawArcProgress(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
//繪制圓弧
RectF rectF = new RectF(dp2Px(5) - radius, dp2Px(5) - radius, radius - dp2Px(5), radius - dp2Px(5));
canvas.drawArc(rectF, 120, curValue * degree, false, arcReachPaint);
//繪制指示方塊,方塊是從0開(kāi)始移動(dòng)某一個(gè)位置的
canvas.rotate(30 + degree * curValue);
Path path = new Path();
path.moveTo(dp2Px(5), radius);
path.lineTo(dp2Px(5), radius - dp2Px(10));
path.lineTo(0, radius - dp2Px(15));
path.lineTo(-dp2Px(5), radius - dp2Px(10));
path.lineTo(-dp2Px(5), radius);
path.close();
canvas.drawPath(path, arrowPaint);
//繪制已經(jīng)達(dá)到的刻度
canvas.restore();
canvas.save();
canvas.translate(halfView, halfView);
canvas.rotate(30);
for (int i = 0; i < curValue; i++) {
canvas.drawLine(0, radius - dp2Px(15), 0,
radius - dp2Px(15) - lineLength,
reachProgressPaint);
canvas.rotate(degree);
}
canvas.restore();
}
(3) 繪制分?jǐn)?shù)文本。
private void drawScoreText(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
String scoreText = curValue + "分";
float textLength = scoreTextPaint.measureText(scoreText);
canvas.drawText(scoreText, -textLength / 2, 0, scoreTextPaint);
canvas.restore();
}
(4) 動(dòng)畫(huà)結(jié)束時(shí),繪制最終分?jǐn)?shù)對(duì)應(yīng)的提示信息,該信息只有在動(dòng)畫(huà)結(jié)束后,才會(huì)顯示出來(lái)。
private void drawDescText(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
String desc = "";
if (curValue >= 90) {
desc = "車(chē)神";
} else {
desc = "馬路殺手";
}
float descLength = descTextPaint.measureText(desc);
canvas.drawText(desc, -descLength / 2, dp2Px(30), descTextPaint);
canvas.restore();
isAnimEnd = false; // isAnimEnd置為false,防止再次點(diǎn)擊start時(shí),就顯示動(dòng)畫(huà)結(jié)束時(shí)才能顯示的內(nèi)容
}
(5)提供對(duì)外設(shè)置最大值的接口,決定最后的分?jǐn)?shù)。
/**
* 對(duì)外提供的接口,用于設(shè)置進(jìn)度的最大值
*
* @param value
*/
public void setMaxValue(int value) {
if (valueAnimator == null) {
valueAnimator = ValueAnimator.ofInt(0, value);
}
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setDuration(30 * value);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
curValue = (int) animation.getAnimatedValue();
Log.i("debug", "curValue=" + curValue);
invalidate();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isAnimEnd = true; //標(biāo)記動(dòng)畫(huà)結(jié)束
Log.i("debug", "onAnimationEnd");
Log.i("debug", "onAnimationEnd curValue = " + curValue);
invalidate();
}
});
valueAnimator.start();
}
完整代碼:
public class ScoreView extends View {
private final int DEFAULT_PADDING = dp2Px(5);
private final int DEFAULT_WIDTH = dp2Px(200); //默認(rèn)寬度為200dp
private final int DEFAULT_HEIGHT = dp2Px(200); //默認(rèn)高度為200dp
private int viewWidth; //View寬度
private int viewHeight; //View高度
private int halfView; //view寬度或高度的一半
private int radius; //繪制圓形區(qū)域的半徑
private Paint bgPaint;
private Paint arrowPaint; //指示塊畫(huà)筆
private Paint arcPaint; //圓弧畫(huà)筆
private Paint arcReachPaint; //圓弧畫(huà)筆
private Paint reachProgressPaint; //已達(dá)刻度
private Paint scoreTextPaint; //繪制分?jǐn)?shù)文本
private Paint descTextPaint; //繪制描述文本
private float degree = 3f; //相鄰刻度之間夾角大小為3度,角度制,不是弧度制
private float lineLength;
private int lineColor;
private int curValue; //動(dòng)畫(huà)進(jìn)行的當(dāng)前值
private ValueAnimator valueAnimator;
private boolean isAnimEnd = false;
public ScoreView(Context context) {
this(context, null);
}
public ScoreView(Context context, AttributeSet attrs) {
super(context, attrs);
obtainAttrs(context, attrs);
init();
}
private void obtainAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ScoreView);
lineLength = typedArray.getDimension(R.styleable.ScoreView_lineLength, dp2Px(10));
lineColor = typedArray.getColor(R.styleable.ScoreView_lineColor, Color.WHITE);
typedArray.recycle();
}
private void init() {
arrowPaint = createPaint(Color.WHITE, 0, Paint.Style.FILL, 0);
arcPaint = createPaint(lineColor, dp2Px(1), Paint.Style.STROKE, 0);
bgPaint = createPaint(lineColor, dp2Px(1), Paint.Style.FILL, 0);
reachProgressPaint = createPaint(Color.WHITE, dp2Px(1), Paint.Style.FILL, 0);
arcReachPaint = createPaint(Color.WHITE, dp2Px(1), Paint.Style.STROKE, 0);
scoreTextPaint = createPaint(Color.WHITE, 0, Paint.Style.STROKE, dp2Px(26));
descTextPaint = createPaint(Color.WHITE, 0, Paint.Style.STROKE, dp2Px(16));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureSize(widthMeasureSpec, DEFAULT_WIDTH), measureSize(heightMeasureSpec, DEFAULT_HEIGHT));
}
private int measureSize(int measureSpec, int defaultSize) {
int measureSize = defaultSize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
measureSize = size;
} else {
if (mode == MeasureSpec.AT_MOST) {
measureSize = Math.min(defaultSize, size);
}
}
return measureSize;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
viewWidth = w;
viewHeight = h;
halfView = Math.min(viewWidth, viewHeight) / 2; //寬度或高度中最小值的一半,即圓形的位置
radius = (Math.min(viewWidth, viewHeight) - 2 * DEFAULT_PADDING) / 2; //半徑是寬高除去默認(rèn)內(nèi)邊距的
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawArcBackground(canvas);
drawArcProgress(canvas);
drawScoreText(canvas);
if (isAnimEnd) {
drawDescText(canvas);
}
}
private void drawScoreText(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
String scoreText = curValue + "分";
float textLength = scoreTextPaint.measureText(scoreText);
canvas.drawText(scoreText, -textLength / 2, 0, scoreTextPaint);
canvas.restore();
}
/**
* 繪制文本
*
* @param canvas
*/
private void drawDescText(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
String desc = "";
if (curValue >= 90) {
desc = "車(chē)神";
} else {
desc = "馬路殺手";
}
float descLength = descTextPaint.measureText(desc);
canvas.drawText(desc, -descLength / 2, dp2Px(30), descTextPaint);
canvas.restore();
isAnimEnd = false; // isAnimEnd置為false,防止再次點(diǎn)擊start時(shí),就顯示動(dòng)畫(huà)結(jié)束時(shí)才能顯示的內(nèi)容
}
/**
* 繪制進(jìn)度
*
* @param canvas
*/
private void drawArcProgress(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
//繪制圓弧
RectF rectF = new RectF(dp2Px(5) - radius, dp2Px(5) - radius, radius - dp2Px(5), radius - dp2Px(5));
canvas.drawArc(rectF, 120, curValue * degree, false, arcReachPaint);
//繪制指示方塊,方塊是從0開(kāi)始移動(dòng)某一個(gè)位置的
canvas.rotate(30 + degree * curValue);
Path path = new Path();
path.moveTo(dp2Px(5), radius);
path.lineTo(dp2Px(5), radius - dp2Px(10));
path.lineTo(0, radius - dp2Px(15));
path.lineTo(-dp2Px(5), radius - dp2Px(10));
path.lineTo(-dp2Px(5), radius);
path.close();
canvas.drawPath(path, arrowPaint);
//繪制已經(jīng)達(dá)到的刻度
canvas.restore();
canvas.save();
canvas.translate(halfView, halfView);
canvas.rotate(30);
for (int i = 0; i < curValue; i++) {
canvas.drawLine(0, radius - dp2Px(15), 0,
radius - dp2Px(15) - lineLength,
reachProgressPaint);
canvas.rotate(degree);
}
canvas.restore();
}
/**
* 繪制背景
*
* @param canvas
*/
private void drawArcBackground(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
//繪制圓弧
RectF rectF = new RectF(dp2Px(5) - radius, dp2Px(5) - radius, radius - dp2Px(5), radius - dp2Px(5));
canvas.drawArc(rectF, 120, 300, false, arcPaint);
//繪制刻度線
canvas.rotate(30);
for (int i = 0; i < 100; i++) {
canvas.drawLine(0, radius - dp2Px(15), 0,
radius - dp2Px(15) - lineLength,
bgPaint);
canvas.rotate(degree);
}
canvas.restore();
}
/**
* 創(chuàng)建畫(huà)筆
*
* @param color
* @param strokeWidth
* @param style
* @param textSize
* @return
*/
private Paint createPaint(int color, int strokeWidth, Paint.Style style, float textSize) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(style);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setTextSize(textSize);
return paint;
}
/**
* dp轉(zhuǎn)換成px
*
* @param dpValue
* @return
*/
private int dp2Px(int dpValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpValue, getResources().getDisplayMetrics());
}
/**
* 對(duì)外提供的接口,用于設(shè)置進(jìn)度的最大值
*
* @param value
*/
public void setMaxValue(int value) {
if (valueAnimator == null) {
valueAnimator = ValueAnimator.ofInt(0, value);
}
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setDuration(30 * value);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
curValue = (int) animation.getAnimatedValue();
Log.i("debug", "curValue=" + curValue);
invalidate();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isAnimEnd = true; //標(biāo)記動(dòng)畫(huà)結(jié)束
Log.i("debug", "onAnimationEnd");
Log.i("debug", "onAnimationEnd curValue = " + curValue);
invalidate();
}
});
valueAnimator.start();
}
}
以上所述是小編給大家介紹的Android自定義View實(shí)現(xiàn)仿駕考寶典顯示分?jǐn)?shù)效果(收藏),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Android中MPAndroidChart自定義繪制最高點(diǎn)標(biāo)識(shí)的方法
目前在做一款軟件,要求在展示走勢(shì)圖的時(shí)候?qū)ψ罡唿c(diǎn)進(jìn)行自定義繪制,下面這篇文章主要給大家介紹了關(guān)于Android中MPAndroidChart自定義繪制最高點(diǎn)標(biāo)識(shí)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03
Android記事本項(xiàng)目開(kāi)發(fā)
這篇文章主要為大家詳細(xì)介紹了Android記事本項(xiàng)目開(kāi)發(fā)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
關(guān)于Android中點(diǎn)擊通知欄的通知啟動(dòng)Activity問(wèn)題解決
這篇文章主要介紹了關(guān)于解決Android中點(diǎn)擊通知欄的通知啟動(dòng)Activity問(wèn)題的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-03-03
Mac中Eclipse連不上Android手機(jī)的解決方法
這篇文章主要介紹了Mac中Eclipse連不上Android手機(jī)的解決方法,本文方法同樣適用其它的移動(dòng)設(shè)備,需要的朋友可以參考下2015-06-06
Android自定義控件實(shí)現(xiàn)下拉刷新效果
這篇文章主要為大家詳細(xì)介紹了Android自定義控件實(shí)現(xiàn)下拉刷新效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
XListView實(shí)現(xiàn)下拉刷新和上拉加載原理解析
這篇文章主要為大家解析了XListView實(shí)現(xiàn)下拉刷新和上拉加載原理,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Android中SwipeBack實(shí)現(xiàn)右滑返回效果
這篇文章主要介紹了Android中SwipeBack實(shí)現(xiàn)右滑返回效果的相關(guān)資料,需要的朋友可以參考下2016-02-02
android H5本地緩存加載優(yōu)化的實(shí)戰(zhàn)
這篇文章主要介紹了android H5本地緩存加載優(yōu)化的實(shí)戰(zhàn),幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-04-04
android 類(lèi)似微信的搖一搖功能實(shí)現(xiàn)思路及代碼
微信的搖一搖功能的出現(xiàn),讓彼此之間的距離有近了一步,本文也想實(shí)現(xiàn)以下微信的搖一搖功能,感興趣的朋友可以了解下啊,希望本人對(duì)你有所幫助2013-01-01
Android 自定義view和屬性動(dòng)畫(huà)實(shí)現(xiàn)充電進(jìn)度條效果
近期項(xiàng)目中需要使用到一種類(lèi)似手機(jī)電池充電進(jìn)度的動(dòng)畫(huà)效果,以前沒(méi)學(xué)屬性動(dòng)畫(huà)的時(shí)候,是用圖片+定時(shí)器的方式來(lái)完成的,下面給大家分享android自定義view和屬性動(dòng)畫(huà)實(shí)現(xiàn)充電進(jìn)度條2016-12-12

