SwitchButton的Why/What/How

SwitchButton是我在Android上开发的一个开源项目。Github:https://github.com/kyleduo/SwitchButton

default_off

做SwitchButton的缘由

最初的原型在很早就做了,但是没有发到Github上。后来使用的时候逐渐发现一些Bug和不合理的地方,修改起来也比较麻烦,鉴于现在很多UI都扁平化了,干脆做个更好用、更适合扁平效果的版本,然后开源出来。

SwitchButton最早应该是iOS上的控件,它的功能类似CheckBox,有开、关两种状态,但是因为展现形式比CheckBox更友好,同时可以增加动态效果,所以后来Android也增加了这个View,但是苦于Android的碎片化,用这个控件的APP恐怕真心不多。但是更多APP都有这个需要,所以虽然是个小控件,还是有实际价值的。

SwitchButton的特性和效果

SwitchButton这个项目的特性有下面几点:

  • 在XML使用颜色、尺寸配置扁平化效果。
  • 支持对背景和Thumb使用不同的src类型:图片、9.png、shape。
  • 支持Thumb的按压效果,Drawable设置为Selector即可。
  • 提供了Configuration类,方便在代码中配置和更改SwitchButton的效果。
  • Configuration配置项丰富,包含动画速度等等。
  • 可以配置Thumb的四个方向的margin,方便制作类似Instagram等APP中的带阴影的效果(下面图中也有阴影效果的预览)。这点是我在最初做的时候就考虑的主要问题,在代码中用margin配置,实现简单,同时减轻编码和美工负担。
  • SwitchButton类中预置了DEBUG开关,打开可以绘制辅助边框,方便修改和调试。
    SwitchButton的可定制效果:

all_off

 

SwitchButton的结构

switchbutton_struct

 

Configuration到SwitchButton的通信是单向的,使用Configuration类和在xml中配置可以达到同样的效果,在SwitchButton内部,从xml读取的属性,也是保存到Configuration对象中。

AnimationController的实现很大程度上借鉴了碎总(Issacw0ng)的同名项目,这个类负责对动画的计算,通过listener通知SwitchButton进行重绘。目前只支持线性动画,设计上给后面扩展动画类型留了空间。我对Android上的动画设计一直有疑惑,这次算一个尝试,以后的控件设计很可能会在这种方式上进行扩展和发挥。

SwitchButton是最主要的类,读取配置并显示View,接收事件并处理,响应动画并重绘View。

一些代码

本来想写一点值得记录一下的实现细节,但是回顾一下发现真得太细了,直接看源码就行了。下面提出两个对于自定义View应该会有用的地方,其他的,源码里发现吧。

1. 对多种Drawable的支持。

1
2
3
4
5
6
7
8
9
10
private Drawable fetchDrawable(TypedArray ta, int attrId, int alterColorId, int defaultColor) {
Drawable tempDrawable = ta.getDrawable(attrId);
if (tempDrawable == null) {
int tempColor = ta.getColor(alterColorId, defaultColor);
tempDrawable = new GradientDrawable();
((GradientDrawable) tempDrawable).setCornerRadius(999f);
((GradientDrawable) tempDrawable).setColor(tempColor);
}
return tempDrawable;
}

上面这段代码是SwitchButton类中从xml中提取Drawable属性的方法。

第二个参数是Drawable属性名(因为有三个可设置的Drawable属性);

第三个参数是当Drawable不存在时代替的Color的属性(没有Drawable会自动生成纯色GradientDrawable);

最后一个参数就是默认的Color(都没有设置的话就使用Configuration类中的默认配置)。

2. Click事件的判定。

1
2
3
4
5
6
7
8
9
10
11
// ...

float time = event.getEventTime() - event.getDownTime();

if (deltaX < mTouchSlop && deltaY < mTouchSlop && time < mClickTimeout) {
performClick();
} else {
slideToChecked(nextStatus);
}

// ...

这段代码是从onTouchEvent中截取的。要提的是这两个变量:

mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); // Android默认的判断一个Touch是Touch还是滑动的距离阈值。

mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); // Android默认的判断是点击事件的时间阈值。

上面逻辑表达式的含义就是通过当前touch事件的移动距离和按压时间是否满足click事件的条件,如果是,需要调用Override自父类的performClick();方法,也就是将touch事件当做Click事件处理。