这几天做自定义控件,发现一种很常用的开关按钮,效果图如下:
点击或者拖拽按钮可以实现“开”、“关”两种状态的切换,它的实现原理非常简单:
一个View对象展示在手机屏幕上的时候,它一般会执行以下几个步骤:
1)调用构造方法,创建对象
2)测量view的大小,调用onMeasure方法
3)确定view的位置,view自身有一定的建议权,但是决定权在父view手中, onLayout方法
4)绘制view,onDrow方法
依据view对象的执行步骤,我们分别重写view对象的这些方法,然后依次去实现。直接上代码
/**
* 自定义的开关按钮 请访问 http://blog.youkuaiyun.com/qq_20889581?viewmode=contents 阅读详细信息
*
* @author Administrator
*
*/
public class MyToggleButton extends View implements OnClickListener {
// 作为开关按钮的背景图片
private Bitmap backgroundBitmap;
// 可以滑动或点击的按钮
private Bitmap slide_button;
private Paint paint;
// 当前按钮的开关状态
private boolean currState = false;
// 滑动的按钮图片左边的距离
private float slide_btn_left;
// 手指第一次触摸屏幕的时候的X值,也就是down的时候的x值
private float firstX;
// 是否发生拖拽
private boolean isDrag = false;
private float lastX;
/**
* 在代码中new对象的时候,使用此构造
*
* @param context
*/
public MyToggleButton(Context context) {
super(context);
initView();
}
/**
* 在布局文件中声明的view,创建时,由系统调用此构造
*
* @param context
* 上下文
* @param attrs
* 属性集
*/
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
/**
* 完成一些初始化的操作
*/
private void initView() {
backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.background);
slide_button = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button);
paint = new Paint();
// 打开抗矩齿
paint.setAntiAlias(true);
setOnClickListener(this);
}
/**
* 一个View对象显示在屏幕上需要完成的工作如下: 1、调用构造方法,创建对象 2、测量view的大小,调用onMeasure方法
* 3、确定view的位置,view自身有一定的建议权,但是决定权在父view手中, onLayout方法 4、绘制view,onDrow方法
*/
/**
* 测量view大小的时候的回调,设置当前view的大小 width :view的宽度 height :view的高度 (单位:像素)
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
}
// 确定位置的时候调用此方法
// 自定义view的时候,作用不大
// @Override
// protected void onLayout(boolean changed, int left, int top, int right,
// int bottom) {
// super.onLayout(changed, left, top, right, bottom);
// }
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
// 绘制 背景
/*
* canvas 画布 bitmap 要绘制的图片 left 图片的左边届 top 图片的上边届 paint 绘制图片要使用的画笔
*/
canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
// 绘制可滑动的按钮图片
canvas.drawBitmap(slide_button, slide_btn_left, 0, paint);
}
@Override
public void onClick(View v) {
// 点击的时候,更改滑动按钮的开关状态
if (!isDrag) {
currState = !currState;
// 刷新开关状态
flushState();
}
}
/**
* 要想实现拖拽的效果,需要重写手机屏幕的onTouchEvent方法
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 手指按下
// 获得手指在屏幕上的位置,这里只需要X坐标
firstX = lastX = event.getX();// 记录下当前的最开始在屏幕的值
isDrag = false;// down的时候没有发生拖拽
break;
case MotionEvent.ACTION_MOVE:
// 手指移动,表示发生了拖拽
// 判断是否发生了拖拽(移动了5个像素表示发生了拖拽)
if (Math.abs(event.getX() - firstX) > 5) {
isDrag = true;
}
// 计算手指在屏幕上移动的距离
float dis = event.getX() - lastX;
// 手指移动过程中获得的x坐标就是最后的位置
lastX = event.getX();
// 设置滑动按钮左边的边界
slide_btn_left = slide_btn_left + dis;
break;
case MotionEvent.ACTION_UP:
// 手指抬起
lastX = event.getX();
if (lastX - firstX == 0) {
// 这是点击事件
if (!isDrag) {
currState = !currState;
// 刷新开关状态
flushState();
}
} else {
if (isDrag) {
// slideBtn左边届最大值
int maxLeft = backgroundBitmap.getWidth() - slide_button.getWidth();
if (slide_btn_left > maxLeft / 2) {
// 为打开状态
currState = true;
} else {
currState = false;
}
flushState();
}
}
break;
}
// 刷新试图
flushView();
return true;
}
/**
* 刷新开与关的两个状态
*/
private void flushState() {
// currState表示当前的开关状态
if (currState) {
// 如果是开的状态
slide_btn_left = backgroundBitmap.getWidth() - slide_button.getWidth();
} else {
slide_btn_left = 0;
}
// 刷新当前视图
flushView();
}
private void flushView() {
/*
* 对 slide_btn_left 的值进行判断 ,确保其在合理的位置 即 0<=slide_btn_left <= maxLeft
*/
int maxLeft = backgroundBitmap.getWidth() - slide_button.getWidth(); // slideBtn
// 左边届最大值
// 确保 slideBtn_left >= 0
slide_btn_left = (slide_btn_left > 0) ? slide_btn_left : 0;
// 确保 slideBtn_left <=maxLeft
slide_btn_left = (slide_btn_left < maxLeft) ? slide_btn_left : maxLeft;
// 刷新当前视图,导致onDraw方法执行
invalidate();
}
}
值得注意的是,onClick方法和onTouchEvent方法会冲突,也就是说onClick方法不会执行,为了解决这个问题,在down和up的时候分别记录下手指在屏幕的位置,如果up的时候的位置相对于down的位置没有改变,则认为是执行了onClick事件。