我们看过一些博客文章,讲述了为什么要适时地使用自定义的view,以及它们是如何帮你正确地封装应用代码的。却少有人讨论这类思考如何转变为我们的app中与view无关的其它部分。
在我开发的应用程序Fragment里,在一些地方我使用自定义的Drawable封装我的逻辑代码,就像使用自定义view一样。代码放在Gist!
在Fragment中,我们使用了水平方向的滑动条作为一些地方的可选的视图区域。这就意味着中央的图标是被选择的图标,每一项都能够以平滑移动的方式移进移出。由此,我们认为最好展现出一个优雅的过渡效果。
然而这并非完全必要。我认为这是一个效果,它使得运动更为流畅,并给应用增加了些许品味。我本可以设置多个图像view并单独呈现,但是这却是自定义drawable的最佳使用场景。
在Android中,Drawable实际上是很接近于View。它们有相似的方法获取布局的边距和边界,并且有可以被覆写的draw方法。在我的例子里,我需要在选中和未选中这两个基于值的drawable中实现平移效果。
在我们的例子中,我们简单地创建出来包含了其他Drawables(含方向)的Drawable的子类。
1 2 3 4 5 6 7 8 9monospace !important; font-size: 12px !important; font-style: normal !important; font-weight: bold !important; vertical-align: baseline !important; float: none !important; position: static !important; min-height: inherit !important; box-sizing: content-box !important;">public
class
RevealDrawable
extends
Drawable {
public
RevealDrawable(Drawable unselected, Drawable selected,
int
orientation) {
this
(
null
,
null
);
mUnselectedDrawable = unselected;
mSelectedDrawable = selected;
mOrientation = orientation;
}
}
接下来,我们需要做的就是设定一个值,用来标明drawable是选中的一栏。恰好Drawable有一个内置函数可以做到这一点,即setLevel(int)。
一个Drawable的级别是从0到10000的整数值,这仅仅允许Drawable根据一个值来定义它的视图。在我们的例子中,我们可以简单地设定5000作为选中状态,0表示左侧完全未选中,10000表示右侧完全未选中。
我们要做的就是重写draw(Canvas canvas)方法,根据当前level值裁剪canvas,从而绘制合适的drawable。
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();
}
}
}
}
就这样,我们仅仅设置了基于滚动位置的水平方向的图标,这事就搞定了。
1 2 3 4 5 6float
offset = getOffestForPosition(recyclerView, position);
if
(Math.abs(offset) <= 1f) {
holder.image.setImageLevel((
int
) (offset *
5000
) +
5000
);
}
else
{
holder.image.setImageLevel(
0
);
}
如果你想看到这份自定义Drawable的源代码,你可以在Github的这里查看。