弄过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 }
View Code

代码里的注释够多吧,哈哈,所以就不进行讲解了。

接下来我们在activity布局文件里面使用这个ToggleButton 控件

1 <包名.ToggleButton 
2         android:layout_width="wrap_content"
3         android:layout_height="wrap_content"
4         android:id="@+id/testToggleButton"
5         />
View Code

我们在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         });
View Code

注意啊,我上面的一行代码的位置

toggle.setState(ToggleState.Close);

设置状态这行代码放在了设置图片之前,但效果仍然是关闭状态。

但ToggleButton里面如果不重写OnLayout方法,显示出来的状态就只能是打开状态。