弄过android开发的都知道,系统有一个默认的ToggleButton,但很多人都觉得他很难看,当然也包括我。如果你感觉他不难看,那你就继续使用系统的吧,这篇文章对你来说是多余的了。
今天来写一个模仿微信的ToggleButton控件,是啊,模仿都是模仿"大家之作",腾讯、360等等,也确实,他们设计出来的东西确实好看。
下面看效果图打开状态,关闭状态
先奉献上三张图片的素材,打开背景图,关闭背景图,滑块背景图。这里背景图高度是小于前两个背景图2个像素的。这样才能出现最终的打开和关闭后的滑块上下出现1个像素背景的效果。
开始我们的控件代码,这里我们自定义一个ToggleButton控件,当然要继承自View,下面贴出我的代码
public class ToggleButton extends View { private Bitmap onBackgroundImage;
private Bitmap offBackgroundImage;
private Bitmap blockImage;
private ToggleState state = ToggleState.Open;
private int currentX;
private int mouseDownX = -1;
private boolean isMove = false;
private ToggleState preState;
private boolean isInvalidate = true;
public ToggleButton(Context context) {
super(context);
// TODO Auto-generated constructor stub
} private OnClickListener clickListener = null; /*
* 其实应该是stateChange事件,懒得改了
*/
public void SetOnClickListener(OnClickListener listener) {
if (listener != null)
clickListener = listener;
} @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// TODO Auto-generated method stub
super.onLayout(changed, left, top, right, bottom);
//layout方法是在ondraw方法之前执行,
//在这个做判断是防止用户在设置Image之前setState,
//这样Image为null,系统无法获取block宽度,也就无法设置currentX坐标,
//系统将采用默认Open状态,会出现即使用户设置状态为close,而界面显示仍未open状态,
//在这里判断,是因为当前image和state肯定已经设置完毕了
if (state == ToggleState.Open) {
currentX = 2;
} else {
if (blockImage == null || offBackgroundImage == null || onBackgroundImage == null)
return;
currentX=offBackgroundImage.getWidth()-blockImage.getWidth()-2;
}
}
public enum ToggleState {
Open, Close
} public ToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
} public void setOnBackgroundResource(int id) { onBackgroundImage = BitmapFactory.decodeResource(getResources(), id);
} public void setOffBackgroundResource(int id) {
offBackgroundImage = BitmapFactory.decodeResource(getResources(), id); } public void setState(ToggleState state) {
preState = this.state = state;
if (state == ToggleState.Open) {
currentX = 2;
} else {
if (blockImage != null && offBackgroundImage != null)
currentX = offBackgroundImage.getWidth() - blockImage.getWidth() - 2;
}
invalidate();
} public ToggleState getState() {
return state;
} public void setBlockResource(int id) {
blockImage = BitmapFactory.decodeResource(getResources(), id);
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(onBackgroundImage.getWidth(), onBackgroundImage.getHeight()); } @Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
// 有效性判断,如果其中有一个为空,拒绝绘制,继续绘制也没有意义
if (blockImage == null || offBackgroundImage == null || onBackgroundImage == null)
return;
if ((currentX + blockImage.getWidth() / 2) > onBackgroundImage.getWidth() / 2) {
canvas.drawBitmap(offBackgroundImage, 0, 0, null);
} else {
canvas.drawBitmap(onBackgroundImage, 0, 0, null);
}
canvas.drawBitmap(blockImage, currentX, 1, null);
} @Override
public boolean onTouchEvent(MotionEvent event) {
currentX = (int) event.getX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录鼠标按下时的x
mouseDownX = (int) event.getX();
// 按下的时候不进行重绘
isInvalidate = false;
break;
case MotionEvent.ACTION_MOVE:
// 记录鼠标发生了移动
isMove = true;
break;
case MotionEvent.ACTION_UP:
// 判断鼠标按下和抬起过程中是否发生了移动
// 鼠标抬起时,判断当前x坐标位置
if (isMove) {
// 发生了移动,判断当前位置
if ((currentX + blockImage.getWidth() / 2) > onBackgroundImage.getWidth() / 2) {
// 背景图的后半段
currentX = onBackgroundImage.getWidth() - blockImage.getWidth();
state = ToggleState.Close;
} else {
// 背景图的前半段
currentX = 2;
state = ToggleState.Open;
}
} else {
// 没有发生移动,即为点击事件,更改状态,同时改变滑块位置
if (state == ToggleState.Open) {
state = ToggleState.Close;
currentX = onBackgroundImage.getWidth() - blockImage.getWidth() - 2;
} else {
state = ToggleState.Open;
currentX = 2;
}
}
// 复位,以免影响下一次的触摸事件
isMove = false;
if (preState != state && clickListener != null) {
clickListener.onClick(this);
preState = state;
}
break;
}
if (currentX < 2)
currentX = 2;
if (currentX + blockImage.getWidth() >= onBackgroundImage.getWidth())
currentX = onBackgroundImage.getWidth() - blockImage.getWidth() - 2;
// 通知控件绘制
if (isInvalidate)
invalidate();
else
isInvalidate = true;
return true;
} }
代码里的注释够多吧,哈哈,所以就不进行讲解了。
接下来我们在activity布局文件里面使用这个ToggleButton 控件
<包名.ToggleButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/testToggleButton"
/>
我们在activity里面使用吧
final ToggleButton toggle=(ToggleButton) findViewById(R.id.testToggleButton);
toggle.setState(ToggleState.Close);
toggle.setOnBackgroundResource(R.drawable.on);
toggle.setOffBackgroundResource(R.drawable.off);
toggle.setBlockResource(R.drawable.block); toggle.SetOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
String state=(toggle.getState()==ToggleState.Open?"打开":"关闭");
Toast.makeText(MainActivity.this, "当前状态:"+state, Toast.LENGTH_SHORT).show();
}
});
注意啊,我上面的一行代码的位置
toggle.setState(ToggleState.Close);
设置状态这行代码放在了设置图片之前,但效果仍然是关闭状态。
但ToggleButton里面如果不重写OnLayout方法,显示出来的状态就只能是打开状态。