这几天做自定义控件,发现一种很常用的开关按钮,效果图如下:
点击或者拖拽按钮可以实现“开”、“关”两种状态的切换,它的实现原理非常简单:
一个View对象展示在手机屏幕上的时候,它一般会执行以下几个步骤:
1)调用构造方法,创建对象
2)测量view的大小,调用onMeasure方法
3)确定view的位置,view自身有一定的建议权,但是决定权在父view手中, onLayout方法
4)绘制view,onDrow方法
依据view对象的执行步骤,我们分别重写view对象的这些方法,然后依次去实现。直接上代码
/** * 自定义的开关按钮 请访问 http://blog.csdn.net/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事件。
点我下载源码