Android - Custom - SlidingToggleButton(滑动开关按钮)
2013-04-12 11:27
441 查看
先看效果图:
![](http://img.my.csdn.net/uploads/201304/12/1365737417_4873.png)
再贴代码:
其次是具体的子类(负责为BaseSlidingToggleButton提供资源图片,然后在使用的时候就可以直接使用子类了):
说明:
关键地方有三个,第一个就是如何使用资源图片绘制出效果来,所用到的资源图如下:
btn_sliding_frame.png
![](http://img.my.csdn.net/uploads/201304/12/1365737441_3421.png)
btn_sliding_state_normal.png
![](http://img.my.csdn.net/uploads/201304/12/1365737455_4131.png)
btn_sliding_state_disable.png
![](http://img.my.csdn.net/uploads/201304/12/1365737467_8824.png)
btn_sliding_state_mask.png
![](http://img.my.csdn.net/uploads/201304/12/1365737478_1245.png)
btn_sliding_slider_normal.png
![](http://img.my.csdn.net/uploads/201304/12/1365737493_6724.png)
btn_sliding_slider_pressed.png
![](http://img.my.csdn.net/uploads/201304/12/1365737505_9334.png)
btn_sliding_slider_disable.png
![](http://img.my.csdn.net/uploads/201304/15/1365988272_6341.png)
btn_sliding_slider_mask.png
![](http://img.my.csdn.net/uploads/201304/12/1365737530_5745.png)
在将资源图片一层一层的绘制到画布上的时候,要使用遮罩效果,再配合两个黑色的遮罩层即可达到预期的效果,具体请看代码。已经注释的很清除了。
再一个关键点就是滑动效果,滑动效果可以使用Scroller实现,具体Scroller的使用方式,这里就不说了,不明白的问百度。
最后一个就是,各种状态的维护,包括单击切换状态、滑动结束的时候根据滑动的距离切换状态或者回滚、飞速滑动的时候切换状态、在合适的时候调用状态改变回调、按下以及弹起的时候切换滑块状态图片、禁用的时候切换状态曾以及滑块层的状态图片,设置默认值等等。
![](http://img.my.csdn.net/uploads/201304/12/1365737417_4873.png)
再贴代码:
/** * 滑动开关按钮 */ public abstract class BaseSlidingToggleButton extends View implements OnGestureListener, OnDoubleTapListener{ private static final int DURATION = 300; private static final int MIN_ROLLING_DISTANCE = 30;//滚动最小生效距离 private GestureDetector gestureDetector;//手势识别器 private Scroller scroller;//滚动器 private Bitmap stateNormalBitmap;//正常状态时的状态图片 private Bitmap stateDisableBitmap;//禁用状态时的状态图片 private Bitmap stateMaskBitmap;//状态遮罩图片 private Bitmap frameBitmap;//框架图片 private Bitmap sliderNormalBitmap;//正常状态时的滑块图片 private Bitmap sliderPressedBitmap;//按下状态时的滑块图片 private Bitmap sliderDisableBitmap;//禁用状态时的滑块图片 private Bitmap sliderMaskBitmap;//滑块遮罩图片 private Paint paint;//颜料 private PorterDuffXfermode porterDuffXfermode;//遮罩类型 private boolean checked;//状态,true:开启;false:关闭 private int currentLeft;//当前状态图以及滑块图的X坐标 private int checkedLeft;//当状态为开启时状态图以及滑块图的X坐标 private int uncheckedLeft;//当状态为关闭时状态图以及滑块图的X坐标 private int scrollDistanceCount;//滚动距离计数器 private boolean needHandle;//当在一组时件中发生了滚动操作时,在弹起或者取消的时候就需要根据滚动的距离来切换状态或者回滚 private boolean down;//是否按下,用来在弹起的时候,恢复状态图以及滑块的状态 private boolean enabled;//是否可用,表示当前视图的激活状态 private OnCheckedChanageListener onCheckedChanageListener;//状态改变监听器 private boolean pendingSetState;//在调用setState()来设置初始状态的时候,如果onLeft字段还没有初始化(在Activity的onCreate()中调用此setState的时候就会出现这种情况),那么就将此字段标记为true,等到在onDraw()方法中初始化onLeft字段时,会检查此字段,如果为true就会再次调用setState()设置初始状态 private boolean pendingChecked;//记录默认状态值 public BaseSlidingToggleButton(Context context) { super(context); init(); } public BaseSlidingToggleButton(Context context, AttributeSet attrs) { super(context, attrs); init(); } private final void init(){ gestureDetector = new GestureDetector(getContext(), this); gestureDetector.setOnDoubleTapListener(this); stateNormalBitmap = onGetStateNormalBitmap(); if(stateNormalBitmap == null){ throw new RuntimeException("onGetStateNormalBitmap() The return value cannot be null"); } stateDisableBitmap = onGetStateDisableBitmap(); if(stateDisableBitmap == null){ stateDisableBitmap = stateNormalBitmap; } stateMaskBitmap = onGetStateMaskBitmap(); if(stateMaskBitmap == null){ throw new RuntimeException("onGetStateMasklBitmap() The return value cannot be null"); } frameBitmap = onGetFrameBitmap(); if(frameBitmap == null){ throw new RuntimeException("onGetFrameBitmap() The return value cannot be null"); } sliderNormalBitmap = onGetSliderNormalBitmap(); if(sliderNormalBitmap == null){ throw new RuntimeException("onGetSliderNormalBitmap() The return value cannot be null"); } sliderPressedBitmap = onGetSliderPressedBitmap(); if(sliderPressedBitmap == null){ sliderPressedBitmap = sliderNormalBitmap; } sliderDisableBitmap = onGetSliderDisableBitmap(); if(sliderDisableBitmap == null){ sliderDisableBitmap = sliderNormalBitmap; } sliderMaskBitmap = onGetSliderMaskBitmap(); if(sliderMaskBitmap == null){ throw new RuntimeException("onGetSliderMaskBitmap() The return value cannot be null"); } paint = new Paint(); paint.setFilterBitmap(false); porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN); scroller = new Scroller(getContext(), new AccelerateDecelerateInterpolator()); enabled = isEnabled(); } @Override protected void onDraw(Canvas canvas) { //初始化状态为开启时状态图以及滑块图的X坐标 if(checkedLeft == 0){ checkedLeft = -1 * (stateNormalBitmap.getWidth() - frameBitmap.getWidth());//选中时的X坐标就是状态层的宽度减去框架层的宽度的负值 //如果有需要设置的状态 if(pendingSetState){ pendingSetState = false; setChecked(pendingChecked, 0); } } //创建一个新的全透明图层,大小同当前视图的大小一样,这一步绝对不可缺少,要不然最周绘制出来的图片背景会是黑色的 canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); //绘制状态层 canvas.drawBitmap(enabled?stateNormalBitmap:stateDisableBitmap, currentLeft, 0, paint); paint.setXfermode(porterDuffXfermode); canvas.drawBitmap(stateMaskBitmap, 0, 0, paint);//使用遮罩模式只显示状态层中和状态遮罩重合的部分 paint.setXfermode(null);//因为是共用一个Paint,所以要立马清除掉遮罩效果 //绘制框架层 canvas.drawBitmap(frameBitmap, 0, 0, paint); //绘制滑块层 if(enabled){ canvas.drawBitmap(down?sliderPressedBitmap:sliderNormalBitmap, currentLeft, 0, paint); }else{ canvas.drawBitmap(sliderDisableBitmap, currentLeft, 0, paint); } paint.setXfermode(porterDuffXfermode); canvas.drawBitmap(sliderMaskBitmap, 0, 0, paint);//使用遮罩模式只显示滑块层中和滑块遮罩重合的部分 paint.setXfermode(null);//因为是共用一个Paint,所以要立马清除掉遮罩效果 //合并图层 canvas.restore(); super.onDraw(canvas); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //计算宽度 int realWidthSize = 0; int widthMode = MeasureSpec.getMode(widthMeasureSpec);//解析宽度参考类型 int widthSize = MeasureSpec.getSize(widthMeasureSpec);//解析宽度尺寸 switch (widthMode) { case MeasureSpec.AT_MOST://如果widthSize是当前视图可使用的最大宽度 realWidthSize = frameBitmap.getWidth(); break; case MeasureSpec.EXACTLY://如果widthSize是当前视图可使用的绝对宽度 realWidthSize = widthSize; break; case MeasureSpec.UNSPECIFIED://如果widthSize对当前视图宽度的计算没有任何参考意义 realWidthSize = frameBitmap.getWidth(); break; } //计算高度 int realHeightSize = 0; int heightMode = MeasureSpec.getMode(heightMeasureSpec);//解析参考类型 int heightSize = MeasureSpec.getSize(heightMeasureSpec);//解析高度尺寸 switch (heightMode) { case MeasureSpec.AT_MOST://如果heightSize是当前视图可使用的最大高度 realHeight 4000 Size = frameBitmap.getHeight(); break; case MeasureSpec.EXACTLY://如果heightSize是当前视图可使用的绝对高度 realHeightSize = heightSize; break; case MeasureSpec.UNSPECIFIED://如果heightSize对当前视图高度的计算没有任何参考意义 realHeightSize = frameBitmap.getHeight(); break; } setMeasuredDimension(realWidthSize, realHeightSize); } @Override public void computeScroll() { //如果正处于滚动中那么就更改X坐标并刷新 if(scroller.computeScrollOffset()){ currentLeft = scroller.getCurrX(); invalidate(); } } @Override public boolean onTouchEvent(MotionEvent event) { if(enabled){ //先经过手势识别器的处理 gestureDetector.onTouchEvent(event); //如果当前事件使弹起或者取消 if(event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP){ //如果之前发生了按下事件,那么此时一定要恢复显示的滑块图片为正常状态时的图片 if(down){ down = false; invalidate(); } //如果本次事件中发生了滑动,那么此时需要判断是否需要切换状态还是需要回滚到原来的位置 if(needHandle){ //如果本次滚动的距离超过的最小生效距离,就切换状态,否则就回滚 if(Math.abs(scrollDistanceCount) >= MIN_ROLLING_DISTANCE){ setChecked(scrollDistanceCount > 0, currentLeft, DURATION); }else{ setChecked(isChecked(), currentLeft, DURATION); } needHandle = false; } } } return true; } @Override public boolean onDown(MotionEvent e) { scrollDistanceCount = 0; needHandle = false; //切换滑块图片的状态 down = true; invalidate(); return true; } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { toggle(); return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { needHandle = true;//标记在弹起或取消的时候需要处理 scrollDistanceCount += distanceX;//记录本次总的滑动的距离 currentLeft -= distanceX;//计算接下来状态层以及滑块曾的X坐标 //防止滑动的过程中超过范围 if(currentLeft >= uncheckedLeft){ currentLeft = uncheckedLeft; }else if(currentLeft <= checkedLeft){ currentLeft = checkedLeft; } invalidate(); return true; } @Override public void onLongPress(MotionEvent e) { } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { needHandle = false;//标记在弹起或取消时不再处理 setChecked(e2.getX() < e1.getX(), currentLeft, DURATION);//根据前后两次X坐标的大小,判断接下来谁要切换为开启状态还是关闭状态 return true; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { return true; } @Override public boolean onDoubleTap(MotionEvent e) { return true; } @Override public boolean onDoubleTapEvent(MotionEvent e) { return true; } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); this.enabled = enabled; } public boolean isChecked() { return checked; } /** * 滚动 * @param startX 开始X坐标 * @param endX 结束Y坐标 * @param duration 持续时间 */ private void scroll(int startX, int endX, int duration){ //当开始位置和结束位置一样时不处理 if(startX != endX){ scroller.startScroll(startX, 0, endX - startX, 0, duration); invalidate(); } } /** * 设置状态 * @param isChecked 开启还是关闭 * @param startX 开始滚动的位置 * @param duration 持续时间 */ private void setChecked(boolean isChecked, int startX, int duration){ this.checked = isChecked; //如果是要开启 if(isChecked()){ scroll(startX, checkedLeft, duration); }else{ scroll(startX, uncheckedLeft, duration); } //调用选中状态改变回调 if(onCheckedChanageListener != null){ onCheckedChanageListener.onCheckedChanage(this, isChecked()); } } /** * 设置状态 * @param isChecked 开启还是关闭 * @param duration 持续时间 */ public void setChecked(boolean isChecked, int duration){ //如果尚未完成初始化工作,就先延迟,等待初始化完毕之后再处理 if(checkedLeft == 0){ pendingSetState = true; pendingChecked = isChecked; }else{ setChecked(isChecked, isChecked?uncheckedLeft:checkedLeft, duration); } } /** * 设置状态 * @param isChecked 开启还是关闭 */ public void setChecked(boolean isChecked){ setChecked(isChecked, DURATION); } /** * 切换状态 * @param duration 持续时间 */ public void toggle(int duration){ setChecked(!isChecked(), duration); } /** * 切换状态 */ public void toggle(){ setChecked(!isChecked()); } /** * 设置选中状态改变监听器 * @param onCheckedChanageListener 选中状态改变监听器 */ public void setOnCheckedChanageListener(OnCheckedChanageListener onCheckedChanageListener) { this.onCheckedChanageListener = onCheckedChanageListener; } /** * 选中状态改变监听器 */ public interface OnCheckedChanageListener{ /** * 当选中状态发生改变 * @param slidingToggleButton * @param isChecked 是否选中 */ public void onCheckedChanage(BaseSlidingToggleButton slidingToggleButton, boolean isChecked); } /** * 获取正常状态时的状态图片 * @return */ public abstract Bitmap onGetStateNormalBitmap(); /** * 获取禁用状态时的状态图片 * @return */ public abstract Bitmap onGetStateDisableBitmap(); /** * 获取状态遮罩图片 * @return */ public abstract Bitmap onGetStateMaskBitmap(); /** * 获取框架图片 * @return */ public abstract Bitmap onGetFrameBitmap(); /** * 获取正常状态时的滑块图片 * @return */ public abstract Bitmap onGetSliderNormalBitmap(); /** * 获取按下状态时的滑块图片 * @return */ public abstract Bitmap onGetSliderPressedBitmap(); /** * 获取禁用状态时的滑块图片 * @return */ public abstract Bitmap onGetSliderDisableBitmap(); /** * 获取滑块遮罩图片 * @return */ public abstract Bitmap onGetSliderMaskBitmap(); }
其次是具体的子类(负责为BaseSlidingToggleButton提供资源图片,然后在使用的时候就可以直接使用子类了):
/** * 滑动开关按钮 */ public class SlidingToggleButton extends BaseSlidingToggleButton { public SlidingToggleButton(Context context) { super(context); } public SlidingToggleButton(Context context, AttributeSet attrs) { super(context, attrs); } @Override public Bitmap onGetStateNormalBitmap() { return BitmapFactory.decodeResource(getResources(), R.drawable.btn_sliding_state_normal); } @Override public Bitmap onGetStateDisableBitmap() { return BitmapFactory.decodeResource(getResources(), R.drawable.btn_sliding_state_disable); } @Override public Bitmap onGetStateMaskBitmap() { return BitmapFactory.decodeResource(getResources(), R.drawable.btn_sliding_state_mask); } @Override public Bitmap onGetFrameBitmap() { return BitmapFactory.decodeResource(getResources(), R.drawable.btn_sliding_frame); } @Override public Bitmap onGetSliderNormalBitmap() { return BitmapFactory.decodeResource(getResources(), R.drawable.btn_sliding_slider_normal); } @Override public Bitmap onGetSliderPressedBitmap() { return BitmapFactory.decodeResource(getResources(), R.drawable.btn_sliding_slider_pressed); } @Override public Bitmap onGetSliderDisableBitmap() { return BitmapFactory.decodeResource(getResources(), R.drawable.btn_sliding_slider_disable); } @Override public Bitmap onGetSliderMaskBitmap() { return BitmapFactory.decodeResource(getResources(), R.drawable.btn_sliding_slider_mask); } }
说明:
关键地方有三个,第一个就是如何使用资源图片绘制出效果来,所用到的资源图如下:
btn_sliding_frame.png
![](http://img.my.csdn.net/uploads/201304/12/1365737441_3421.png)
btn_sliding_state_normal.png
![](http://img.my.csdn.net/uploads/201304/12/1365737455_4131.png)
btn_sliding_state_disable.png
![](http://img.my.csdn.net/uploads/201304/12/1365737467_8824.png)
btn_sliding_state_mask.png
![](http://img.my.csdn.net/uploads/201304/12/1365737478_1245.png)
btn_sliding_slider_normal.png
![](http://img.my.csdn.net/uploads/201304/12/1365737493_6724.png)
btn_sliding_slider_pressed.png
![](http://img.my.csdn.net/uploads/201304/12/1365737505_9334.png)
btn_sliding_slider_disable.png
![](http://img.my.csdn.net/uploads/201304/15/1365988272_6341.png)
btn_sliding_slider_mask.png
![](http://img.my.csdn.net/uploads/201304/12/1365737530_5745.png)
在将资源图片一层一层的绘制到画布上的时候,要使用遮罩效果,再配合两个黑色的遮罩层即可达到预期的效果,具体请看代码。已经注释的很清除了。
再一个关键点就是滑动效果,滑动效果可以使用Scroller实现,具体Scroller的使用方式,这里就不说了,不明白的问百度。
最后一个就是,各种状态的维护,包括单击切换状态、滑动结束的时候根据滑动的距离切换状态或者回滚、飞速滑动的时候切换状态、在合适的时候调用状态改变回调、按下以及弹起的时候切换滑块状态图片、禁用的时候切换状态曾以及滑块层的状态图片,设置默认值等等。
相关文章推荐
- android-UI组件实例大全(五)------开关按钮ToggleButton和开关Switch
- Android ToggleButton Example--开关按钮
- Android的ToggleButton实现开关按钮
- Android自学笔记之ToggleButton(开关按钮)的功能、特殊属性、用法
- Android-开关按钮ToggleButton
- Android控件之ProgressBar(进度条),ToggleButton/SwitchBar(开关按钮),SeekBar(拖动条)/RatingBar(等级评分)
- Android学习路之开关按钮ToggleButton和开关Switch
- Android入门教程二十八之开关按钮ToggleButton和开关Switch
- Android ToggleButton Example--开关按钮
- Android——图片视图(ImageView)、状态开关按钮(ToggleButton)、时钟、图片透明度、滚动和时间选择器
- Android——滚动视图(ScrollView)图片视图(ImageView)、状态开关按钮(ToggleButton)、时钟
- Android开关按钮ToggleButton的使用,可以实现密码框和文本框的切换
- Android基础入门教程——2.3.6 开关按钮ToggleButton和开关Switch
- android(11) 滑动的开关按钮
- Android控件ToggleButton多状态按钮使用详解
- ToggleButton(状态开关按钮)及Swich(开关)使用详解
- 状态开关按钮(ToggleButton)和开关(Switch)的功能与用法
- 状态开关按钮ToggleButton的简单使用:实现动态控制布局
- Android——使用多状态按钮ToggleButton(自己动手 丰衣足食)
- Android SwitchButton 开关按钮