我們看過一些部落格文章,講述了為什麼要適時地使用自定義的view,以及它們是如何幫你正確地封裝應用程式碼的。卻少有人討論這類思考如何轉變為我們的app中與view無關的其它部分。
在我開發的應用程式Fragment裡,在一些地方我使用自定義的Drawable封裝我的邏輯程式碼,就像使用自定義view一樣。程式碼放在Gist!
用例
在Fragment中,我們使用了水平方向的滑動條作為一些地方的可選的檢視區域。這就意味著中央的圖示是被選擇的圖示,每一項都能夠以平滑移動的方式移進移出。由此,我們認為最好展現出一個優雅的過渡效果。
然而這並非完全必要。我認為這是一個效果,它使得運動更為流暢,並給應用增加了些許品味。我本可以設定多個影像view並單獨呈現,但是這卻是自定義drawable的最佳使用場景。
自定義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的這裡檢視。