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