[轉]Android自定義控件:進度條的四種實現方式(Progress Wheel的解析)


最近一直在學習自定義控件,搜了許多大牛們Blog里分享的小教程,也上GitHub找了一些類似的控件進行學習。發現讀起來都不太好懂,就想寫這么一篇東西作為學習筆記吧。

一、控件介紹:

進度條在App中非常常見,例如下載進度、加載圖片、打開文章、打開網頁等等……都需要這么一個效果讓用戶知道我們的App正在讀取,以構造良好 的交互。如果沒有這樣一個效果的話,用戶沒法知道東西有沒有下載好、圖片加載了沒有、文章打開了沒……會讓用戶很不爽。基於這樣的情景我們的UI設計師們 創造了這樣一個控件。

二、這篇文章會涉及的知識點:

跟我一樣剛入門的Android菜鳥們,我推薦大家先了解一下這些知識點再往下看。這些知識點我也會推薦一些博客給大家看看,更推薦大家看文檔里的解釋,當然大牛們可以直接無視……

1、ClipDrawable類:能夠對一個drawable類進行剪切操作(即只顯示某一部分的區域,另一部分隱藏),顯示多大的區域由level控制(level取值是0~10000)

【博客:http://blog.csdn.net/lonelyroamer/article/details/8244777】、沒文檔的可以在這看【http://www.apihome.cn/api/android/ClipDrawable.html】

2、自定義View:guolin大神的深入學習View四部曲

Android LayoutInflater原理分析,帶你一步步深入了解View —— http://blog.csdn.net/guolin_blog/article/details/12921889】

Android視圖繪制流程完全解析,帶你一步步深入了解View —— http://blog.csdn.net/guolin_blog/article/details/16330267】

Android視圖狀態及重繪流程分析,帶你一步步深入了解View —— http://blog.csdn.net/guolin_blog/article/details/17045157】

Android自定義View的實現方法,帶你一步步深入了解View ——

http://blog.csdn.net/guolin_blog/article/details/17357967】

3、沒看過我寫的:Android自定義控件——老版優酷三級菜單的話,或許需要看看這個:

【RotateAnimation詳解——】

三、Android上的實現方式:

(前三種方法比較簡單,第四種方法是GitHub項目的解析,對前三種沒興趣可以直接跳到后邊……)

1、效果圖:

將進度條的變換過程分解為一幀一幀的圖片,將這些一幀一幀的圖片連起來構成一個動畫。常用於:手機閱讀網頁、逛社區時,加載圖片、文章等不需要清楚知道加載進度,但是需要知道是否進行加載的情景。

這種方法實現可以通過創建一個animation-list的XML文件,然后給系統API提供的ProgressBar的indeterminateDrawable屬性就可以了。(這個屬性應該是類似於設置一個動畫吧……)

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"  android:oneshot="false" >  <item android:duration="150" >   <clip    android:clipOrientation="horizontal"    android:drawable="@drawable/loading_01"    android:gravity="left"/>  </item>  <item android:duration="150" >   <clip    android:clipOrientation="horizontal"    android:drawable="@drawable/loading_02"    android:gravity="left"/>  </item>  <item android:duration="150" >   <clip    android:clipOrientation="horizontal"    android:drawable="@drawable/loading_03"    android:gravity="left"/>  </item>  <item android:duration="150" >   <clip    android:clipOrientation="horizontal"    android:drawable="@drawable/loading_04"    android:gravity="left"/>  </item>  <item android:duration="150" >   <clip    android:clipOrientation="horizontal"    android:drawable="@drawable/loading_05"    android:gravity="left"/>  </item>  <item android:duration="150" >   <clip    android:clipOrientation="horizontal"    android:drawable="@drawable/loading_06"    android:gravity="left"/>  </item>  <item android:duration="150" >   <clip    android:clipOrientation="horizontal"    android:drawable="@drawable/loading_07"    android:gravity="left"/>  </item>  <item android:duration="150" > <clip android:clipOrientation="horizontal" android:drawable="@drawable/loading_08" android:gravity="left"/> </item> <item android:duration="150" > <clip android:clipOrientation="horizontal" android:drawable="@drawable/loading_09" android:gravity="left"/> </item> <item android:duration="150" > <clip android:clipOrientation="horizontal" android:drawable="@drawable/loading_10" android:gravity="left"/> </item> <item android:duration="150" > <clip android:clipOrientation="horizontal" android:drawable="@drawable/loading_11" android:gravity="left"/> </item> <item android:duration="150" > <clip android:clipOrientation="horizontal" android:drawable="@drawable/loading_12" android:gravity="left"/> </item> </animation-list>
<ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:indeterminateDrawable="@drawable/progressbar1" />

