Android自定義View實現(xiàn)圓形加載進(jìn)度條效果的方法

這篇文章將為大家詳細(xì)講解有關(guān)Android自定義View實現(xiàn)圓形加載進(jìn)度條效果的方法,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

我們提供的服務(wù)有:成都做網(wǎng)站、網(wǎng)站制作、成都外貿(mào)網(wǎng)站建設(shè)、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、沛縣ssl等。為1000+企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的沛縣網(wǎng)站制作公司

View仿華為圓形加載進(jìn)度條效果圖

Android自定義View實現(xiàn)圓形加載進(jìn)度條效果的方法

實現(xiàn)思路

可以看出該View可分為三個部分來實現(xiàn)

最外圍的圓,該部分需要區(qū)分進(jìn)度圓和底部的刻度圓,進(jìn)度部分的刻度需要和底色刻度區(qū)分開來

中間顯示的文字進(jìn)度,需要讓文字在View中居中顯示

旋轉(zhuǎn)的小圓點(diǎn),小圓點(diǎn)需要模擬小球下落運(yùn)動時的加速度效果,開始下落的時候慢,到最底部時最快,上來時速度再逐漸減慢

具體實現(xiàn)

先具體細(xì)分講解,博客最后面給出全部源碼

(1)首先為View創(chuàng)建自定義的xml屬性
在工程的values目錄下新建attrs.xml文件

<resources>
 <!-- 仿華為圓形加載進(jìn)度條 -->
 <declare-styleable name="CircleLoading">
 <attr name="indexColor" format="color"/>
 <attr name="baseColor" format="color"/>
 <attr name="dotColor" format="color"/>
 <attr name="textSize" format="dimension"/>
 <attr name="textColor" format="color"/>
 </declare-styleable>
</resources>

各個屬性的作用:

indexColor:進(jìn)度圓的顏色
baseColor:刻度圓底色
dotColor:小圓點(diǎn)顏色
textSize:文字大小
textColor:文字顏色

(2)新建CircleLoadingView類繼承View類,重寫它的三個構(gòu)造方法,獲取用戶設(shè)置的屬性,同時指定默認(rèn)值

public CircleLoadingView(Context context) {
 this(context, null);
 }

 public CircleLoadingView(Context context, AttributeSet attrs) {
 this(context, attrs, 0);
 }

 public CircleLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 // 獲取用戶配置屬性
 TypedArray tya = context.obtainStyledAttributes(attrs, R.styleable.CircleLoading);
 baseColor = tya.getColor(R.styleable.CircleLoading_baseColor, Color.LTGRAY);
 indexColor = tya.getColor(R.styleable.CircleLoading_indexColor, Color.BLUE);
 textColor = tya.getColor(R.styleable.CircleLoading_textColor, Color.BLUE);
 dotColor = tya.getColor(R.styleable.CircleLoading_dotColor, Color.RED);
 textSize = tya.getDimensionPixelSize(R.styleable.CircleLoading_textSize, 36);
 tya.recycle();

 initUI();
 }

我們從View繪制的第一步開始

(3)測量onMeasure,首先需要測量出View的寬和高,并指定View在wrap_content時的最小范圍,對于View繪制流程還不熟悉的同學(xué),可以先去了解下具體的繪制流程

淺談Android View繪制三大流程探索及常見問題

重寫onMeasure方法,其中我們要考慮當(dāng)View的寬高被指定為wrap_content時的情況,如果我們不對wrap_content的情況進(jìn)行處理,那么當(dāng)使用者指定View的寬高為wrap_content時將無法正常顯示出View

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);

 int myWidthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
 int myWidthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
 int myHeightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
 int myHeightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

 // 獲取寬
 if (myWidthSpecMode == MeasureSpec.EXACTLY) {
  // match_parent/精確值
  mWidth = myWidthSpecSize;
 } else {
  // wrap_content
  mWidth = DensityUtil.dip2px(mContext, 120);
 }

 // 獲取高
 if (myHeightSpecMode == MeasureSpec.EXACTLY) {
  // match_parent/精確值
  mHeight = myHeightSpecSize;
 } else {
  // wrap_content
  mHeight = DensityUtil.dip2px(mContext, 120);
 }

 // 設(shè)置該view的寬高
 setMeasuredDimension(mWidth, mHeight);
 }

MeasureSpec的狀態(tài)分為三種EXACTLY、AT_MOST、UNSPECIFIED,這里只要單獨(dú)指定非精確值EXACTLY之外的情況就好了。

