SwitchButton 1.2

(这篇主要谈实现方法,使用方法请移步Github

昨天晚上把SwitchButton更新到了1.2,增加了一些新特性。这个小东西变得越来越好用,越来越强大了。

首先是增加了Material Design样式的支持。先放一张图:

switchbutton_md

感觉略专业的样子啊~

回顾iOS 7 Style

MD的样式实际上和之前的iOS样式(下图)一样,都是对SwitchButton的应用。在完成样式的时候对SwitchButton实现的修改才是比较有意义的更新。

ios_style

 

实现iOS样式时,难点在于iOS的thumb有阴影,这就使得thumb的实际大小要超出背景素材的大小范围。其实在做第一版的时候就意识到了这个问题,只是在用第一版实现iOS样式时出现了不少bug。实现方式方法是:

对SwitchButton中的thumbMarginLeft/Top/Right/Bottom属性为负值的情况进行处理。因为measure是要以thumb的大小(大于background)为准,所以在定义background的范围的时候就要把thumbMargin的部分减去,相当于缩小background的范围,这样就实现了阴影投在background外面的效果。

Material Design样式和Shrink特性

其实利用1.1已经完善的thumbMargin属性已经完全可以实现Material Design样式了,原理都是一样的,把背景范围缩小到thumb以内,就可以完成。但是和iOS还不同的是,MD样式pressed状态的素材大小要远大于normal状态,如果单纯使用thumbMargin实现,虽然也能达到目的,但是SwitchButton的大小就会变得性价比很低,因为要为pressed素材保留空间。

为了压缩SwitchButton的大小,在实现Material Design样式之后,我在想Android能不能像iOS一样,在View的边界之外绘制,这样的话就可以缩小View的边界,也就是在不影响显示效果的同时缩小SwitchButton的大小,更方便平衡美观。

实现方法是在onDraw方法中扩展canvas的bounds。

在之前的文章中提到过canvas实际上可以理解为coordinate,坐标系。这个坐标系的bounds是父控件在layout的时候确定的,子View的onDraw方法中传入的canvas就是被父控件clip之后的坐标系。那么我们在拿到这个canvas之后,是不是可以扩展它的bounds呢?答案是肯定的,因为在屏幕上canvas只有一个,所以只需要扩展coordinate的bounds就可以了,核心代码(onDraw中)如下:

1
2
3
4
5
6
canvas.getClipBounds(mBounds);
if (mBounds != null && mConf.needShrink()) {
mBounds.inset(mConf.getInsetX(), mConf.getInsetY());
canvas.clipRect(mBounds, Region.Op.REPLACE);
canvas.translate(mConf.getInsetBounds().left, mConf.getInsetBounds().top);
}

首先获取当前canvas的bounds,然后调用inset方法扩展bounds的范围,再移动坐标系的位置,使得原点位于扩展之后的bounds的左上角。这样就完成了,而且原来的绘制方法可以保持不变。

绘制的问题解决了,然后就是更新measureWidth和measureHeight方法,也很简单,以measureWidth为例,只需要在返回之前对shrink参数进行处理即可:
measuredWidth += (mConf.getInsetBounds().left + mConf.getInsetBounds().right);
因为Configuration中保存的参数是负值,所以在这里用+=求结果。

还有就是对一些辅助的Zone做处理,不同的是,因为这些Zone基于measuredWidth/Height,所以需要把shrink的部分再加回来:(以setupBackZone为例setupSafeZone和这个类似)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void setupBackZone() {
int w = getMeasuredWidth();
int h = getMeasuredHeight();
if (w > 0 && h > 0) {
if (mBackZone == null) {
mBackZone = new Rect();
}
int left, right, top, bottom;
left = getPaddingLeft() + (mConf.getThumbMarginLeft() > 0 ? 0 : -mConf.getThumbMarginLeft());
right = w - getPaddingRight() - (mConf.getThumbMarginRight() > 0 ? 0 : -mConf.getThumbMarginRight()) + (-mConf.getShrinkX());
top = getPaddingTop() + (mConf.getThumbMarginTop() > 0 ? 0 : -mConf.getThumbMarginTop());
bottom = h - getPaddingBottom() - (mConf.getThumbMarginBottom() > 0 ? 0 : -mConf.getThumbMarginBottom()) + (-mConf.getShrinkY());
mBackZone.set(left, top, right, bottom);
} else {
mBackZone = null;
}
}

SateList

好像篇幅又有点长了……打手打手……这部分快速结束。

1.2版还增加了对StateListDrawable的支持。这个应该算是修复吧,因为以前版本没有考虑到这点,导致对thumb和background设置了StateListDrawable之后不会更新状态。其实实现这个也不难的:

  • 在onTouch方法中适时调用View的setPressed方法;
  • 在setChecked方法中手动调用refreshDrawableState方法(如果你在setChecked方法中调用了父类的同名方法就算了,因为我没有调用所以就手动调用了);
  • 覆写drawableStateChanged方法变更Drawable状态。
    这么说来好像也不是那么方便哈。

其实这次更新给了我一个教训,就是尽量在覆写的方法中保留父类的同名方法的调用,这样才能延续特性。

结尾

终于到结尾了~

Switch Button虽小,但是感动常在,Oh yeah!