Android 自定View實(shí)現(xiàn)仿QQ運(yùn)動(dòng)步數(shù)圓弧及動(dòng)畫(huà)效果
在之前的Android超精準(zhǔn)計(jì)步器開(kāi)發(fā)-Dylan計(jì)步中的首頁(yè)用到了一個(gè)自定義控件,和QQ運(yùn)動(dòng)的界面有點(diǎn)類(lèi)似,還有動(dòng)畫(huà)效果,下面就來(lái)講一下這個(gè)View是如何繪制的。
1.先看效果圖
2.效果圖分析
功能說(shuō)明:黃色的代表用戶設(shè)置的總計(jì)劃鍛煉步數(shù),紅色的代表用戶當(dāng)前所走的步數(shù)。
初步分析:完全自定義View重寫(xiě)onDraw()方法,畫(huà)圓弧。
3.畫(huà)一個(gè)圓弧必備知識(shí)
在Canvas中有一個(gè)畫(huà)圓弧的方法
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//畫(huà)弧,
參數(shù)一是RectF對(duì)象,一個(gè)矩形區(qū)域橢圓形的界限用于定義在形狀、大小、電弧,
參數(shù)二是起始角(度)在電弧的開(kāi)始,圓弧起始角度,單位為度。
參數(shù)三圓弧掃過(guò)的角度,順時(shí)針?lè)较?,單位為?從右中間開(kāi)始為零度。
參數(shù)四是如果是true(真)的話,在繪制圓弧時(shí)將圓心包括在內(nèi),通常用來(lái)繪制扇形;如果是false(假)這將是一個(gè)弧線。
參數(shù)五是Paint對(duì)象;
對(duì)于這個(gè)方法,大家可以看一下我手繪的草圖,比較爛,表達(dá)一下這幾個(gè)參數(shù)的意思和繪制過(guò)程,畫(huà)得不好望大家見(jiàn)諒!
4.繪圖的準(zhǔn)備工作
(1).獲取中心點(diǎn)坐標(biāo)
/**中心點(diǎn)的x坐標(biāo)*/ float centerX = (getWidth()) / 2;
(2).建立一個(gè)圓弧外的參考矩形
/**指定圓弧的外輪廓矩形區(qū)域*/ RectF rectF = new RectF(0 + borderWidth, borderWidth, 2 * centerX - borderWidth, 2 * centerX - borderWidth);
5.繪圖的主要步驟
(1).【第一步】繪制整體的黃色圓弧
/** * 1.繪制總步數(shù)的黃色圓弧 * * @param canvas 畫(huà)筆 * @param rectF 參考的矩形 */ private void drawArcYellow(Canvas canvas, RectF rectF) { Paint paint = new Paint(); /** 默認(rèn)畫(huà)筆顏色,黃色 */ paint.setColor(getResources().getColor(R.color.yellow)); /** 結(jié)合處為圓弧*/ paint.setStrokeJoin(Paint.Join.ROUND); /** 設(shè)置畫(huà)筆的樣式 Paint.Cap.Round ,Cap.SQUARE等分別為圓形、方形*/ paint.setStrokeCap(Paint.Cap.ROUND); /** 設(shè)置畫(huà)筆的填充樣式 Paint.Style.FILL :填充內(nèi)部;Paint.Style.FILL_AND_STROKE :填充內(nèi)部和描邊; Paint.Style.STROKE :僅描邊*/ paint.setStyle(Paint.Style.STROKE); /**抗鋸齒功能*/ paint.setAntiAlias(true); /**設(shè)置畫(huà)筆寬度*/ paint.setStrokeWidth(borderWidth); /**繪制圓弧的方法 * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//畫(huà)弧, 參數(shù)一是RectF對(duì)象,一個(gè)矩形區(qū)域橢圓形的界限用于定義在形狀、大小、電弧, 參數(shù)二是起始角(度)在電弧的開(kāi)始,圓弧起始角度,單位為度。 參數(shù)三圓弧掃過(guò)的角度,順時(shí)針?lè)较颍瑔挝粸槎?從右中間開(kāi)始為零度。 參數(shù)四是如果這是true(真)的話,在繪制圓弧時(shí)將圓心包括在內(nèi),通常用來(lái)繪制扇形;如果它是false(假)這將是一個(gè)弧線, 參數(shù)五是Paint對(duì)象; */ canvas.drawArc(rectF, startAngle, angleLength, false, paint); }
(2).【第二步】繪制當(dāng)前進(jìn)度的紅色圓弧
/** * 2.繪制當(dāng)前步數(shù)的紅色圓弧 */ private void drawArcRed(Canvas canvas, RectF rectF) { Paint paintCurrent = new Paint(); paintCurrent.setStrokeJoin(Paint.Join.ROUND); paintCurrent.setStrokeCap(Paint.Cap.ROUND);//圓角弧度 paintCurrent.setStyle(Paint.Style.STROKE);//設(shè)置填充樣式 paintCurrent.setAntiAlias(true);//抗鋸齒功能 paintCurrent.setStrokeWidth(borderWidth);//設(shè)置畫(huà)筆寬度 paintCurrent.setColor(getResources().getColor(R.color.red));//設(shè)置畫(huà)筆顏色 canvas.drawArc(rectF, startAngle, currentAngleLength, false, paintCurrent); }
(3).【第三步】繪制當(dāng)前進(jìn)度的紅色數(shù)字
/** * 3.圓環(huán)中心的步數(shù) */ private void drawTextNumber(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true);//抗鋸齒功能 vTextPaint.setTextSize(numberTextSize); Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL); vTextPaint.setTypeface(font);//字體風(fēng)格 vTextPaint.setColor(getResources().getColor(R.color.red)); Rect bounds_Number = new Rect(); vTextPaint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); canvas.drawText(stepNumber, centerX, getHeight() / 2 + bounds_Number.height() / 2, vTextPaint); }
(4).【第四步】繪制”步數(shù)”的紅色數(shù)字
/** * 4.圓環(huán)中心[步數(shù)]的文字 */ private void drawTextStepString(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextSize(dipToPx(16)); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true);//抗鋸齒功能 vTextPaint.setColor(getResources().getColor(R.color.grey)); String stepString = "步數(shù)"; Rect bounds = new Rect(); vTextPaint.getTextBounds(stepString, 0, stepString.length(), bounds); canvas.drawText(stepString, centerX, getHeight() / 2 + bounds.height() + getFontHeight(numberTextSize), vTextPaint); }
6.動(dòng)畫(huà)是如何實(shí)現(xiàn)的->ValueAnimator
ValueAnimator是整個(gè)屬性動(dòng)畫(huà)機(jī)制當(dāng)中最核心的一個(gè)類(lèi),屬性動(dòng)畫(huà)的運(yùn)行機(jī)制是通過(guò)不斷地對(duì)值進(jìn)行操作來(lái)實(shí)現(xiàn)的,而初始值和結(jié)束值之間的動(dòng)畫(huà)過(guò)渡就是由ValueAnimator這個(gè)類(lèi)來(lái)負(fù)責(zé)計(jì)算的。它的內(nèi)部使用一種時(shí)間循環(huán)的機(jī)制來(lái)計(jì)算值與值之間的動(dòng)畫(huà)過(guò)渡, 我們只需要將初始值和結(jié)束值提供給ValueAnimator,并且告訴它動(dòng)畫(huà)所需運(yùn)行的時(shí)長(zhǎng),那么ValueAnimator就會(huì)自動(dòng)幫我們完成從初始值平滑地過(guò)渡到結(jié)束值這樣的效果。
/*為進(jìn)度設(shè)置動(dòng)畫(huà) * @param start 初始值 * @param current 結(jié)束值 * @param length 動(dòng)畫(huà)時(shí)長(zhǎng) */ private void setAnimation(float start, float current, int length) { ValueAnimator progressAnimator = ValueAnimator.ofFloat(start, current); progressAnimator.setDuration(length); progressAnimator.setTarget(currentAngleLength); progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { /**每次在初始值和結(jié)束值之間產(chǎn)生的一個(gè)平滑過(guò)渡的值,逐步去更新進(jìn)度*/ currentAngleLength = (float) animation.getAnimatedValue(); invalidate(); } }); progressAnimator.start(); }
7.整個(gè)自定義StepArcView的源碼
import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.util.AttributeSet; import android.view.View; import cn.bluemobi.dylan.step.R; /** * Created by DylanAndroid on 2016/5/26. * 顯示步數(shù)的圓弧 */ public class StepArcView extends View { /** * 圓弧的寬度 */ private float borderWidth = 38f; /** * 畫(huà)步數(shù)的數(shù)值的字體大小 */ private float numberTextSize = 0; /** * 步數(shù) */ private String stepNumber = "0"; /** * 開(kāi)始繪制圓弧的角度 */ private float startAngle = 135; /** * 終點(diǎn)對(duì)應(yīng)的角度和起始點(diǎn)對(duì)應(yīng)的角度的夾角 */ private float angleLength = 270; /** * 所要繪制的當(dāng)前步數(shù)的紅色圓弧終點(diǎn)到起點(diǎn)的夾角 */ private float currentAngleLength = 0; /** * 動(dòng)畫(huà)時(shí)長(zhǎng) */ private int animationLength = 3000; public StepArcView(Context context) { super(context); } public StepArcView(Context context, AttributeSet attrs) { super(context, attrs); } public StepArcView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /**中心點(diǎn)的x坐標(biāo)*/ float centerX = (getWidth()) / 2; /**指定圓弧的外輪廓矩形區(qū)域*/ RectF rectF = new RectF(0 + borderWidth, borderWidth, 2 * centerX - borderWidth, 2 * centerX - borderWidth); /**【第一步】繪制整體的黃色圓弧*/ drawArcYellow(canvas, rectF); /**【第二步】繪制當(dāng)前進(jìn)度的紅色圓弧*/ drawArcRed(canvas, rectF); /**【第三步】繪制當(dāng)前進(jìn)度的紅色數(shù)字*/ drawTextNumber(canvas, centerX); /**【第四步】繪制"步數(shù)"的紅色數(shù)字*/ drawTextStepString(canvas, centerX); } /** * 1.繪制總步數(shù)的黃色圓弧 * * @param canvas 畫(huà)筆 * @param rectF 參考的矩形 */ private void drawArcYellow(Canvas canvas, RectF rectF) { Paint paint = new Paint(); /** 默認(rèn)畫(huà)筆顏色,黃色 */ paint.setColor(getResources().getColor(R.color.yellow)); /** 結(jié)合處為圓弧*/ paint.setStrokeJoin(Paint.Join.ROUND); /** 設(shè)置畫(huà)筆的樣式 Paint.Cap.Round ,Cap.SQUARE等分別為圓形、方形*/ paint.setStrokeCap(Paint.Cap.ROUND); /** 設(shè)置畫(huà)筆的填充樣式 Paint.Style.FILL :填充內(nèi)部;Paint.Style.FILL_AND_STROKE :填充內(nèi)部和描邊; Paint.Style.STROKE :僅描邊*/ paint.setStyle(Paint.Style.STROKE); /**抗鋸齒功能*/ paint.setAntiAlias(true); /**設(shè)置畫(huà)筆寬度*/ paint.setStrokeWidth(borderWidth); /**繪制圓弧的方法 * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//畫(huà)弧, 參數(shù)一是RectF對(duì)象,一個(gè)矩形區(qū)域橢圓形的界限用于定義在形狀、大小、電弧, 參數(shù)二是起始角(度)在電弧的開(kāi)始,圓弧起始角度,單位為度。 參數(shù)三圓弧掃過(guò)的角度,順時(shí)針?lè)较?,單位為?從右中間開(kāi)始為零度。 參數(shù)四是如果這是true(真)的話,在繪制圓弧時(shí)將圓心包括在內(nèi),通常用來(lái)繪制扇形;如果它是false(假)這將是一個(gè)弧線, 參數(shù)五是Paint對(duì)象; */ canvas.drawArc(rectF, startAngle, angleLength, false, paint); } /** * 2.繪制當(dāng)前步數(shù)的紅色圓弧 */ private void drawArcRed(Canvas canvas, RectF rectF) { Paint paintCurrent = new Paint(); paintCurrent.setStrokeJoin(Paint.Join.ROUND); paintCurrent.setStrokeCap(Paint.Cap.ROUND);//圓角弧度 paintCurrent.setStyle(Paint.Style.STROKE);//設(shè)置填充樣式 paintCurrent.setAntiAlias(true);//抗鋸齒功能 paintCurrent.setStrokeWidth(borderWidth);//設(shè)置畫(huà)筆寬度 paintCurrent.setColor(getResources().getColor(R.color.red));//設(shè)置畫(huà)筆顏色 canvas.drawArc(rectF, startAngle, currentAngleLength, false, paintCurrent); } /** * 3.圓環(huán)中心的步數(shù) */ private void drawTextNumber(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true);//抗鋸齒功能 vTextPaint.setTextSize(numberTextSize); Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL); vTextPaint.setTypeface(font);//字體風(fēng)格 vTextPaint.setColor(getResources().getColor(R.color.red)); Rect bounds_Number = new Rect(); vTextPaint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); canvas.drawText(stepNumber, centerX, getHeight() / 2 + bounds_Number.height() / 2, vTextPaint); } /** * 4.圓環(huán)中心[步數(shù)]的文字 */ private void drawTextStepString(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextSize(dipToPx(16)); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true);//抗鋸齒功能 vTextPaint.setColor(getResources().getColor(R.color.grey)); String stepString = "步數(shù)"; Rect bounds = new Rect(); vTextPaint.getTextBounds(stepString, 0, stepString.length(), bounds); canvas.drawText(stepString, centerX, getHeight() / 2 + bounds.height() + getFontHeight(numberTextSize), vTextPaint); } /** * 獲取當(dāng)前步數(shù)的數(shù)字的高度 * * @param fontSize 字體大小 * @return 字體高度 */ public int getFontHeight(float fontSize) { Paint paint = new Paint(); paint.setTextSize(fontSize); Rect bounds_Number = new Rect(); paint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); return bounds_Number.height(); } /** * dip 轉(zhuǎn)換成px * * @param dip * @return */ private int dipToPx(float dip) { float density = getContext().getResources().getDisplayMetrics().density; return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1)); } /** * 所走的步數(shù)進(jìn)度 * * @param totalStepNum 設(shè)置的步數(shù) * @param currentCounts 所走步數(shù) */ public void setCurrentCount(int totalStepNum, int currentCounts) { stepNumber = currentCounts + ""; setTextSize(currentCounts); /**如果當(dāng)前走的步數(shù)超過(guò)總步數(shù)則圓弧還是270度,不能成為園*/ if (currentCounts > totalStepNum) { currentCounts = totalStepNum; } /**所走步數(shù)占用總共步數(shù)的百分比*/ float scale = (float) currentCounts / totalStepNum; /**換算成弧度最后要到達(dá)的角度的長(zhǎng)度-->弧長(zhǎng)*/ float currentAngleLength = scale * angleLength; /**開(kāi)始執(zhí)行動(dòng)畫(huà)*/ setAnimation(0, currentAngleLength, animationLength); } /** * 為進(jìn)度設(shè)置動(dòng)畫(huà) * ValueAnimator是整個(gè)屬性動(dòng)畫(huà)機(jī)制當(dāng)中最核心的一個(gè)類(lèi),屬性動(dòng)畫(huà)的運(yùn)行機(jī)制是通過(guò)不斷地對(duì)值進(jìn)行操作來(lái)實(shí)現(xiàn)的, * 而初始值和結(jié)束值之間的動(dòng)畫(huà)過(guò)渡就是由ValueAnimator這個(gè)類(lèi)來(lái)負(fù)責(zé)計(jì)算的。 * 它的內(nèi)部使用一種時(shí)間循環(huán)的機(jī)制來(lái)計(jì)算值與值之間的動(dòng)畫(huà)過(guò)渡, * 我們只需要將初始值和結(jié)束值提供給ValueAnimator,并且告訴它動(dòng)畫(huà)所需運(yùn)行的時(shí)長(zhǎng), * 那么ValueAnimator就會(huì)自動(dòng)幫我們完成從初始值平滑地過(guò)渡到結(jié)束值這樣的效果。 * * @param last * @param current */ private void setAnimation(float last, float current, int length) { ValueAnimator progressAnimator = ValueAnimator.ofFloat(last, current); progressAnimator.setDuration(length); progressAnimator.setTarget(currentAngleLength); progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { currentAngleLength = (float) animation.getAnimatedValue(); invalidate(); } }); progressAnimator.start(); } /** * 設(shè)置文本大小,防止步數(shù)特別大之后放不下,將字體大小動(dòng)態(tài)設(shè)置 * * @param num */ public void setTextSize(int num) { String s = String.valueOf(num); int length = s.length(); if (length <= 4) { numberTextSize = dipToPx(50); } else if (length > 4 && length <= 6) { numberTextSize = dipToPx(40); } else if (length > 6 && length <= 8) { numberTextSize = dipToPx(30); } else if (length > 8) { numberTextSize = dipToPx(25); } } }
8.用法說(shuō)明
xml中
<cn.bluemobi.dylan.step.view.StepArcView android:id="@+id/sv " android:layout_width="200dp" android:layout_height="200dp" android:layout_centerHorizontal="true" android:layout_marginTop="50dp" />
Activity中
StepArcView sv = (StepArcView) findViewById(R.id.sv); sv.setCurrentCount(7000, 1000);
以上所述是小編給大家介紹的Android 仿QQ運(yùn)動(dòng)步數(shù)圓弧及動(dòng)畫(huà)效果,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Android?Scroller實(shí)現(xiàn)彈性滑動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了Android?Scroller實(shí)現(xiàn)彈性滑動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04Android中實(shí)現(xiàn)長(zhǎng)按修改ListView對(duì)象的內(nèi)容
這篇文章主要給大家介紹了在Android中實(shí)現(xiàn)長(zhǎng)按修改ListView對(duì)象內(nèi)容的相關(guān)資料,文中給出了完整的示例代碼,相信對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-02-02Kotlin擴(kuò)展函數(shù)及實(shí)現(xiàn)機(jī)制的深入探索
擴(kuò)展函數(shù)與擴(kuò)展屬性的神奇之處在于,可以在不修改原來(lái)類(lèi)的條件下,使用函數(shù)和屬性,表現(xiàn)得就像是屬于這個(gè)類(lèi)的一樣。下面這篇文章主要給大家介紹了關(guān)于Kotlin擴(kuò)展函數(shù)及實(shí)現(xiàn)機(jī)制的相關(guān)資料,需要的朋友可以參考下2018-06-06如何使用SurfaceView實(shí)現(xiàn)魚(yú)兒游動(dòng)動(dòng)畫(huà)
這篇文章主要教大家如何使用SurfaceView實(shí)現(xiàn)魚(yú)兒游動(dòng)動(dòng)畫(huà),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04從源碼編譯Android系統(tǒng)的Java類(lèi)庫(kù)和JNI動(dòng)態(tài)庫(kù)的方法
這篇文章主要介紹了從源碼編譯Android系統(tǒng)的Java類(lèi)庫(kù)和JNI動(dòng)態(tài)庫(kù)的方法,例子基于Linux系統(tǒng)環(huán)境下來(lái)講,需要的朋友可以參考下2016-02-02Kotlin標(biāo)準(zhǔn)庫(kù)函數(shù)使用分析及介紹
Kotlin提供了一個(gè)系統(tǒng)庫(kù),是Java庫(kù)的增強(qiáng)。其中有很多函數(shù)在適配了Java的類(lèi)型和方法同時(shí)使用Kotlin的語(yǔ)法。其中一些底層的函數(shù) 是使用比較廣泛的2022-09-09Flutter實(shí)現(xiàn)笑嘻嘻的動(dòng)態(tài)表情的示例代碼
這篇文章主要為大家介紹了如何利用Flutter實(shí)現(xiàn)笑嘻嘻的動(dòng)態(tài)表情,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Flutter有一定幫助,感興趣的可以了解一下2022-04-04