本文中使用到的DensityUtil類,是為了將dp轉(zhuǎn)換為px來使用,以便適配不同的屏幕顯示效果

public static int dip2px(Context context, float dpValue) {
 final float scale = context.getResources().getDisplayMetrics().density;
 return (int) (dpValue * scale + 0.5f);
 }

(4)重寫onDraw,繪制需要顯示的內(nèi)容

因為做的是單純的View而不是ViewGroup,內(nèi)部沒有子控件需要確定位置,所以可直接跳過onLayout方法,直接開始對View進(jìn)行繪制
分為三個部分繪制,繪制刻度圓,繪制文字值,繪制旋轉(zhuǎn)小圓點(diǎn)

@Override
 protected void onDraw(Canvas canvas) {
 drawArcScale(canvas);
 drawTextValue(canvas);
 drawRotateDot(canvas);
 }

繪制刻度圓

先畫一個小豎線,通過canvas.rotate()方法每次旋轉(zhuǎn)3.6度(總共360度,用100/360=3.6)得到一個刻度為100的圓,然后通過progress參數(shù),得到要顯示的進(jìn)度數(shù),并把小于progress的刻度變成進(jìn)度圓的顏色

 /**
 * 畫刻度
 */
 private void drawArcScale(Canvas canvas) {
 canvas.save();

 for (int i = 0; i < 100; i++) {
  if (progress > i) {
  mScalePaint.setColor(indexColor);
  } else {
  mScalePaint.setColor(baseColor);
  }
  canvas.drawLine(mWidth / 2, 0, mHeight / 2, DensityUtil.dip2px(mContext, 10), mScalePaint);
  // 旋轉(zhuǎn)的度數(shù) = 100 / 360
  canvas.rotate(3.6f, mWidth / 2, mHeight / 2);
 }

 canvas.restore();
 }

繪制中間文字

文字繪制的坐標(biāo)是以文字的左下角開始繪制的,所以需要先通過把文字裝載到一個矩形Rect,通過畫筆的getTextBounds方法取得字符串的長度和寬度,通過動態(tài)計算,來使文字居中顯示

 /**
 * 畫內(nèi)部數(shù)值
 */
 private void drawTextValue(Canvas canvas) {
 canvas.save();

 String showValue = String.valueOf(progress);
 Rect textBound = new Rect();
 mTextPaint.getTextBounds(showValue, 0, showValue.length(), textBound); // 獲取文字的矩形范圍
 float textWidth = textBound.right - textBound.left; // 獲得文字寬
 float textHeight = textBound.bottom - textBound.top; // 獲得文字高
 canvas.drawText(showValue, mWidth / 2 - textWidth / 2, mHeight / 2 + textHeight / 2, mTextPaint);

 canvas.restore();
 }

繪制旋轉(zhuǎn)小圓點(diǎn)

這個小圓點(diǎn)就是簡單的繪制一個填充的圓形就好

 /**
 * 畫旋轉(zhuǎn)小圓點(diǎn)
 */
 private void drawRotateDot(final Canvas canvas) {
 canvas.save();

 canvas.rotate(mDotProgress * 3.6f, mWidth / 2, mHeight / 2);
 canvas.drawCircle(mWidth / 2, DensityUtil.dip2px(mContext, 10) + DensityUtil.dip2px(mContext, 5), DensityUtil.dip2px(mContext, 3), mDotPaint);

 canvas.restore();
 }

讓它自己動起來可以通過兩種方式,一種是開一個線程,在線程中改變mDotProgress的數(shù)值,并通過postInvalidate方法跨線程刷新View的顯示效果

 new Thread() {
  @Override
  public void run() {
  while (true) {
   mDotProgress++;
   if (mDotProgress == 100) {
   mDotProgress = 0;
   }
   postInvalidate();
   try {
   Thread.sleep(50);
   } catch (InterruptedException e) {
   e.printStackTrace();
   }
  }
  }
 }.start();

