1. 程式人生 > >Android自定義View分享——一個水平的進度條

Android自定義View分享——一個水平的進度條

寫在前面

筆者近來在學習Android自定義View,收集了一些不算複雜但又“長得”還可以的自定義View效果實現,這些View的邏輯不算複雜,大多都只用到了Paint、Canvas類的一些常用的API。在後續的部落格裡面,將分享幾個不同的效果,本文作為第一篇,先來一個很簡單的——一個水平進度條(跟系統的那個不同)。

本文適合什麼樣的人

如果你接觸自定義View不久,看懂了View繪製基本流程,知道onMeasured()、onLayout()、onDraw(),知道Paint、Canvas類及其API很強大但沒用過,正準備大顯身手來幾個自定義View卻又不知道該“畫”一個怎麼樣自定義View比較合適,苦惱於網上各種開源專案太高階,看不懂,那麼本文及後續的部落格很適合你,來讓我們一起拿起鉛筆(Paint)、白紙(Canvas)來當一回藝術家唄。

效果展現

看這就是我們要的效果,朝兩邊滾動的進度條。
TwoSideProgressBar圖片展示

設計圖

作為一個藝術家(碼農),當然要先有設計圖,才能“畫”出一個靠譜的自定義View,你要認真看我的設計圖,儘管文章最後面會給出完整程式碼地址,不過如果你認真看設計圖,完全可以自己畫哦~~

第一個情況設計圖

第一個情況設計圖
好了根據設計圖能看到:

  • 我們的View以控制元件的中間為分界線,分別向左右兩邊滾動。
  • 每個bar長度都是一樣的,除了左右兩邊的第一個。另外相鄰bar之間的間距也是一樣。
  • 我們需要關注三個引數,第一個bar的寬度,其他普通bar的寬度,兩個bar之間的間隔。

我們只要繪製一邊,另一邊是對稱的,所以繪製的邏輯是:

  1. 從中間開始,先繪製第一個較特殊的bar,然後間隔barSpace距離,繪製第二個寬度為barWidth的bar,重複,直到你的繪製位置超出控間的右邊界。
  2. 繪製完成,同樣操作對左邊也繪製一次,注意傳入API裡面的關於座標相關的引數。此時完成一次繪製,注意了只是一次,我們需要將firstBarWidth變大一些,然後呼叫invalidate()方法重繪,這樣才能顯示出動態的效果。

程式碼片段如下所示:

//第一個"bar"寬度小於普通"bar",注意我的座標系原點在控制元件中間
if(firstBarWidth<=barWidth){
    //繪製右邊第一個“條”
    //index是繪製索引
int index = 0; canvas.drawLine(index, 0, firstBarWidth, 0, barPaint); index+=firstBarWidth+barSpace; //迴圈繪製右邊其它普通的“條” while (index <= measuredWidth/2){ canvas.drawLine(index, 0, index+barWidth, 0, barPaint); index+=barWidth+barSpace; } //繪製左邊第一個“條” index = 0; canvas.drawLine(-firstBarWidth, 0, index, 0, barPaint); index-=(firstBarWidth+barSpace); //迴圈繪製左邊其它普通的“條” while (index >= -measuredWidth/2){ canvas.drawLine(index-barWidth, 0, index, 0, barPaint); index-=(barWidth+barSpace); } firstBarWidth+=barWidth/10; }

第二個情況設計圖

為什麼會有兩種情況,你是否想過,當firstBarWidth(第一個bar的寬度)達到普通的bar寬度時,該怎麼辦?直接將變數重置為0,從頭畫過?絕對不是,仔細想想你會發現,如果你這樣做的話,會讓滾動條有一個被“抽”一下的感覺,這種思考是錯誤的,那麼正確的是怎樣,來看第二個設計圖
第一個bar寬度達到普通寬度時的情況
當第一個bar達到了普通bar的寬度,我們就要執行另外一個繪製邏輯:

  1. 從繪製起點開始,先間隔一小段的距離(長度為firstBarDistance),才開始繪製第一個bar。
  2. 然後間隔barWidth距離,繪製第二個,重複,直到繪製起點超過控制元件邊界。這樣就完成了一次繪製,類似於第一個情況,為了體現動態效果,我們需要將firstBarDistance變大一些,然後呼叫invalidate()方法重繪,這樣才能顯示出動態的效果。
  3. 問題在於,firstBarDistance達到什麼程度才好,其實當他的值等於barSpace時,就是第二個情況的臨界點了,在下一刻,就回到了第一個情況的起點,此時只要重置所有計算引數就可以。

程式碼片段如下所示:

//第一個"bar"寬度達到普通的"bar"寬度時
//迴圈繪製右邊的“條”
float index = firstBarDistance;
while (index <= measuredWidth/2){
    canvas.drawLine(index, 0, index+barWidth, 0, barPaint);
    index+=barWidth+barSpace;
}
//迴圈繪製左邊的“條”
index = -firstBarDistance;
while (index >= -measuredWidth/2){
    canvas.drawLine(index-barWidth, 0, index, 0, barPaint);
    index-=(barWidth+barSpace);
}
firstBarDistance+=barWidth/10;
//到達臨界點,全部重置
if(firstBarDistance > barSpace) {
    firstBarDistance = barWidth/10;
    firstBarWidth = barWidth/10;
}

注意一下

針對我們這個View的特點,在你正式繪圖之前,我提一下:

  • 在onDraw()裡面,在所有的繪圖操作執行之前,呼叫一下這個方法:
canvas.translate(measuredWidth/2, getMeasuredHeight()/2);

讓座標系移到中間,可以簡化一些計算。

  • 提前看看canvas.drawLine()方法,在畫線時不要傳錯引數了哦。
  • invalidate()方法可以引發控間重繪,如果你想控制重繪頻率,postInvalidateDelayed();方法是個不錯的替代品。
  • 沒錯,除了一些數學計算之外,我們就僅僅用到了Paint物件基本建立,Canvas類的drawLine()方法、translate()方法,就是這麼簡單啦。

下期預告

下篇文章我將分享這樣子的效果,一個圓形的溫度顯示器
溫度顯示器