Switch?
Switch的使用场景非常广泛,它的功能和Checkbox几乎相同,但一般来说看起来更美观。虽然系统有自带的Switch控件,但是和很多app的自有风格并不搭配。
所以自定义实现一个Switch也是非常有用的。
**************************************************************************
自定义实现Switch关键点?
总结一下,Switch的外观千差万别,但必备的属性即这几个:
(1)、记录一个Boolean值(只有两种状态)
(2)、外观可以直接显示当前状态
(3)、可以单击切换状态
(4)、可以触摸滑动切换状态(个人觉得就这一点儿和Checkbox有区别)
观察源码其实可以发现系统自带的“Switch”和“Checkbox”都继承于“CompoundButton”。其实 CompoundButton已经有了Switch的最基本属性就是能够记录一个Boolean状态。那么我也通过继承CompoundButton来实现自定义的Switch。(可以通过下方提供的源码看下运行效果)结构图如下(最终只显示红色圆圈部分):
*************************************************************************具体实现?
我觉得要实现这个Switch主要是以下几个问题:
(1)、怎样自定义属性以及传递属性值?(自定义控件应该都需要这一步的)
如何实现这一步可以参照: http://blog.csdn.net/tianjf0514/article/details/7520988此处Switch是直接将定义属性和属性赋值放在同一个xml中的,通过一个Style来传递所有自定义属性值,所以在布局文件中没有新增该包的资源命名空间。<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="Switch"> <!-- Drawable to use as the "thumb" that switches back and forth. --> <attr name="thumb" format="reference" /> <!-- Drawable to use as the "track" that the switch thumb slides within. --> <attr name="track" format="reference" /> <attr name="switchWidth" format="dimension" /> <attr name="switchHeight" format="dimension" /> </declare-styleable> <style name="DemoSwitch"> <item name="track">@drawable/settings_close_bg</item> <item name="thumb">@drawable/settings_green_bg</item> <item name="switchWidth">320px</item> <item name="switchHeight">240px</item> </style> </resources>
java代码中需要接受传递过来的参数,在构造方法中获取:// 获取布局文件中配置的属性,生成一个TypedArray 对象 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Switch, defStyle, 0); // 通过获取的TypedArray 对象,得到具体每个配置的对象的值 mThumbDrawable = a.getDrawable(R.styleable.Switch_thumb); mTrackDrawable = a.getDrawable(R.styleable.Switch_track); switchWidth = a.getDimensionPixelSize(R.styleable.Switch_switchWidth, 120); switchHeight = a.getDimensionPixelSize(R.styleable.Switch_switchHeight, 120);
(2)、怎样只显示图片中红色圆?
其实要想只显示一个特殊图形区域,就可以使用canvas的修剪功能了。我们只需要在onDraw()方法中使用canvas的修剪功能即可。具体如何使用修剪功能,可以参考如下连接: http://blog.sina.com.cn/s/blog_4cd5d2bb0101g2la.html经过测试发现,在andoid 4.2的版本上,切割圆失败了,android4.4切割成功。// 绘制switch中所有部分 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw the switch canvas.save(); mTrackDrawable.setBounds(mTrackLeft, mTrackTop, mTrackRight, mTrackBottom); final int thumbPos = (int) (mThumbPosition + 0.5f); int thumbLeft = thumbPos; int thumbRight = thumbPos + mThumbWidth; mThumbDrawable .setBounds(thumbLeft, mTrackTop, thumbRight, mTrackBottom); mPath.addCircle(mcircleX, mcircleY, mcircleR, Path.Direction.CW); //切显示部分的红色圆 canvas.clipPath(mPath, Region.Op.REPLACE); //因为实现效果要显示thumb 在track中,所以需要先绘制thumb,后绘制track mThumbDrawable.draw(canvas); mTrackDrawable.draw(canvas); canvas.restore(); }
至于如何实现让滑动过来的thumb图片在track图片之上呢?只需要先绘制track图片,后绘制thumb图片就可以了(3)、怎样让图片跟随手指移动?
首先是判定用户的行为是拖拽,此处实现方式为:
case TOUCH_MODE_DOWN: { final float x = ev.getX(); final float y = ev.getY(); if (Math.abs(x - mTouchX) > mTouchSlop || Math.abs(y - mTouchY) > mTouchSlop) { // 设置为拖动模式 mTouchMode = TOUCH_MODE_DRAGGING; mTouchX = x; mTouchY = y; return true; } break;
处于拖拽模式时,只需要计算每次手指移动传递过来的水平方向差值,然后赋值给thumb的位置即可,// 计算thumb的新位置 final float x = ev.getX(); final float dx = x - mTouchX; float newPos = Math.min(mThumbRightRange, Math.max(mThumbPosition + dx, mThumbLeftRange)); if (newPos != mThumbPosition) { mThumbPosition = newPos; mTouchX = x; // 刷新UI invalidate(); }
当然,thumb的移动范围仅限于预定范围内(4)、怎样实现自动滑动?
当触摸事件为“ACTION_UP”或“ACTION_CANCEL”时,我们就需要判定好当前状态了,如果thumb不处于当前状态的最终位置时,就需要自动滑过去。 此处我是采用了timer 和timerTask来实现。
//自动滑动 private void autoSlide(final int currentPosition, boolean checked) { // 单次的移动的改变量 final int increment; final int finalPosition; if (checked) { increment = -2; finalPosition = mThumbLeftRange; } else { increment = 2; finalPosition = mThumbRightRange; } if (finalPosition == currentPosition) { if (mOnAutoSlideListener != null) { mOnAutoSlideListener.onAutoSlideDone(); } return; } if (mTimer == null) { mTimer = new Timer(); } if (autoSliding) { if (mTimerTask != null) { mTimerTask.cancel(); } } autoSliding = true; mTimerTask = new TimerTask() { @Override public void run() { mThumbPosition = mThumbPosition + increment; if (mThumbPosition < mThumbLeftRange) { mThumbPosition = mThumbLeftRange; mTimerTask.cancel(); autoSliding = false; if (mOnAutoSlideListener != null) { mOnAutoSlideListener.onAutoSlideDone(); } } if (mThumbPosition > mThumbRightRange) { mThumbPosition = mThumbRightRange; mTimerTask.cancel(); autoSliding = false; if (mOnAutoSlideListener != null) { mOnAutoSlideListener.onAutoSlideDone(); } } postInvalidate(); } }; mTimer.schedule(mTimerTask, autoSlideDelay, autoSlidePeriod); }
理解了这几步应该就理解了这个自定义Switch。
***********************************************************************缺陷?
(1)、因为配置使用的是像素大小,所以适配多种屏幕的效果很差,所以如果需要适配不同屏需要自己做更多的自适应处理。 (2)、当Switch放置在本身也需要监听手指动作的父控件上时, 会出现抢夺现象。这个需要在Switch中做更多的处理,主要是 使用到这个方法 “requestDisallowInterceptTouchEvent”。
***********************************************************************源码下载 ?
android自定义控件Switch