Android自訂控制項:進度條的四種實現方式

來源:互聯網
上載者:User

Android自訂控制項:進度條的四種實現方式

最近一直在學習自訂控制項,搜了許多大牛們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】

【Android視圖繪製流程完全解析,帶你一步步深入瞭解View】

【Android檢視狀態及重繪流程分析,帶你一步步深入瞭解View】

【Android自訂View的實現方法,帶你一步步深入瞭解View】

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

【RotateAnimation詳解——http://blog.csdn.net/u012403246/article/details/41415799】

三、Android上的實現方式:

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

 

1、:

 

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

 

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

2、:

 

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

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

XML:

    android:pivotY="50%" android:fromDegrees="0" android:toDegrees="360" android:interpolator="@android:anim/accelerate_decelerate_interpolator"> [html]  3、:

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

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

代碼:


  1. publicclassMyProgressBarextendsFrameLayout{ privatebooleanrunning; privateintprogress=0; privatestaticfinalintMAX_PROGRESS=10000;   privateClipDrawableclip;   privateHandlerhandler=newHandler(){ @Override publicvoidhandleMessage(android.os.Messagemsg){ if(msg.what==0x123) clip.setLevel(progress); } };   publicMyProgressBar(Contextcontext){ this(context,null,0); }   publicMyProgressBar(Contextcontext,AttributeSetattrs){ this(context,null,0); }   publicMyProgressBar(Contextcontext,AttributeSetattrs,intdefStyle){ super(context,attrs,defStyle); Init(context); }   publicvoidInit(Contextcontext){ Viewview=LayoutInflater.from(context).inflate(R.layout.view,null);   ImageViewiv=(ImageView)view.findViewById(R.id.progress_img);   addView(view); clip=(ClipDrawable)iv.getDrawable();   Threadthread=newThread(newRunnable(){   @Override publicvoidrun(){ running=true; while(running){ handler.sendEmptyMessage(0x123); if(progress==MAX_PROGRESS) progress=0; progress+=100; try{ Thread.sleep(18); }catch(InterruptedExceptione){ e.printStackTrace(); } } } }); thread.start(); }   publicvoidstop(){ progress=0; running=false; } }
    通過代碼我們可以看到,邏輯非常簡單,關鍵就在於ClipDrawable的setLevel()方法,這個是設定剪裁效果的。

     

    4、:

    實現一個View的子類——Progress Wheel類,實現進度條效果。具體的內容我都寫在了注釋上,如果不瞭解自訂控制項的知識,可以去閱讀guolin部落格裡自訂View四部曲的講解,講的挺好的。

    代碼:


    1. publicclassProgressWheelextendsView{   //繪製View用到的各種長、寬頻大小 privateintlayout_height=0; privateintlayout_width=0; privateintfullRadius=100; privateintcircleRadius=80; privateintbarLength=60; privateintbarWidth=20; privateintrimWidth=20; privateinttextSize=20; privatefloatcontourSize=0;   //與頁邊的間距 privateintpaddingTop=5; privateintpaddingBottom=5; privateintpaddingLeft=5; privateintpaddingRight=5;   //View要繪製的顏色 privateintbarColor=0xAA000000; privateintcontourColor=0xAA000000; privateintcircleColor=0x00000000; privateintrimColor=0xAADDDDDD; privateinttextColor=0xFF000000;   //繪製要用的畫筆 privatePaintbarPaint=newPaint(); privatePaintcirclePaint=newPaint(); privatePaintrimPaint=newPaint(); privatePainttextPaint=newPaint(); privatePaintcontourPaint=newPaint();   //繪製要用的矩形 @SuppressWarnings("unused") privateRectFrectBounds=newRectF(); privateRectFcircleBounds=newRectF(); privateRectFcircleOuterContour=newRectF(); privateRectFcircleInnerContour=newRectF();   //動畫 //每次繪製要移動的像素數目 privateintspinSpeed=2; //繪製過程的時間間隔 privateintdelayMillis=0; intprogress=0; booleanisSpinning=false;   //其他 privateStringtext=""; privateString[]splitText={};   /** *ProgressWheel的構造方法 * *@paramcontext *@paramattrs */ publicProgressWheel(Contextcontext,AttributeSetattrs){ 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 protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){ //首先我們要調用超類的onMeasure借口 //原因是我們自己去實現一個方法獲得長度、寬度太麻煩了 //使用超類的的方法非常方便而且讓複雜的細節可控 super.onMeasure(widthMeasureSpec,heightMeasureSpec);   //在這裡我們不能使用getWidth()和getHeight()。 //因為這兩個方法只能在View的布局完成後才能使用,而一個View的繪製過程是先繪製元素,再繪製Layout //所以我們必須使用getMeasuredWidth()和getMeasuredHeight() intsize=0; intwidth=getMeasuredWidth(); intheight=getMeasuredHeight(); intwidthWithoutPadding=width-getPaddingLeft()-getPaddingRight(); intheigthWithoutPadding=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 protectedvoidonSizeChanged(intw,inth,intoldw,intoldh){ super.onSizeChanged(w,h,oldw,oldh);   //Sharethedimensions layout_width=w; layout_height=h;   setupBounds(); setupPaints(); invalidate(); }   /** *設定我們想要繪製的progresswheel的顏色 */ privatevoidsetupPaints(){ 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); }   /** *設定元素的邊界 */ privatevoidsetupBounds(){ //為了保持寬度和長度的一致,我們要獲得layout_width和layout_height中較小的一個,從而繪製一個圓 intminValue=Math.min(layout_width,layout_height);   //計算在繪製過程中在x,y方向的位移量 intxOffset=layout_width-minValue; intyOffset=layout_height-minValue;   //間距加上位移量 paddingTop=this.getPaddingTop()+(yOffset/2); paddingBottom=this.getPaddingBottom()+(yOffset/2); paddingLeft=this.getPaddingLeft()+(xOffset/2); paddingRight=this.getPaddingRight()+(xOffset/2);   intwidth=getWidth();//this.getLayoutParams().width; intheight=getHeight();//this.getLayoutParams().height;   rectBounds=newRectF(paddingLeft, paddingTop, width-paddingRight, height-paddingBottom);   circleBounds=newRectF(paddingLeft+barWidth, paddingTop+barWidth, width-paddingRight-barWidth, height-paddingBottom-barWidth); circleInnerContour=newRectF(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=newRectF(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中解析控制項的屬性 * *@paramatheattributestoparse */ privatevoidparseAttributes(TypedArraya){ 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(); }   //---------------------------------- //動畫 //----------------------------------   protectedvoidonDraw(Canvascanvas){ 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); } //繪製我們想要設定的文字(並讓它顯示在圓水平和垂直方向的中心處) floattextHeight=textPaint.descent()-textPaint.ascent(); floatverticalTextOffset=(textHeight/2)-textPaint.descent();   for(Strings:splitText){ floathorizontalTextOffset=textPaint.measureText(s)/2; canvas.drawText(s,this.getWidth()/2-horizontalTextOffset, this.getHeight()/2+verticalTextOffset,textPaint); } if(isSpinning){ scheduleRedraw(); } }   privatevoidscheduleRedraw(){ progress+=spinSpeed; if(progress>360){ progress=0; } postInvalidateDelayed(delayMillis); }   /** *判斷wheel是否在旋轉 */   publicbooleanisSpinning(){ if(isSpinning){ returntrue; }else{ returnfalse; } }   /** *重設進度條的值 */ publicvoidresetCount(){ progress=0; setText("0%"); invalidate(); }   /** *停止進度條的旋轉 */ publicvoidstopSpinning(){ isSpinning=false; progress=0; postInvalidate(); }     /** *讓進度條開啟旋轉模式 */ publicvoidspin(){ isSpinning=true; postInvalidate(); }   /** *讓進度條每次增加1(最大值為360) */ publicvoidincrementProgress(){ isSpinning=false; progress++; if(progress>360) progress=0; setText(Math.round(((float)progress/360)*100)+"%"); postInvalidate(); }     /** *設定進度條為一個確切的數值 */ publicvoidsetProgress(inti){ isSpinning=false; progress=i; postInvalidate(); }   //---------------------------------- //get和set方法 //----------------------------------   /** *設定progressbar的文字並不需要重新整理View * *@paramtextthetexttoshow('\n'constitutesanewline) */ publicvoidsetText(Stringtext){ this.text=text; splitText=this.text.split("\n"); }   publicintgetCircleRadius(){ returncircleRadius; }   publicvoidsetCircleRadius(intcircleRadius){ this.circleRadius=circleRadius; }   publicintgetBarLength(){ returnbarLength; }   publicvoidsetBarLength(intbarLength){ this.barLength=barLength; }   publicintgetBarWidth(){ returnbarWidth; }   publicvoidsetBarWidth(intbarWidth){ this.barWidth=barWidth;   if(this.barPaint!=null){ this.barPaint.setStrokeWidth(this.barWidth); } }   publicintgetTextSize(){ returntextSize; }   publicvoidsetTextSize(inttextSize){ this.textSize=textSize;   if(this.textPaint!=null){ this.textPaint.setTextSize(this.textSize); } }   publicintgetPaddingTop(){ returnpaddingTop; }   publicvoidsetPaddingTop(intpaddingTop){ this.paddingTop=paddingTop; }   publicintgetPaddingBottom(){ returnpaddingBottom; }   publicvoidsetPaddingBottom(intpaddingBottom){ this.paddingBottom=paddingBottom; }   publicintgetPaddingLeft(){ returnpaddingLeft; }   publicvoidsetPaddingLeft(intpaddingLeft){ this.paddingLeft=paddingLeft; }   publicintgetPaddingRight(){ returnpaddingRight; }   publicvoidsetPaddingRight(intpaddingRight){ this.paddingRight=paddingRight; }   publicintgetBarColor(){ returnbarColor; }   publicvoidsetBarColor(intbarColor){ this.barColor=barColor;   if(this.barPaint!=null){ this.barPaint.setColor(this.barColor); } }   publicintgetCircleColor(){ returncircleColor; }   publicvoidsetCircleColor(intcircleColor){ this.circleColor=circleColor;   if(this.circlePaint!=null){ this.circlePaint.setColor(this.circleColor); } }   publicintgetRimColor(){ returnrimColor; }   publicvoidsetRimColor(intrimColor){ this.rimColor=rimColor;   if(this.rimPaint!=null){ this.rimPaint.setColor(this.rimColor); } }     publicShadergetRimShader(){ returnrimPaint.getShader(); }   publicvoidsetRimShader(Shadershader){ this.rimPaint.setShader(shader); }   publicintgetTextColor(){ returntextColor; }   publicvoidsetTextColor(inttextColor){ this.textColor=textColor;   if(this.textPaint!=null){ this.textPaint.setColor(this.textColor); } }   publicintgetSpinSpeed(){ returnspinSpeed; }   publicvoidsetSpinSpeed(intspinSpeed){ this.spinSpeed=spinSpeed; }   publicintgetRimWidth(){ returnrimWidth; }   publicvoidsetRimWidth(intrimWidth){ this.rimWidth=rimWidth;   if(this.rimPaint!=null){ this.rimPaint.setStrokeWidth(this.rimWidth); } }   publicintgetDelayMillis(){ returndelayMillis; }   publicvoidsetDelayMillis(intdelayMillis){ this.delayMillis=delayMillis; }   publicintgetContourColor(){ returncontourColor; }   publicvoidsetContourColor(intcontourColor){ this.contourColor=contourColor;   if(contourPaint!=null){ this.contourPaint.setColor(this.contourColor); } }   publicfloatgetContourSize(){ returnthis.contourSize; }   publicvoidsetContourSize(floatcontourSize){ this.contourSize=contourSize;   if(contourPaint!=null){ this.contourPaint.setStrokeWidth(this.contourSize); } } }

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.