開線程的方式不推薦使用,這是沒必要的開銷,而且線程不好控制,要實現(xiàn)讓小圓點(diǎn)在運(yùn)行過程中開始和結(jié)束時慢,運(yùn)動到中間時加快這種效果不好實現(xiàn),所以最好的方式是使用屬性動畫,需要讓小圓點(diǎn)動起來時,調(diào)用以下方法就好了

 /**
 * 啟動小圓點(diǎn)旋轉(zhuǎn)動畫
 */
 public void startDotAnimator() {
 animator = ValueAnimator.ofFloat(0, 100);
 animator.setDuration(1500);
 animator.setRepeatCount(ValueAnimator.INFINITE);
 animator.setRepeatMode(ValueAnimator.RESTART);
 animator.setInterpolator(new AccelerateDecelerateInterpolator());
 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  @Override
  public void onAnimationUpdate(ValueAnimator animation) {
  // 設(shè)置小圓點(diǎn)的進(jìn)度,并通知界面重繪
  mDotProgress = (Float) animation.getAnimatedValue();
  invalidate();
  }
 });
 animator.start();
 }

在屬性動畫中可以通過setInterpolator方法指定不同的插值器,這里要模擬小球掉下來的重力效果,所以需要使用AccelerateDecelerateInterpolator插值類,該類的效果就是在動畫開始時和結(jié)束時變慢,中間加快

(5)設(shè)置當(dāng)前進(jìn)度值

對外提供一個方法,用來更新當(dāng)前圓的進(jìn)度

 /**
 * 設(shè)置進(jìn)度
 */
 public void setProgress(int progress) {
 this.progress = progress;
 invalidate();
 }

通過外部調(diào)用setProgress方法就可以跟更新當(dāng)前圓的進(jìn)度了

源碼

/**
 * 仿華為圓形加載進(jìn)度條
 * Created by zhuwentao on 2017-08-19.
 */
public class CircleLoadingView extends View {

 private Context mContext;

 // 刻度畫筆
 private Paint mScalePaint;

 // 小原點(diǎn)畫筆
 private Paint mDotPaint;

 // 文字畫筆
 private Paint mTextPaint;

 // 當(dāng)前進(jìn)度
 private int progress = 0;

 /**
 * 小圓點(diǎn)的當(dāng)前進(jìn)度
 */
 public float mDotProgress;

 // View寬
 private int mWidth;

 // View高
 private int mHeight;

 private int indexColor;

 private int baseColor;

 private int dotColor;

 private int textSize;

 private int textColor;

 private ValueAnimator animator;

 public CircleLoadingView(Context context) {
 this(context, null);
 }

 public CircleLoadingView(Context context, AttributeSet attrs) {
 this(context, attrs, 0);
 }

 public CircleLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 // 獲取用戶配置屬性
 TypedArray tya = context.obtainStyledAttributes(attrs, R.styleable.CircleLoading);
 baseColor = tya.getColor(R.styleable.CircleLoading_baseColor, Color.LTGRAY);
 indexColor = tya.getColor(R.styleable.CircleLoading_indexColor, Color.BLUE);
 textColor = tya.getColor(R.styleable.CircleLoading_textColor, Color.BLUE);
 dotColor = tya.getColor(R.styleable.CircleLoading_dotColor, Color.RED);
 textSize = tya.getDimensionPixelSize(R.styleable.CircleLoading_textSize, 36);
 tya.recycle();

