弄過android開發的都知道,系統有一個默認的ToggleButton,但很多人都覺得他很難看,當然也包括我。如果你感覺他不難看,那你就繼續使用系統的吧,這篇文章對你來說是多余的了。
今天來寫一個模仿微信的ToggleButton控件,是啊,模仿都是模仿"大家之作",騰訊、360等等,也確實,他們設計出來的東西確實好看。
下面看效果圖打開狀態,關閉狀態
先奉獻上三張圖片的素材,打開背景圖,關閉背景圖
,滑塊背景圖
。這里背景圖高度是小於前兩個背景圖2個像素的。這樣才能出現最終的打開和關閉后的滑塊上下出現1個像素背景的效果。
開始我們的控件代碼,這里我們自定義一個ToggleButton控件,當然要繼承自View,下面貼出我的代碼

1 public class ToggleButton extends View { 2 3 private Bitmap onBackgroundImage; 4 private Bitmap offBackgroundImage; 5 private Bitmap blockImage; 6 private ToggleState state = ToggleState.Open; 7 private int currentX; 8 private int mouseDownX = -1; 9 private boolean isMove = false; 10 private ToggleState preState; 11 private boolean isInvalidate = true; 12 public ToggleButton(Context context) { 13 super(context); 14 // TODO Auto-generated constructor stub 15 } 16 17 18 private OnClickListener clickListener = null; 19 20 /* 21 * 其實應該是stateChange事件,懶得改了 22 */ 23 public void SetOnClickListener(OnClickListener listener) { 24 if (listener != null) 25 clickListener = listener; 26 } 27 28 @Override 29 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 30 // TODO Auto-generated method stub 31 super.onLayout(changed, left, top, right, bottom); 32 //layout方法是在ondraw方法之前執行, 33 //在這個做判斷是防止用戶在設置Image之前setState, 34 //這樣Image為null,系統無法獲取block寬度,也就無法設置currentX坐標, 35 //系統將采用默認Open狀態,會出現即使用戶設置狀態為close,而界面顯示仍未open狀態, 36 //在這里判斷,是因為當前image和state肯定已經設置完畢了 37 if (state == ToggleState.Open) { 38 currentX = 2; 39 } else { 40 if (blockImage == null || offBackgroundImage == null || onBackgroundImage == null) 41 return; 42 currentX=offBackgroundImage.getWidth()-blockImage.getWidth()-2; 43 } 44 } 45 public enum ToggleState { 46 Open, Close 47 } 48 49 public ToggleButton(Context context, AttributeSet attrs) { 50 super(context, attrs); 51 // TODO Auto-generated constructor stub 52 } 53 54 public void setOnBackgroundResource(int id) { 55 56 onBackgroundImage = BitmapFactory.decodeResource(getResources(), id); 57 } 58 59 public void setOffBackgroundResource(int id) { 60 offBackgroundImage = BitmapFactory.decodeResource(getResources(), id); 61 62 } 63 64 public void setState(ToggleState state) { 65 preState = this.state = state; 66 if (state == ToggleState.Open) { 67 currentX = 2; 68 } else { 69 if (blockImage != null && offBackgroundImage != null) 70 currentX = offBackgroundImage.getWidth() - blockImage.getWidth() - 2; 71 } 72 invalidate(); 73 } 74 75 public ToggleState getState() { 76 return state; 77 } 78 79 public void setBlockResource(int id) { 80 blockImage = BitmapFactory.decodeResource(getResources(), id); 81 } 82 83 84 @Override 85 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 86 // TODO Auto-generated method stub 87 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 88 setMeasuredDimension(onBackgroundImage.getWidth(), onBackgroundImage.getHeight()); 89 90 } 91 92 @Override 93 protected void onDraw(Canvas canvas) { 94 // TODO Auto-generated method stub 95 super.onDraw(canvas); 96 // 有效性判斷,如果其中有一個為空,拒絕繪制,繼續繪制也沒有意義 97 if (blockImage == null || offBackgroundImage == null || onBackgroundImage == null) 98 return; 99 if ((currentX + blockImage.getWidth() / 2) > onBackgroundImage.getWidth() / 2) { 100 canvas.drawBitmap(offBackgroundImage, 0, 0, null); 101 } else { 102 canvas.drawBitmap(onBackgroundImage, 0, 0, null); 103 } 104 canvas.drawBitmap(blockImage, currentX, 1, null); 105 } 106 107 @Override 108 public boolean onTouchEvent(MotionEvent event) { 109 currentX = (int) event.getX(); 110 switch (event.getAction()) { 111 case MotionEvent.ACTION_DOWN: 112 // 記錄鼠標按下時的x 113 mouseDownX = (int) event.getX(); 114 // 按下的時候不進行重繪 115 isInvalidate = false; 116 break; 117 case MotionEvent.ACTION_MOVE: 118 // 記錄鼠標發生了移動 119 isMove = true; 120 break; 121 case MotionEvent.ACTION_UP: 122 // 判斷鼠標按下和抬起過程中是否發生了移動 123 // 鼠標抬起時,判斷當前x坐標位置 124 if (isMove) { 125 // 發生了移動,判斷當前位置 126 if ((currentX + blockImage.getWidth() / 2) > onBackgroundImage.getWidth() / 2) { 127 // 背景圖的后半段 128 currentX = onBackgroundImage.getWidth() - blockImage.getWidth(); 129 state = ToggleState.Close; 130 } else { 131 // 背景圖的前半段 132 currentX = 2; 133 state = ToggleState.Open; 134 } 135 } else { 136 // 沒有發生移動,即為點擊事件,更改狀態,同時改變滑塊位置 137 if (state == ToggleState.Open) { 138 state = ToggleState.Close; 139 currentX = onBackgroundImage.getWidth() - blockImage.getWidth() - 2; 140 } else { 141 state = ToggleState.Open; 142 currentX = 2; 143 } 144 } 145 // 復位,以免影響下一次的觸摸事件 146 isMove = false; 147 if (preState != state && clickListener != null) { 148 clickListener.onClick(this); 149 preState = state; 150 } 151 break; 152 } 153 if (currentX < 2) 154 currentX = 2; 155 if (currentX + blockImage.getWidth() >= onBackgroundImage.getWidth()) 156 currentX = onBackgroundImage.getWidth() - blockImage.getWidth() - 2; 157 // 通知控件繪制 158 if (isInvalidate) 159 invalidate(); 160 else 161 isInvalidate = true; 162 return true; 163 } 164 165 }
代碼里的注釋夠多吧,哈哈,所以就不進行講解了。
接下來我們在activity布局文件里面使用這個ToggleButton 控件

1 <包名.ToggleButton 2 android:layout_width="wrap_content" 3 android:layout_height="wrap_content" 4 android:id="@+id/testToggleButton" 5 />
我們在activity里面使用吧

1 final ToggleButton toggle=(ToggleButton) findViewById(R.id.testToggleButton); 2 toggle.setState(ToggleState.Close); 3 toggle.setOnBackgroundResource(R.drawable.on); 4 toggle.setOffBackgroundResource(R.drawable.off); 5 toggle.setBlockResource(R.drawable.block); 6 7 toggle.SetOnClickListener(new OnClickListener() { 8 @Override 9 public void onClick(View v) { 10 // TODO Auto-generated method stub 11 String state=(toggle.getState()==ToggleState.Open?"打開":"關閉"); 12 Toast.makeText(MainActivity.this, "當前狀態:"+state, Toast.LENGTH_SHORT).show(); 13 } 14 });
注意啊,我上面的一行代碼的位置
toggle.setState(ToggleState.Close);
設置狀態這行代碼放在了設置圖片之前,但效果仍然是關閉狀態。
但ToggleButton里面如果不重寫OnLayout方法,顯示出來的狀態就只能是打開狀態。