本文比較詳細的介紹了繪制圓環(huán)及圓弧的基礎(chǔ)知識,為實現(xiàn)釘釘運動步數(shù)打下基礎(chǔ),實現(xiàn)了下面的效果,實現(xiàn)釘釘運動就灰常簡單了,本文實現(xiàn)的初步效果如下:
如果想直接看釘釘運動的最終效果,請戳:Android進階之自定義控件(2)高仿釘釘運動步數(shù)實現(xiàn)可動的進度圓環(huán)(下)
1、圓環(huán)的繪制
2、繪制背景圓環(huán)和進度圓環(huán)
3、繪制中間的文字
(1)使用drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)繪制圓環(huán):
public class SportStepView extends View {
private Paint mPaint;
//圓環(huán)繪制的寬度
private int mRoundWidth = 40;
public SportStepView(Context context) {
this(context, null);
}
public SportStepView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SportStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲取寬的模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//獲取寬的尺寸
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//對wrap_content這種模式進行處理
if (heightMode == MeasureSpec.AT_MOST) {
heightSize = widthSize;
}
//繪制圓環(huán)以寬度為標準,保存丈量結(jié)果
setMeasuredDimension(widthSize, heightSize);
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(mRoundWidth);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪制圓,設(shè)置畫筆的Style為Paint.Style.STROKE,則繪制出來的為圓環(huán),否則繪制出來的為圓
//canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);
RectF oval = new RectF(0 , 0, getWidth(), getWidth());
//畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
canvas.drawArc(oval, 0, 360, false, mPaint);
}
}
會發(fā)現(xiàn)顯示不全,繪制超出邊界了,因為圓的寬度是在當(dāng)前半徑向兩邊展開的。如下圖分析得知,圓所在的矩形區(qū)域不是rect(為屏幕的矩形區(qū)域),而是real Rect所在的區(qū)域:
因此只需要修改如下即可。
RectF oval = new RectF(0 + mRoundWidth / 2, 0 + mRoundWidth / 2, getWidth() - mRoundWidth / 2, getWidth() - mRoundWidth / 2);
// RectF oval = new RectF(0 , 0, getWidth(), getWidth());
//畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
canvas.drawArc(oval, 0, 360, false, mPaint);
修改后達到我們的預(yù)期效果:
(2)如果只是繪制上面的圓環(huán)效果,還可以使用: canvas.drawCircle()的方式實現(xiàn),這種方法更簡單:
//繪制圓,設(shè)置畫筆的Style為Paint.Style.STROKE,則繪制出來的為圓環(huán),否則繪制出來的為圓
//由于圓環(huán)本身有寬度,所以半徑要減去圓環(huán)寬度的一半,不然一部分圓會在view外面。
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);
(3)接下來我們來實現(xiàn)背景圓環(huán)+進度圓環(huán)的效果了,利用drawCircle繪制背景圓環(huán),drawArc()繪制進度圓環(huán),預(yù)期效果如下:
這個很簡單,再畫個圓弧即可:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪制背景圓環(huán),設(shè)置畫筆的Style為Paint.Style.STROKE,則繪制出來的為圓環(huán),否則繪制出來的為圓
//正常情況下
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);
//正常情況下,繪制進度圓環(huán)
RectF oval = new RectF(0 + mProgressRoundWidth / 2, 0 + mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2);
//畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
canvas.drawArc(oval, 0, 300, false, mProgressPaint);
}
但是光這樣處理,會有個小問題,就是當(dāng)背景圓環(huán)和進度圓環(huán)寬度不一致時,會出現(xiàn)下面的問題。
解決方法:以寬度較大為準即可。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* 如果背景圓環(huán)和進度圓環(huán)寬度不一致,都以較大的寬度為準繪制。避免出現(xiàn)兩者顯示不居中的問題
*/
//繪制背景圓環(huán),設(shè)置畫筆的Style為Paint.Style.STROKE,則繪制出來的為圓環(huán),否則繪制出來的為圓
// canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, mPaint);
if (mRoundWidth < mProgressRoundWidth) {
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mProgressRoundWidth / 2, mPaint);
} else {
//正常情況下
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);
}
if (mRoundWidth < mProgressRoundWidth) {
// //正常情況下,繪制進度圓環(huán)
RectF oval = new RectF(0 + mProgressRoundWidth / 2, 0 + mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2);
// RectF oval = new RectF(0 , 0, getWidth(), getWidth());
//畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
canvas.drawArc(oval, 0, 300, false, mProgressPaint);
} else {
//繪制進度圓環(huán)
RectF oval = new RectF(0 + mRoundWidth / 2, 0 + mRoundWidth / 2, getWidth() - mRoundWidth / 2, getWidth() - mRoundWidth / 2);
//畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
canvas.drawArc(oval, 0, 300, false, mProgressPaint);
}
}
(4)繪制居中的進度文字
代碼實現(xiàn):
//繪制中間的文字
Rect textRect = new Rect();
//進度百分比
int progressPercent = (int) (mCurrentProgress * 1f / mMaxProgress * 100);
String mShowText = progressPercent + "%";
mTextPaint.getTextBounds(mShowText, 0, mShowText.length(), textRect);
canvas.drawText(mShowText, getWidth() / 2 - textRect.width() / 2, getHeight() / 2 + textRect.height() / 2, mTextPaint);
(5)處理圓環(huán)進度和文字進度的動態(tài)顯示
方法一:開一個分線程,動態(tài)改變進度的值,不斷繪制達到進度變化的效果。
方法二:下篇會介紹到_
在測試的Activity中使用:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// mytextview = findViewById(R.id.mytextview);
final SportStepView sportStepView = findViewById(R.id.sportstepview);
mCurrentProgress = 80;
//速度,值越大,變化速度越快
rate = 1;
//開一個分線程,動態(tài)改變進度的值,不斷繪制達到進度變化的效果
new Thread(new Runnable() {
@Override
public void run() {
sportStepView.setCurrentProgress(0);
for (int i = 0; i < mCurrentProgress / rate; i++) {
sportStepView.setCurrentProgress(sportStepView.getCurrentProgress() + rate);
SystemClock.sleep(20);
// pb_progress.invalidate();//invalidate()必須在主線程中執(zhí)行,此處不能使用
sportStepView.postInvalidate();//強制重繪,postInvalidate()可以在主線程也可以在分線程中執(zhí)行
}
}
}).start();
}
完整代碼:
package com.example.jojo.learn.customview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import com.example.jojo.learn.R;
/**
* Created by JoJo on 2018/7/31.
* wechat:18510829974
* description: 仿釘釘運動步數(shù)
*/
public class SportStepView extends View {
//繪制背景圓環(huán)的畫筆
private Paint mPaint;
//繪制外面進度的圓環(huán)的畫筆
private Paint mProgressPaint;
//繪制外面進度的圓環(huán)的畫筆
private Paint mTextPaint;
//背景圓弧的繪制的寬度
private int mRoundWidth = 40;
//進度圓環(huán)的寬度
private float mProgressRoundWidth = 60;
private int mTextSize = 40;//單位 sp
//圓環(huán)最大進度
private int mMaxProgress = 100;
//圓環(huán)當(dāng)前進度
private int mCurrentProgress = 0;
public SportStepView(Context context) {
this(context, null);
}
public SportStepView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SportStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲取寬的模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//獲取寬的尺寸
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//對wrap_content這種模式進行處理
if (heightMode == MeasureSpec.AT_MOST) {
heightSize = widthSize;
}
//以寬度為標準保存丈量結(jié)果
setMeasuredDimension(widthSize, heightSize);
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(mRoundWidth);
mProgressPaint = new Paint();
mProgressPaint.setAntiAlias(true);// 抗鋸齒效果
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setColor(Color.YELLOW);
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);// 圓形筆頭
mProgressPaint.setStrokeWidth(mProgressRoundWidth);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);// 抗鋸齒效果
mTextPaint.setStyle(Paint.Style.STROKE);
mTextPaint.setColor(Color.BLACK);
mTextPaint.setTextSize(sp2px(mTextSize));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* 如果背景圓環(huán)和進度圓環(huán)寬度不一致,都以較大的寬度為準繪制。避免出現(xiàn)兩者顯示不居中的問題
*/
//繪制背景圓環(huán),設(shè)置畫筆的Style為Paint.Style.STROKE,則繪制出來的為圓環(huán),否則繪制出來的為圓
// canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, mPaint);
if (mRoundWidth < mProgressRoundWidth) {
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mProgressRoundWidth / 2, mPaint);
} else {
//正常情況下
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);
}
if (mRoundWidth < mProgressRoundWidth) {
// //正常情況下,繪制進度圓環(huán)
RectF oval = new RectF(0 + mProgressRoundWidth / 2, 0 + mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2);
// RectF oval = new RectF(0 , 0, getWidth(), getWidth());
//畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
canvas.drawArc(oval, 0 + 90, mCurrentProgress * 1f / mMaxProgress * 360, false, mProgressPaint);
} else {
//繪制進度圓環(huán)
RectF oval = new RectF(0 + mRoundWidth / 2, 0 + mRoundWidth / 2, getWidth() - mRoundWidth / 2, getWidth() - mRoundWidth / 2);
//畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
canvas.drawArc(oval, 0 + 90, mCurrentProgress * 1f / mMaxProgress * 360, false, mProgressPaint);
}
//繪制中間的文字
Rect textRect = new Rect();
//進度百分比
int progressPercent = (int) (mCurrentProgress * 1f / mMaxProgress * 100);
String mShowText = progressPercent + "%";
mTextPaint.getTextBounds(mShowText, 0, mShowText.length(), textRect);
canvas.drawText(mShowText, getWidth() / 2 - textRect.width() / 2, getHeight() / 2 + textRect.height() / 2, mTextPaint);
}
public void setCurrentProgress(int currentProgress) {
this.mCurrentProgress = currentProgress;
}
public void setMaxProgress(int maxProgress) {
this.mMaxProgress = maxProgress;
}
public int getMaxProgress() {
return mMaxProgress;
}
public int getCurrentProgress() {
return mCurrentProgress;
}
/**
* 將sp轉(zhuǎn)換成px
*
* @param sp
* @return
*/
private int sp2px(int sp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
getResources().getDisplayMetrics());
}
}
涉及到的自定義屬性
<!--SportStepView-->
<declare-styleable name="SportStepView">
<!--圓環(huán)半徑-->
<attr name="radius" format="dimension"></attr>
<attr name="outerRoundColor" format="color"></attr>
<attr name="innerRoundColor" format="color"></attr>
</declare-styleable>
最后,附上我的一個Kotlin編寫+組件化開發(fā)的開源項目Designer
Kotlin+組件化開發(fā)實踐—開源項目Designer-App
Designer項目算是傾注了我蠻多心血了,每個頁面和功能都當(dāng)成是上線的App來做,App的logo還特地做了UI設(shè)計??力求做到精致和完善,其中還包括了很多自己項目開發(fā)中的經(jīng)驗匯總和對新技術(shù)的探索和整合,希望對各位讀者有所幫助,歡迎點個star,follow,或者給個小心心,嘻嘻??也可以分享給你更多的朋友一起學(xué)習(xí),您的支持是我不斷前進的動力。如果有任何問題,歡迎在GitHub上給我提issue或者留言。