 initUI();
 }

 private void initUI() {
 mContext = getContext();

 // 刻度畫筆
 mScalePaint = new Paint();
 mScalePaint.setAntiAlias(true);
 mScalePaint.setStrokeWidth(DensityUtil.dip2px(mContext, 1));
 mScalePaint.setStrokeCap(Paint.Cap.ROUND);
 mScalePaint.setColor(baseColor);
 mScalePaint.setStyle(Paint.Style.STROKE);

 // 小圓點(diǎn)畫筆
 mDotPaint = new Paint();
 mDotPaint.setAntiAlias(true);
 mDotPaint.setColor(dotColor);
 mDotPaint.setStrokeWidth(DensityUtil.dip2px(mContext, 1));
 mDotPaint.setStyle(Paint.Style.FILL);

 // 文字畫筆
 mTextPaint = new Paint();
 mTextPaint.setAntiAlias(true);
 mTextPaint.setColor(textColor);
 mTextPaint.setTextSize(textSize);
 mTextPaint.setStrokeWidth(DensityUtil.dip2px(mContext, 1));
 mTextPaint.setStyle(Paint.Style.FILL);
 }

 @Override
 protected void onDraw(Canvas canvas) {
 drawArcScale(canvas);
 drawTextValue(canvas);
 drawRotateDot(canvas);
 }

 /**
 * 畫刻度
 */
 private void drawArcScale(Canvas canvas) {
 canvas.save();

 for (int i = 0; i < 100; i++) {
  if (progress > i) {
  mScalePaint.setColor(indexColor);
  } else {
  mScalePaint.setColor(baseColor);
  }
  canvas.drawLine(mWidth / 2, 0, mHeight / 2, DensityUtil.dip2px(mContext, 10), mScalePaint);
  // 旋轉(zhuǎn)的度數(shù) = 100 / 360
  canvas.rotate(3.6f, mWidth / 2, mHeight / 2);
 }

 canvas.restore();
 }

 /**
 * 畫內(nèi)部數(shù)值
 */
 private void drawTextValue(Canvas canvas) {
 canvas.save();

 String showValue = String.valueOf(progress);
 Rect textBound = new Rect();
 mTextPaint.getTextBounds(showValue, 0, showValue.length(), textBound); // 獲取文字的矩形范圍
 float textWidth = textBound.right - textBound.left; // 獲得文字寬
 float textHeight = textBound.bottom - textBound.top; // 獲得文字高
 canvas.drawText(showValue, mWidth / 2 - textWidth / 2, mHeight / 2 + textHeight / 2, mTextPaint);

 canvas.restore();
 }

 /**
 * 畫旋轉(zhuǎn)小圓點(diǎn)
 */
 private void drawRotateDot(final Canvas canvas) {
 canvas.save();

 canvas.rotate(mDotProgress * 3.6f, mWidth / 2, mHeight / 2);
 canvas.drawCircle(mWidth / 2, DensityUtil.dip2px(mContext, 10) + DensityUtil.dip2px(mContext, 5), DensityUtil.dip2px(mContext, 3), mDotPaint);

 canvas.restore();
 }

 /**
 * 啟動小圓點(diǎn)旋轉(zhuǎn)動畫
 */
 public void startDotAnimator() {
 animator = ValueAnimator.ofFloat(0, 100);
 animator.setDuration(1500);
 animator.setRepeatCount(ValueAnimator.INFINITE);
 animator.setRepeatMode(ValueAnimator.RESTART);
 animator.setInterpolator(new AccelerateDecelerateInterpolator());
 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  @Override
  public void onAnimationUpdate(ValueAnimator animation) {
  // 設(shè)置小圓點(diǎn)的進(jìn)度,并通知界面重繪
  mDotProgress = (Float) animation.getAnimatedValue();
  invalidate();
  }
 });
 animator.start();
 }

 /**
 * 設(shè)置進(jìn)度
 */
 public void setProgress(int progress) {
 this.progress = progress;
 invalidate();
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);

 int myWidthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
 int myWidthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
 int myHeightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
 int myHeightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

 // 獲取寬
 if (myWidthSpecMode == MeasureSpec.EXACTLY) {
  // match_parent/精確值
  mWidth = myWidthSpecSize;
 } else {
  // wrap_content
  mWidth = DensityUtil.dip2px(mContext, 120);
 }

 // 獲取高
 if (myHeightSpecMode == MeasureSpec.EXACTLY) {
  // match_parent/精確值
  mHeight = myHeightSpecSize;
 } else {
  // wrap_content
  mHeight = DensityUtil.dip2px(mContext, 120);
 }

 // 設(shè)置該view的寬高
 setMeasuredDimension(mWidth, mHeight);
 }
}

在的onDraw方法中需要避免頻繁的new對象,所以把一些如初始化畫筆Paint的方法放到了最前面的構(gòu)造方法中進(jìn)行。

在分多個模塊繪制時,應(yīng)該使用canvas.save()和canvas.restore()的組合,來避免不同模塊繪制時的相互干擾,在這兩個方法中繪制相當(dāng)于PS中的圖層概念,上一個圖層進(jìn)行的修改不會影響到下一個圖層的顯示效果。

在需要顯示動畫效果的地方使用屬性動畫來處理,可自定義的效果強(qiáng),在系統(tǒng)提供的插值器類不夠用的情況下,我么還可通過繼承Animation類,重寫它的applyTransformation方法來處理各種復(fù)雜的動畫效果。

關(guān)于Android自定義View實現(xiàn)圓形加載進(jìn)度條效果的方法就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

分享文章:Android自定義View實現(xiàn)圓形加載進(jìn)度條效果的方法
鏈接URL:http://muchs.cn/article20/pdgcjo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制開發(fā)、電子商務(wù)網(wǎng)站導(dǎo)航、網(wǎng)站營銷、軟件開發(fā)動態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都app開發(fā)公司