2 、效果圖:

在上一篇有關自定義控件的博客里我們使用了一個RotateAnimation類來實現旋轉效果 (http://blog.csdn.net/u012403246/article/details/41309161),其實,我們在這里也可以把一 張圖片,通過旋轉,達到我們要的效果。本質上和上一種方法沒多大區別。

我們只需要創建一個rotate的XML,對其屬性進行一些簡單的設置,然后加入我們要用的圖片就可以了。

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"  android:pivotX="50%"  android:pivotY="50%"  android:fromDegrees="0"  android:toDegrees="360"  android:interpolator="@android:anim/accelerate_decelerate_interpolator" >  <bitmap   android:antialias="true"   android:filter="true"   android:src="@drawable/loading_360"/> </rotate>
<ProgressBar  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:indeterminateDrawable="@drawable/progressbar2"/>

3、效果圖:

我們可以弄兩張照片,第一張是純黑色的,然后把這張照片中心挖一個圓出來,圓區域弄成白色,挖出來的圓弄成第二張照片。我們不妨疊加顯示兩張照片,剛開始把第二張完全“遮住”,隨着加載進度的增加,我們減少遮住的區域把第二張照片慢慢的顯示出來。

Android 上剛好就有這么一個ClipDrawable類,能夠實現剪裁的過程。我們來看看怎么通過這樣的方式自定義一個進度條控件。

public class MyProgressBar extends FrameLayout{  private boolean running;  private int progress = 0;  private static final int MAX_PROGRESS = 10000;   private ClipDrawable clip;   private Handler handler = new Handler(){   @Override   public void handleMessage(android.os.Message msg) {    if(msg.what == 0x123)     clip.setLevel(progress);   }  };   public MyProgressBar(Context context){   this(context,null,0);  }  public MyProgressBar(Context context,AttributeSet attrs){   this(context,null,0);  }   public MyProgressBar(Context context, AttributeSet attrs, int defStyle) {   super(context, attrs, defStyle);   Init(context);  }   public void Init(Context context){   View view = LayoutInflater.from(context).inflate(R.layout.view, null);     ImageView iv = (ImageView)view.findViewById(R.id.progress_img);     addView(view);   clip = (ClipDrawable)iv.getDrawable();     Thread thread = new Thread(new Runnable() {       @Override    public void run() {     running = true;     while(running){      handler.sendEmptyMessage(0x123);      if(progress == MAX_PROGRESS)       progress = 0;      progress += 100;      try {       Thread.sleep(18);      } catch (InterruptedException e) {       e.printStackTrace();      }     }    }   });   thread.start();  }  public void stop(){   progress = 0;   running = false;  } }

通過代碼我們可以看到,邏輯非常簡單,關鍵就在於ClipDrawable的setLevel()方法,這個是設置剪裁效果的。

4、效果圖:

實現一個View的子類——Progress Wheel類,實現進度條效果。具體的內容我都寫在了注釋上,如果不了解自定義控件的知識,可以去閱讀guolin博客里自定義View四部曲的講解,講的挺好的。

代碼 :

public class ProgressWheel extends View {  //繪制View用到的各種長、寬帶大小  private int layout_height = 0;  private int layout_width = 0;  private int fullRadius = 100;  private int circleRadius = 80;  private int barLength = 60;  private int barWidth = 20;  private int rimWidth = 20;  private int textSize = 20;  private float contourSize = 0;  //與頁邊的間距  private int paddingTop = 5;  private int paddingBottom = 5;  private int paddingLeft = 5;  private int paddingRight = 5;  //View要繪制的顏色  private int barColor = 0xAA000000;  private int contourColor = 0xAA000000;  private int circleColor = 0x00000000;  private int rimColor = 0xAADDDDDD;  private int textColor = 0xFF000000;  //繪制要用的畫筆  private Paint barPaint = new Paint();  private Paint circlePaint = new Paint();  private Paint rimPaint = new Paint();  private Paint textPaint = new Paint();  private Paint contourPaint = new Paint();  //繪制要用的矩形  @SuppressWarnings("unused")  private RectF rectBounds = new RectF();  private RectF circleBounds = new RectF();  private RectF circleOuterContour = new RectF();  private RectF circleInnerContour = new RectF();  //動畫  //每次繪制要移動的像素數目  private int spinSpeed = 2;  //繪制過程的時間間隔  private int delayMillis = 0;  int progress = 0;  boolean isSpinning = false;  //其他  private String text = "";  private String[] splitText = {};  /**  * ProgressWheel的構造方法  *  * @param context  * @param attrs  */  public ProgressWheel(Context context, AttributeSet attrs) {   super(context, attrs);   parseAttributes(context.obtainStyledAttributes(attrs,     R.styleable.ProgressWheel));  }  //----------------------------------  //初始化一些元素  //----------------------------------  /*  * 調用這個方法時,使View繪制為方形  * From: http://www.jayway.com/2012/12/12/creating-custom-android-views-part-4-measuring-and-how-to-force-a-view-to-be-square/  *  */  @Override  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   // 首先我們要調用超類的onMeasure借口   // 原因是我們自己去實現一個方法獲得長度、寬度太麻煩了   // 使用超類的的方法非常方便而且讓復雜的細節可控   super.onMeasure(widthMeasureSpec, heightMeasureSpec);   // 在這里我們不能使用getWidth()和getHeight()。 // 因為這兩個方法只能在View的布局完成后才能使用,而一個View的繪制過程是先繪制元素,再繪制Layout // 所以我們必須使用getMeasuredWidth()和getMeasuredHeight() int size = 0; int width = getMeasuredWidth(); int height = getMeasuredHeight(); int widthWithoutPadding = width - getPaddingLeft() - getPaddingRight(); int heigthWithoutPadding = height - getPaddingTop() - getPaddingBottom(); // 最后我們用一些簡單的邏輯去計算View的大小並調用setMeasuredDimension()去設置View的大小 // 在比較View的長寬前我們不考慮間距,但當我們設置View所需要繪制的面積時,我們要考慮它 // 不考慮間距的View(View內的實際畫面)此時就應該是方形的,但是由於間距的存在,最終View所占的面積可能不是方形的 if (widthWithoutPadding > heigthWithoutPadding) { size = heigthWithoutPadding; } else { size = widthWithoutPadding; } // 如果你重寫了onMeasure()方法,你必須調用setMeasuredDimension()方法 // 這是你設置View大小的唯一途徑 // 如果你不調用setMeasuredDimension()方法,父控件會拋出異常,並且程序會崩潰 // 如果我們使用了超類的onMeasure()方法,我們就不是那么需要setMeasuredDimension()方法 // 然而,重寫onMeasure()方法是為了改變既有的繪制流程,所以我們必須調用setMeasuredDimension()方法以達到我們的目的 setMeasuredDimension(size + getPaddingLeft() + getPaddingRight(), size + getPaddingTop() + getPaddingBottom()); } /** * 使用onSizeChanged方法代替onAttachedToWindow獲得View的面積 * 因為這個方法會在測量了MATCH_PARENT和WRAP_CONTENT后馬上被調用 * 使用獲得的面積設置View */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // Share the dimensions layout_width = w; layout_height = h; setupBounds(); setupPaints(); invalidate(); } /** * 設置我們想要繪制的progress wheel的顏色 */ private void setupPaints() { barPaint.setColor(barColor); barPaint.setAntiAlias(true); barPaint.setStyle(Style.STROKE); barPaint.setStrokeWidth(barWidth); rimPaint.setColor(rimColor); rimPaint.setAntiAlias(true); rimPaint.setStyle(Style.STROKE); rimPaint.setStrokeWidth(rimWidth); circlePaint.setColor(circleColor); circlePaint.setAntiAlias(true); circlePaint.setStyle(Style.FILL); textPaint.setColor(textColor); textPaint.setStyle(Style.FILL); textPaint.setAntiAlias(true); textPaint.setTextSize(textSize); contourPaint.setColor(contourColor); contourPaint.setAntiAlias(true); contourPaint.setStyle(Style.STROKE); contourPaint.setStrokeWidth(contourSize); } /** * 設置元素的邊界 */ private void setupBounds() { // 為了保持寬度和長度的一致,我們要獲得layout_width和layout_height中較小的一個,從而繪制一個圓 int minValue = Math.min(layout_width, layout_height); // 計算在繪制過程中在x,y方向的偏移量 int xOffset = layout_width - minValue; int yOffset = layout_height - minValue; // 間距加上偏移量 paddingTop = this.getPaddingTop() + (yOffset / 2); paddingBottom = this.getPaddingBottom() + (yOffset / 2); paddingLeft = this.getPaddingLeft() + (xOffset / 2); paddingRight = this.getPaddingRight() + (xOffset / 2); int width = getWidth(); //this.getLayoutParams().width; int height = getHeight(); //this.getLayoutParams().height; rectBounds = new RectF(paddingLeft, paddingTop, width - paddingRight, height - paddingBottom); circleBounds = new RectF(paddingLeft + barWidth, paddingTop + barWidth, width - paddingRight - barWidth, height - paddingBottom - barWidth); circleInnerContour = new RectF(circleBounds.left + (rimWidth / 2.0f) + (contourSize / 2.0f), circleBounds.top + (rimWidth / 2.0f) + (contourSize / 2.0f), circleBounds.right - (rimWidth / 2.0f) - (contourSize / 2.0f), circleBounds.bottom - (rimWidth / 2.0f) - (contourSize / 2.0f)); circleOuterContour = new RectF(circleBounds.left - (rimWidth / 2.0f) - (contourSize / 2.0f), circleBounds.top - (rimWidth / 2.0f) - (contourSize / 2.0f), circleBounds.right + (rimWidth / 2.0f) + (contourSize / 2.0f), circleBounds.bottom + (rimWidth / 2.0f) + (contourSize / 2.0f)); fullRadius = (width - paddingRight - barWidth) / 2; circleRadius = (fullRadius - barWidth) + 1; } /** * 從XML中解析控件的屬性 * * @param a the attributes to parse */ private void parseAttributes(TypedArray a) { barWidth = (int) a.getDimension(R.styleable.ProgressWheel_barWidth, barWidth); rimWidth = (int) a.getDimension(R.styleable.ProgressWheel_rimWidth, rimWidth); spinSpeed = (int) a.getDimension(R.styleable.ProgressWheel_spinSpeed, spinSpeed); delayMillis = a.getInteger(R.styleable.ProgressWheel_delayMillis, delayMillis); if (delayMillis < 0) { delayMillis = 0; } barColor = a.getColor(R.styleable.ProgressWheel_barColor, barColor); barLength = (int) a.getDimension(R.styleable.ProgressWheel_barLength, barLength); textSize = (int) a.getDimension(R.styleable.ProgressWheel_textSize, textSize); textColor = (int) a.getColor(R.styleable.ProgressWheel_textColor, textColor); //如果text是空的,就無視它 if (a.hasValue(R.styleable.ProgressWheel_text)) { setText(a.getString(R.styleable.ProgressWheel_text)); } rimColor = (int) a.getColor(R.styleable.ProgressWheel_rimColor, rimColor); circleColor = (int) a.getColor(R.styleable.ProgressWheel_circleColor, circleColor); contourColor = a.getColor(R.styleable.ProgressWheel_contourColor, contourColor); contourSize = a.getDimension(R.styleable.ProgressWheel_contourSize, contourSize); // 使用TypedArray獲得控件屬性時必須要注意:使用結束后必須回收TypedArray的對象 a.recycle(); } //---------------------------------- //動畫 //---------------------------------- protected void onDraw(Canvas canvas) { super.onDraw(canvas); //繪制內圓 canvas.drawArc(circleBounds, 360, 360, false, circlePaint); //繪制邊界 canvas.drawArc(circleBounds, 360, 360, false, rimPaint); canvas.drawArc(circleOuterContour, 360, 360, false, contourPaint); canvas.drawArc(circleInnerContour, 360, 360, false, contourPaint); //繪制條紋 if (isSpinning) { canvas.drawArc(circleBounds, progress - 90, barLength, false, barPaint); } else { canvas.drawArc(circleBounds, -90, progress, false, barPaint); } //繪制我們想要設置的文字 (並讓它顯示在圓水平和垂直方向的中心處) float textHeight = textPaint.descent() - textPaint.ascent(); float verticalTextOffset = (textHeight / 2) - textPaint.descent(); for (String s : splitText) { float horizontalTextOffset = textPaint.measureText(s) / 2; canvas.drawText(s, this.getWidth() / 2 - horizontalTextOffset, this.getHeight() / 2 + verticalTextOffset, textPaint); } if (isSpinning) { scheduleRedraw(); } } private void scheduleRedraw() { progress += spinSpeed; if (progress > 360) { progress = 0; } postInvalidateDelayed(delayMillis); } /** * 判斷wheel是否在旋轉 */ public boolean isSpinning() { if(isSpinning){ return true; } else { return false; } } /** * 重設進度條的值 */ public void resetCount() { progress = 0; setText("0%"); invalidate(); } /** * 停止進度條的旋轉 */ public void stopSpinning() { isSpinning = false; progress = 0; postInvalidate(); } /** * 讓進度條開啟旋轉模式 */ public void spin() { isSpinning = true; postInvalidate(); } /** * 讓進度條每次增加1(最大值為360) */ public void incrementProgress() { isSpinning = false; progress++; if (progress > 360) progress = 0; setText(Math.round(((float) progress / 360) * 100) + "%"); postInvalidate(); } /** * 設置進度條為一個確切的數值 */ public void setProgress(int i) { isSpinning = false; progress = i; postInvalidate(); } //---------------------------------- //get和set方法 //---------------------------------- /** * 設置progress bar的文字並不需要刷新View * * @param text the text to show ('\n' constitutes a new line) */ public void setText(String text) { this.text = text; splitText = this.text.split("\n"); } public int getCircleRadius() { return circleRadius; } public void setCircleRadius(int circleRadius) { this.circleRadius = circleRadius; } public int getBarLength() { return barLength; } public void setBarLength(int barLength) { this.barLength = barLength; } public int getBarWidth() { return barWidth; } public void setBarWidth(int barWidth) { this.barWidth = barWidth; if ( this.barPaint != null ) { this.barPaint.setStrokeWidth( this.barWidth ); } } public int getTextSize() { return textSize; } public void setTextSize(int textSize) { this.textSize = textSize; if ( this.textPaint != null ) { this.textPaint.setTextSize( this.textSize ); } } public int getPaddingTop() { return paddingTop; } public void setPaddingTop(int paddingTop) { this.paddingTop = paddingTop; } public int getPaddingBottom() { return paddingBottom; } public void setPaddingBottom(int paddingBottom) { this.paddingBottom = paddingBottom; } public int getPaddingLeft() { return paddingLeft; } public void setPaddingLeft(int paddingLeft) { this.paddingLeft = paddingLeft; } public int getPaddingRight() { return paddingRight; } public void setPaddingRight(int paddingRight) { this.paddingRight = paddingRight; } public int getBarColor() { return barColor; } public void setBarColor(int barColor) { this.barColor = barColor; if ( this.barPaint != null ) { this.barPaint.setColor( this.barColor ); } } public int getCircleColor() { return circleColor; } public void setCircleColor(int circleColor) { this.circleColor = circleColor; if ( this.circlePaint != null ) { this.circlePaint.setColor( this.circleColor); } } public int getRimColor() { return rimColor; } public void setRimColor(int rimColor) { this.rimColor = rimColor; if ( this.rimPaint != null ) { this.rimPaint.setColor( this.rimColor ); } } public Shader getRimShader() { return rimPaint.getShader(); } public void setRimShader(Shader shader) { this.rimPaint.setShader(shader); } public int getTextColor() { return textColor; } public void setTextColor(int textColor) { this.textColor = textColor; if ( this.textPaint != null ) { this.textPaint.setColor( this.textColor ); } } public int getSpinSpeed() { return spinSpeed; } public void setSpinSpeed(int spinSpeed) { this.spinSpeed = spinSpeed; } public int getRimWidth() { return rimWidth; } public void setRimWidth(int rimWidth) { this.rimWidth = rimWidth; if ( this.rimPaint != null ) { this.rimPaint.setStrokeWidth( this.rimWidth ); } } public int getDelayMillis() { return delayMillis; } public void setDelayMillis(int delayMillis) { this.delayMillis = delayMillis; } public int getContourColor() { return contourColor; } public void setContourColor(int contourColor) { this.contourColor = contourColor; if ( contourPaint != null ) { this.contourPaint.setColor( this.contourColor ); } } public float getContourSize() { return this.contourSize; } public void setContourSize(float contourSize) { this.contourSize = contourSize; if ( contourPaint != null ) { this.contourPaint.setStrokeWidth( this.contourSize ); } } }


---------------------
作者:oooo呼呼
來源:CNBLOGS
原文:https://www.cnblogs.com/wangying222/p/5304990.html
版權聲明:本文為作者原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM