程序媛也話Android 之 自定義控件(垂直方向滑動條)


Android里已經有足夠多的控件供開發者使用,但有時候我們還是會想要一些不一樣的東西,比如一些UI特效,比如一些3D動畫,今天就講講比較basic的東西:自定義控件。

 

1.效果圖

如果項目里需要一個通用的控件,然后UI給你這樣一個效果圖,你接下來會打算怎么做?

  用戶可以按住拖動

點擊要切換的狀態,然后自動滑動到那一端

 

(本來是沒有這個效果圖的,又不想一張張貼不同的狀態,就畫了一下這個gif圖,關於怎么在ubuntu下畫gif圖,可以看一下下面這篇)

程序媛也會畫圖 之 在ubuntu下用GIMP制作gif

 

 

2.分析

看一下有沒有現成的widget,這似乎和android.widget.Switch有點類似,可是Swithc是水平的,水平沒有關系,改成垂直的問題不大,先來嘗試下好了,就先把背景和button的圖片換一下,來看一下結果是怎樣:

額。。。這個切換似乎生硬了點,沒有漸變的動畫。好吧,那還是重新自己寫一個控件吧。

 

 

3.創建Andriod自定義控件的步驟

怎么建立一個自定義的控件,說起來並不難,有三個內容需要實現:

3.1新建一個控件類,繼承android.view.View類:

 1 public class XXXView extends View {
 2     ...
 3     protected void onDraw(Canvas canvas) {
 4         ...
 5     }
 6     
 7     public boolean onTouchEvent(MotionEvent event) {
 8         ...
 9     }
10 
11     public interface OnXXXListener { //狀態回調,同View.OnClickListener
12         public abstract void xxx();
13         public abstract void xxx();
14     }
15 }

 

3.2 在布局文件xml里使用這個控件:

<com.xxx.xxx.XXXView  android:id=”@+id/xxx”
    android:layoutWidth=”...”
    android:layoutHeight=”...”>
</com.xxx.xxx.XXXView>

 

3.3 在Activity類里獲得這個控件:

1 mXXXView = (XXXView) findViewById(R.id.xxx);
2 mXXXView.setListener(mXXXViewListener);

以上這簡單的3個步驟就是創建和使用控件的內容了,到這里,如果你是個喜歡着急寫代碼的人,你也可以先搭一個程序框架出來跑跑看啦。

 

 

 

4.考慮怎么畫?
4.1拖動
用戶需要能拖動Button,那也就是說我們在控件里需要捕獲用戶的touch event,知道用戶到底是做了什么動作(ACTION_DOWN, ACTION_MOVE, UP), 還有操作的位置在哪里(getX(), getY()).
這些信息從哪里可以知道?--》onTouchEvent()回調!

4.2動畫
動畫的本質就是圖片+位置+時間差。
在效果圖中,用戶也可以點擊一個狀態,讓控件滑動。那這個滑動的過程就是一個動畫的。
圖片我們有,那怎么把圖片畫到Canvas上?-》在onDraw()回調里面畫。在主線程里只要調用invalidate(),就會重新觸發onDraw()的執行。如果我們在一定的時間間隔,在不同的位置重新畫圖片,不就是動畫了?
位置可以從用戶行為獲得,或者自己計算;
時間差,在Android里面控制時間最容易的是什么?當然是Handler啦,因為它可以發送delay的消息。

4.3漸變的實現
效果圖中還有個漸變的過程,這個看起來好像蠻麻煩,其實也好辦。因為有Alpha的存在。我們可以在畫的時候根據不同的位置,設置Paint不同的Alpha值,一個圖片Alpha慢慢減小,另一個圖片Alpha慢慢增大。

ok,分析到這里,就大概知道該怎么做了,在onTouchEvent()回調里,獲得用戶的行為和位置,並記錄下來,在適當的時候發送Message給Handler,或者直接調用invalidate()重新畫。在Handler里,接收到信息,就根據當前的狀態,更新圖片下一個應該出現的位置,然后調用invalidate()觸發重新畫。

 

 

 


5.計算位置
ok,上面已經確定以什么方式做了,接下來就要用到一點點數學的計算了。我們要確定圖片從哪里開始動,動到哪里結束,還有在什么位置開始切換狀態。


先切下圖:

         

(文字也做成了圖片,其實凡是涉及到文字的都不應該做成圖片,如果有人切換到中文,然后他又不認識on off呢,而且這些文字應該要可設置的才對。這里圖方便就做成圖片了。)

 

然后就是一些重要坐標位置啦:

圖1    藍色是那個長條的圖片,綠色兩塊是在兩個狀態下Button所在的位置。

 

圖2   黃色的區域是兩個小的灰色文字圖片

 

圖3   這個區域就是文字開始切換的區域

 

 

 

6.偽代碼

現在方法也有了,數據也有了,就可以開始寫代碼了。
為了敘述方便,就用偽代碼代替了,下面是最重要的三個部分的偽碼:

處理用戶行為的邏輯:

 1 public boolean onTouchEvent(MotionEvent event) {  //處理用戶行為
 2     case ACTION_DOWN:
 3         if (坐標在圖1中藍色區域) { //touch在無效的區域
 4             return;
 5         }
 6 
 7         if (坐標在圖1中綠色區域中Button在的區域) { //當前狀態是on,就是上面的區域,否則,就是下面的區域
 8             獲得坐標與上邊緣的距離gap;
 9         } else {
10             設置正在滑動標志;
11             設置動畫的方向,發送Message;  //會執行到這里的情況是,比如當前狀態是on,用戶點擊了off那一端,那接下來控件就要自動滑動切換到off狀態。
12         }
13          break;
14     
15     case ACTION_MOVE:
16          if (上次Down是在無效區域 | 正在切換狀態) { //此時不用響應Move動作。
17              return;
18          }
19 
20          if (根據當前的坐標計算,滑塊將不在背景區域) {
21              return;
22          }
23 
24          if (根據當前的坐標計算,在文字交換的區域) {
25              設置交換標記;
26          }
27          記錄滑塊當前位置;
28          invalidate();
29          break;
30 
31     case ACTION_UP:
32          if (上次Down是在無效區域 | 正在切換狀態) { //此時不用響應Up動作。
33              return;
34          }
35  
36          取消交換標識;
37          if(根據當前坐標計算,最后的狀態是on) {
38             設置滑塊位置為on狀態時的位置;
39             修改狀態為on;
40             invalidate();
41          } else {
42             設置滑塊位置為off狀態時的位置;
43             修改狀態為off;
44             invalidate();
45          }
46 }

處理自動滑動:

 1 private Handler mHandler = new Handler() {  //用於處理自動滑動那部分邏輯
 2     public void handleMessage(Message msg) {
 3          if (計數 > 20) {
 4              設置當前狀態;
 5              設置滑塊的位置;
 6              取消正在滑動的標志;
 7              計數歸0;
 8              return 9          }
10 
11          根據計數,獲得interpolator.getInterpolation;//這里用了AccelerateDecelerateInterpolator,讓動畫有一個加速的效果,其實這么短的距離效果看不出來。
12          計算滑塊的位置;
13          invalidate();
14          計數+115          sendMessageDelayed(0, 20); //20ms后畫下一幀。
16     }
17 
18 };

 

畫:

 1 protected void onDraw(Canvas canvas) { //具體畫的代碼
 2      畫背景;
 3 
 4      if (在狀態交換區域) {
 5          根據滑塊位置這是Paint的Alpha值;
 6          用上面設置的Paint畫那四個小圖; //在狀態交換的時候,四個小圖都是顯示的。
 7      } else {
 8          根據當前的狀態,畫on滑塊或off滑塊;
 9      }
10 }

 

ok,有上面3部分的內容,基本上就可以了。

下面就是運行起來的效果,(不好表示啦,其實就是效果圖那樣的)

 

貼個對應的代碼段:

Handler:

 1     private Handler mHandler = new Handler() {
 2         @Override
 3         public void handleMessage(Message msg) {
 4             if (drawCount > 20) {
 5                 if (button_status == STATUS_OFF) {
 6                     button_status = STATUS_ON;
 7                     buttonY = buttonTopY;
 8                     if (listener != null) {
 9                         listener.slipToTop();
10                     }
11                 } else {
12                     button_status = STATUS_OFF;
13                     buttonY = buttonBottomY;
14                     if (listener != null) {
15                         listener.slipToBottom();
16                     }
17                 }
18 
19                 isTouchDownAnotherSide = false;
20                 drawCount = 0;
21                 return;
22             }
23 
24             float p;
25             if (isToBottom) {
26                 p = (float) (drawCount * 0.05);
27             } else {
28                p = (float) (1 - drawCount * 0.05);
29             }
30             float inter = interpolator.getInterpolation(p);
31             buttonY = buttonTopY + (buttonBottomY - buttonTopY) * inter;
32 
33             if (buttonY >= exchangeBeginY && buttonY <= exchangeEndY) {
34                 isExchange = true;
35             } else {
36                 isExchange = false;
37             }
38             invalidate();
39             drawCount++;
40             sendEmptyMessageDelayed(0, 20);
41         }
42     };
View Code

 

onDraw():

 1     @Override
 2     protected void onDraw(Canvas canvas) {
 3         // TODO Auto-generated method stub
 4         canvas.drawBitmap(mBackBitmap, 0, 0, null);
 5 
 6         if (isExchange) {
 7             // in exchange area, we should set alpha
 8             Paint mPaint = new Paint();
 9 
10             int alpha = (int) (255 - 255 * (buttonY - 25.5) / 50);
11             mPaint.setAlpha(alpha);
12             canvas.drawBitmap(mONBitmap, buttonTopX, buttonY, mPaint);
13             canvas.drawBitmap(mOFFTextBitmap, textBottomX, textBottomY, mPaint);
14 
15             mPaint.setAlpha(255 - alpha);
16             canvas.drawBitmap(mOFFBitmap, buttonBottomX, buttonY, mPaint);
17             canvas.drawBitmap(mONTextBitmap, textTopX, textTopY, mPaint);
18         } else {
19             if (getNearLocation(0, buttonY) == STATUS_ON) {
20                 canvas.drawBitmap(mONBitmap, buttonTopX, buttonY, null);
21                 canvas.drawBitmap(mOFFTextBitmap, textBottomX, textBottomY, null);
22             } else {
23                 canvas.drawBitmap(mOFFBitmap, buttonBottomX, buttonY, null);
24                 canvas.drawBitmap(mONTextBitmap, textTopX, textTopY, null);
25             }
26         }
27     }
View Code

 

onTouchEvent():

  1     @Override
  2     public boolean onTouchEvent(MotionEvent event) {
  3         // TODO Auto-generated method stub
  4 
  5         float x = event.getX();
  6         float y = event.getY();
  7         switch (event.getAction()) {
  8         case MotionEvent.ACTION_DOWN:
  9 
 10             if (isTouchDownAnotherSide) {
 11                 return true;
 12             }
 13 
 14             // check if touch right place
 15             if (isOutOfFrontBitmap(x, y)) {
 16                 isTouchDownValid = false;
 17                 return true;
 18             }
 19 
 20             if (listener != null) {
 21                 listener.touchedDown();
 22             }
 23 
 24             if (isInFrontBitmap(x, y)) {
 25                 // touch in current mode
 26                 Log.e("Slip", "ACTION_DOWN : yes! infrontBitmap");
 27                 isTouchDownValid = true;
 28                 touchDownGap = getGap(x, y);
 29             } else {
 30                 // touch anther side
 31                 Log.e("Slip", "ACTION_DOWN : no! infrontBitmap");
 32                 isTouchDownValid = false;
 33                 isTouchDownAnotherSide = true;
 34                 if (button_status == STATUS_ON) {
 35                     isToBottom = true;
 36                     mHandler.sendEmptyMessage(0);
 37                 } else {
 38                     isToBottom = false;
 39                     mHandler.sendEmptyMessage(0);
 40                 }
 41             }
 42             break;
 43         case MotionEvent.ACTION_MOVE:
 44             // if touch down wrong place, we ignore next action
 45             if (!isTouchDownValid || isTouchDownAnotherSide) {
 46                 return true;
 47             }
 48             if (!isInBackBitmap(x, y)) {
 49                 Log.e("Slip", "ACTION_MOVE : no! isInBackBitmap");
 50                 return true;
 51             }
 52             if (isInExchangeArea(x, y)) {
 53                 isExchange = true;
 54             } else {
 55                 isExchange = false;
 56             }
 57             buttonY = y - touchDownGap;
 58             this.invalidate();
 59 
 60             break;
 61         case MotionEvent.ACTION_UP:
 62             // if touch down wrong place, we ignore next action
 63             if (!isTouchDownValid || isTouchDownAnotherSide) {
 64                 Log.e("Slip", "ACTION_UP : no! isTouchDownValid");
 65                 return true;
 66             }
 67 
 68             isExchange = false;
 69 
 70             if (getFinalLocation(x, y) == STATUS_ON) {
 71                 buttonY = buttonTopY;
 72                 if (button_status != STATUS_ON) {
 73                     button_status = STATUS_ON;
 74                     Log.e("Slip", "ACTION_UP : STATUS_ON! getFinalLocation");
 75                     if (listener != null) {
 76                         listener.slipToTop();
 77                     }
 78                 } else {
 79                     if (listener != null) {
 80                         listener.touchedUp();
 81                     }
 82                 }
 83                 this.invalidate();
 84 
 85             } else {
 86                 buttonY = buttonBottomY;
 87                 if (button_status != STATUS_OFF) {
 88                     button_status = STATUS_OFF;
 89                     Log.e("Slip", "ACTION_UP : STATUS_OFF! getFinalLocation");
 90                     if (listener != null) {
 91                         listener.slipToBottom();
 92                     }
 93                 } else {
 94                     if (listener != null) {
 95                         listener.touchedUp();
 96                     }
 97                 }
 98                 this.invalidate();
 99             }
100 
101             break;
102         default:
103             break;
104         }
105 
106         return true;
107     }
View Code

 

 

Over,Thanks.

 


免責聲明!

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



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