弄过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方法,显示出来的状态就只能是打开状态。