我们看过一些博客文章,讲述了为什么要适时地使用自定义的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 9 |
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 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的这里查看。