Android自定義View實現(xiàn)仿駕考寶典顯示分數(shù)效果(收藏)
小編最近發(fā)現(xiàn),一些炫酷的view效果,通過需要自定義view和屬性動畫結(jié)合在一起,才能更容易的實現(xiàn)。
實現(xiàn)的效果圖如下:
所用的知識有:
(1)自定義View中的 path ,主要用來繪制指示塊。
(2)屬性動畫-ValueAnimator,并設(shè)置屬性動畫的監(jiān)聽器。
(3)根據(jù)屬性動畫是否結(jié)束的標(biāo)志,決定是否繪制分數(shù)對應(yīng)的描述文本內(nèi)容。
實現(xiàn)步驟:
繼承自View,在構(gòu)造函數(shù)中獲取自定義屬性和初始化操作(初始化畫筆)
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)); }
其中初始化畫筆抽取到一個函數(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設(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; //繪制園的半徑是寬高除去默認內(nèi)邊距 }
核心繪制代碼,覆蓋onDraw()方法,根據(jù)動畫是否結(jié)束的標(biāo)志,決定是否繪制分數(shù)對應(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進行動畫的當(dāng)前值curValue,來動態(tài)改變繪制指示塊和已達進度圓弧,從而實現(xiàn)從0開始移動到設(shè)定值的動畫效果。
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開始移動某一個位置的 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)達到的刻度 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) 繪制分數(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) 動畫結(jié)束時,繪制最終分數(shù)對應(yīng)的提示信息,該信息只有在動畫結(jié)束后,才會顯示出來。
private void drawDescText(Canvas canvas) { canvas.save(); canvas.translate(halfView, halfView); String desc = ""; if (curValue >= 90) { desc = "車神"; } else { desc = "馬路殺手"; } float descLength = descTextPaint.measureText(desc); canvas.drawText(desc, -descLength / 2, dp2Px(30), descTextPaint); canvas.restore(); isAnimEnd = false; // isAnimEnd置為false,防止再次點擊start時,就顯示動畫結(jié)束時才能顯示的內(nèi)容 }
(5)提供對外設(shè)置最大值的接口,決定最后的分數(shù)。
/** * 對外提供的接口,用于設(shè)置進度的最大值 * * @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)記動畫結(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); //默認寬度為200dp private final int DEFAULT_HEIGHT = dp2Px(200); //默認高度為200dp private int viewWidth; //View寬度 private int viewHeight; //View高度 private int halfView; //view寬度或高度的一半 private int radius; //繪制圓形區(qū)域的半徑 private Paint bgPaint; private Paint arrowPaint; //指示塊畫筆 private Paint arcPaint; //圓弧畫筆 private Paint arcReachPaint; //圓弧畫筆 private Paint reachProgressPaint; //已達刻度 private Paint scoreTextPaint; //繪制分數(shù)文本 private Paint descTextPaint; //繪制描述文本 private float degree = 3f; //相鄰刻度之間夾角大小為3度,角度制,不是弧度制 private float lineLength; private int lineColor; private int curValue; //動畫進行的當(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; //半徑是寬高除去默認內(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 = "車神"; } else { desc = "馬路殺手"; } float descLength = descTextPaint.measureText(desc); canvas.drawText(desc, -descLength / 2, dp2Px(30), descTextPaint); canvas.restore(); isAnimEnd = false; // isAnimEnd置為false,防止再次點擊start時,就顯示動畫結(jié)束時才能顯示的內(nèi)容 } /** * 繪制進度 * * @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開始移動某一個位置的 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)達到的刻度 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)建畫筆 * * @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()); } /** * 對外提供的接口,用于設(shè)置進度的最大值 * * @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)記動畫結(jié)束 Log.i("debug", "onAnimationEnd"); Log.i("debug", "onAnimationEnd curValue = " + curValue); invalidate(); } }); valueAnimator.start(); } }
以上所述是小編給大家介紹的Android自定義View實現(xiàn)仿駕考寶典顯示分數(shù)效果(收藏),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
Android中MPAndroidChart自定義繪制最高點標(biāo)識的方法
目前在做一款軟件,要求在展示走勢圖的時候?qū)ψ罡唿c進行自定義繪制,下面這篇文章主要給大家介紹了關(guān)于Android中MPAndroidChart自定義繪制最高點標(biāo)識的方法,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03關(guān)于Android中點擊通知欄的通知啟動Activity問題解決
這篇文章主要介紹了關(guān)于解決Android中點擊通知欄的通知啟動Activity問題的相關(guān)資料,文中介紹的非常詳細,對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。2017-03-03XListView實現(xiàn)下拉刷新和上拉加載原理解析
這篇文章主要為大家解析了XListView實現(xiàn)下拉刷新和上拉加載原理,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12Android中SwipeBack實現(xiàn)右滑返回效果
這篇文章主要介紹了Android中SwipeBack實現(xiàn)右滑返回效果的相關(guān)資料,需要的朋友可以參考下2016-02-02android H5本地緩存加載優(yōu)化的實戰(zhàn)
這篇文章主要介紹了android H5本地緩存加載優(yōu)化的實戰(zhàn),幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-04-04android 類似微信的搖一搖功能實現(xiàn)思路及代碼
微信的搖一搖功能的出現(xiàn),讓彼此之間的距離有近了一步,本文也想實現(xiàn)以下微信的搖一搖功能,感興趣的朋友可以了解下啊,希望本人對你有所幫助2013-01-01Android 自定義view和屬性動畫實現(xiàn)充電進度條效果
近期項目中需要使用到一種類似手機電池充電進度的動畫效果,以前沒學(xué)屬性動畫的時候,是用圖片+定時器的方式來完成的,下面給大家分享android自定義view和屬性動畫實現(xiàn)充電進度條2016-12-12