国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 系統(tǒng) > Android > 正文

Android自定義View仿華為圓形加載進(jìn)度條

2019-10-22 18:29:19
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

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

Android,View,進(jìn)度條

實(shí)現(xiàn)思路

可以看出該View可分為三個(gè)部分來(lái)實(shí)現(xiàn)

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

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

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

具體實(shí)現(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>

各個(gè)屬性的作用:

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

(2)新建CircleLoadingView類繼承View類,重寫(xiě)它的三個(gè)構(gòu)造方法,獲取用戶設(shè)置的屬性,同時(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繪制的第一步開(kāi)始

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

淺談Android View繪制三大流程探索及常見(jiàn)問(wèn)題

重寫(xiě)onMeasure方法,其中我們要考慮當(dāng)View的寬高被指定為wrap_content時(shí)的情況,如果我們不對(duì)wrap_content的情況進(jìn)行處理,那么當(dāng)使用者指定View的寬高為wrap_content時(shí)將無(wú)法正常顯示出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來(lái)使用,以便適配不同的屏幕顯示效果

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

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

因?yàn)樽龅氖菃渭兊腣iew而不是ViewGroup,內(nèi)部沒(méi)有子控件需要確定位置,所以可直接跳過(guò)onLayout方法,直接開(kāi)始對(duì)View進(jìn)行繪制
分為三個(gè)部分繪制,繪制刻度圓,繪制文字值,繪制旋轉(zhuǎn)小圓點(diǎn)

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

繪制刻度圓

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

 /**  * 畫(huà)刻度  */ 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)是以文字的左下角開(kāi)始繪制的,所以需要先通過(guò)把文字裝載到一個(gè)矩形Rect,通過(guò)畫(huà)筆的getTextBounds方法取得字符串的長(zhǎng)度和寬度,通過(guò)動(dòng)態(tài)計(jì)算,來(lái)使文字居中顯示

 /**  * 畫(huà)內(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)

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

 /**  * 畫(huà)旋轉(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(); }

讓它自己動(dòng)起來(lái)可以通過(guò)兩種方式,一種是開(kāi)一個(gè)線程,在線程中改變mDotProgress的數(shù)值,并通過(guò)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();

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

 /**  * 啟動(dòng)小圓點(diǎn)旋轉(zhuǎn)動(dòng)畫(huà)  */ 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(); }

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

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

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

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

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

源碼

/** * 仿華為圓形加載進(jìn)度條 * Created by zhuwentao on 2017-08-19. */public class CircleLoadingView extends View { private Context mContext; // 刻度畫(huà)筆 private Paint mScalePaint; // 小原點(diǎn)畫(huà)筆 private Paint mDotPaint; // 文字畫(huà)筆 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();  // 刻度畫(huà)筆  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)畫(huà)筆  mDotPaint = new Paint();  mDotPaint.setAntiAlias(true);  mDotPaint.setColor(dotColor);  mDotPaint.setStrokeWidth(DensityUtil.dip2px(mContext, 1));  mDotPaint.setStyle(Paint.Style.FILL);  // 文字畫(huà)筆  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); } /**  * 畫(huà)刻度  */ 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(); } /**  * 畫(huà)內(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(); } /**  * 畫(huà)旋轉(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(); } /**  * 啟動(dòng)小圓點(diǎn)旋轉(zhuǎn)動(dòng)畫(huà)  */ 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); }}

總結(jié)

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

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

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

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持VEVB武林網(wǎng)。


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到Android開(kāi)發(fā)頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 广丰县| 张家川| 连江县| 顺义区| 塔河县| 札达县| 礼泉县| 阜阳市| 安图县| 西宁市| 翁源县| 永安市| 兴和县| 微博| 鄯善县| 天门市| 准格尔旗| 凉山| 清丰县| 阿克| 长乐市| 永修县| 西宁市| 滁州市| 江安县| 军事| 五寨县| 漳浦县| 湖口县| 浠水县| 合肥市| 南平市| 昌平区| 平凉市| 利辛县| 南华县| 西丰县| 含山县| 玉环县| 广饶县| 怀化市|