最近要作相似網易雲音樂背景高斯模糊的效果, 同時也想讓背景變化時不要那麼生硬, 就是下面這個效果app
Google一番後決定用TransitionDrawable, 因爲是配合UniversalImageLoader使用, 因此只須要實現一個BitmapDisplayer做爲UIL的配置項就好了.ide
最初的代碼是這樣寫的動畫
private static class DrawableFadeDisplayer implements BitmapDisplayer { private final int durationMillis; public DrawableFadeDisplayer(int durationMillis) { this.durationMillis = durationMillis; } @Override public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) { ImageView imageview = (ImageView) imageAware.getWrappedView(); Drawable oldDrawable = imageview.getDrawable(); TransitionDrawable td = new TransitionDrawable(new Drawable[] { oldDrawable==null?(new ColorDrawable(Color.TRANSPARENT)):oldDrawable, new BitmapDrawable(Resources.getSystem(), bitmap) }); imageview.setImageDrawable(td); td.startTransition(durationMillis); } }
最關鍵的部分是display
中的代碼, 首先獲取了舊的Drawable
, 而後和新生成的BitmapDrawable
一塊兒構造一個TransitionDrawable
, 最後調用startTransition
就能夠了.this
簡單明瞭. 實際使用中, 發現app佔用的內存愈來愈高, 但只要退出Activity
, 一兩次GC以後內存就會降下來, 基本能夠肯定是這段代碼形成了內存泄露.code
問題出在這句代碼內存
Drawable oldDrawable = imageview.getDrawable();
乍一看這句代碼邏輯是沒有問題的, 每次咱們都是將舊的Drawable
做爲第一層, 新的Drawable
做爲第二層建立TransitionDrawable
, 可是注意咱們是建立的TransitionDrawable
, 並將它設給ImageView
, 也就是說咱們調用getDrawable
拿到的也是TransitionDrawable
, 一個TransitionDrawable
實際上是持有多個Drawable
的, 在這裏是持有兩個.get
程序進行第一次漸變更畫後, ImageView
中的TransitionDrawable
持有兩個Drawable
, 第二次漸變更畫, 咱們將TransitionDrawable
和新的BitmapDrawable
組合在一塊兒建立一個新的TransitionDrawable
.
簡單示意一下ImageView
持有的Drawable
:
第一次漸變後:it
TransitionDrawable(drawable0, drawable1)
第二次漸變後:io
TransitionDrawable( TransitionDrawable(drawable0, drawable1), drawable2 )
第三次漸變後:class
TransitionDrawable( TransitionDrawable( TransitionDrawable(drawable0, drawable1), drawable2 ), drawable3 )
這樣ImageView
致使不能被回收的Drawable
數量愈來愈多, 最終OOM.
因此咱們正確的作法不該該是直接將getDrawable
的值拿來當第一層Drawable
, 而是先判斷一下這個值的類型, 若是是TransitionDrawable
, 應該獲取它第二層Drawable
做爲咱們的第一層, 這樣原來的第一層Drawable
就會失去到GC Roots的引用鏈, 從而能夠被回收.
固然另外一種思路是TransitionDrawable
動畫完成以後再將新的BitmapDrawable
設給ImageView
, 但並無這個監聽器, 最簡單便捷的仍是上面的思路.
最終代碼修改爲下面的樣子, 主要是須要判斷getDrawable
的類型, 若是是TransitionDrawable
, 就獲取第二層Drawable
.
private static class DrawableFadeDisplayer implements BitmapDisplayer { private final int durationMillis; public DrawableFadeDisplayer(int durationMillis) { this.durationMillis = durationMillis; } @Override public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) { ImageView imageview = (ImageView) imageAware.getWrappedView(); Drawable oldDrawable = imageview.getDrawable(); Drawable oldBitmapDrawable = null; if (oldDrawable == null) { oldBitmapDrawable = new ColorDrawable(Color.TRANSPARENT); } else if (oldDrawable instanceof TransitionDrawable) { oldBitmapDrawable = ((TransitionDrawable) oldDrawable).getDrawable(1); } else { oldBitmapDrawable = oldDrawable; } TransitionDrawable td = new TransitionDrawable(new Drawable[] { oldBitmapDrawable, new BitmapDrawable(Resources.getSystem(), bitmap) }); imageview.setImageDrawable(td); td.startTransition(durationMillis); } }