自定义Drawables

我们都看过关于为什么你应该适当的使自定义Views和如何能帮助你正确的封装你的应用程序代码的帖子。但非视图相关的部分如何转化为我们apps的其他部分的这种思考方式,我们对此并不非常了解。

在我的应用Fragment中,有些地方我使用自定义Drawables来封装我的逻辑,就像你在customView中做的一样。

用例

在Fragment中,有一些使用水平滚动条作为一个选择视图的地方。这意味着该中心图标就是“选中”的图标,整个条目就该平滑的平移进去或平移出。为此,一个好的显示转换将非常棒。

虽然这并非完全必要,但我觉得它是一个能让这个滑动更加流畅并增加一个触摸的类在app上的效果。我本可以设置多个imageviews并让他们每个独立出来,但这真是使用自定义drawables的好地方~

自定义Drawables

在Android里,Drawables和Views实际上非常的相似。他们有相似的方法,例如padding和bounds(layout),并且都有一个可以被重写的draw方法。就我而言,我需要能够在一个选中的图片和一个未选中的图片之间进行转换基础上的一个值。

在我们的例子中,我们简单地创建一个包含其他Drawables(和方向)的Drawable子类.

1
2
3
4
5
6
7
8
9
public class RevealDrawable extends Drawable {
public RevealDrawable(Drawable unselected, Drawable selected, int orientation) {
this(null, null);

mUnselectedDrawable = unselected;
mSelectedDrawable = selected;
mOrientation = orientation;
}
}

接下来,我们需要设定能够与图片选择过程中相关联的值。幸运的是Drawable内置了这种类型的事件,setLevel(int).

一个Drawable的level是介于0和10000的整数,它只是允许Drawable基于一个值去自定义它的view.在我们的例子中,我们可以定义5000作为图片被选择时的状态值,其他没被选中状态值在5000左右两侧。
All we need to do now is to override the draw(Canvas canvas) method to draw the appropriate drawable by clipping the canvas based on the current level.
现在我们要做的就是重写draw(Canvas canvas)方法,通过基于当前的level裁剪画布去绘制相应的图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@Override
public void draw(Canvas canvas) {

// If level == 10000 || level == 0, just draw the unselected image
int level = getLevel();
if (level == 10000 || level == 0) {
mRevealState.mUnselectedDrawable.draw(canvas);
}

// If level == 5000 just draw the selected image
else if (level == 5000) {
mRevealState.mSelectedDrawable.draw(canvas);
}

// Else, draw the transitional version
else {
final Rect r = mTmpRect;
final Rect bounds = getBounds();

{ // Draw the unselected portion
float value = (level / 5000f) - 1f;
int w = bounds.width();
if ((mRevealState.mOrientation & HORIZONTAL) != 0) {
w = (int) (w * Math.abs(value));
}
int h = bounds.height();
if ((mRevealState.mOrientation & VERTICAL) != 0) {
h = (int) (h * Math.abs(value));
}
int gravity = value < 0 ? Gravity.LEFT : Gravity.RIGHT;
Gravity.apply(gravity, w, h, bounds, r);

if (w > 0 && h > 0) {
canvas.save();
canvas.clipRect(r);
mRevealState.mUnselectedDrawable.draw(canvas);
canvas.restore();
}
}

{ // Draw the selected portion
float value = (level / 5000f) - 1f;
int w = bounds.width();
if ((mRevealState.mOrientation & HORIZONTAL) != 0) {
w -= (int) (w * Math.abs(value));
}
int h = bounds.height();
if ((mRevealState.mOrientation & VERTICAL) != 0) {
h -= (int) (h * Math.abs(value));
}
int gravity = value < 0 ? Gravity.RIGHT : Gravity.LEFT;
Gravity.apply(gravity, w, h, bounds, r);

if (w > 0 && h > 0) {
canvas.save();
canvas.clipRect(r);
mRevealState.mSelectedDrawable.draw(canvas);
canvas.restore();
}
}
}
}

就这样,我们可基于滑动的位置以简单地设置icon的level,结束了~

1
2
3
4
5
6
float offset = getOffestForPosition(recyclerView, position);
if (Math.abs(offset) <= 1f) {
holder.image.setImageLevel((int) (offset * 5000) + 5000);
} else {
holder.image.setImageLevel(0);
}

想要看自定义Drawable源码,请在Github上搜索Gisthere