1. 程式人生 > >關於在RecyclerView中高斯模糊造成卡頓,適配問題

關於在RecyclerView中高斯模糊造成卡頓,適配問題

專案需求:

        在RecyclerView的item中使用高斯模糊處理封面圖。固定每個item的高度,但是根據封面圖長寬比例,如果寬比高的比例大於1,個人俗稱寬圖,根據封面圖自動生成高斯模糊的圖片作為背景圖,實際比例的圖片作為顯示的前部圖片。

分析:

        粗看起來,好像就是高斯模糊+RecyclerView,好像沒有什麼特別的,但是確實有個不大不小的坑,而且由於專案中使用Glide,自己也沒有信心換其他的圖片開源框架,也踩了Glide的坑。

         先說高斯模糊的處理,基本是兩種方式,RenderScript,或者fastBlur,具體可參考此文:點選開啟連結。但是我發現,在RecyclerView中,如果用高斯模糊處理每一個item的封面,會造成很嚴重的卡頓。一般使用RenderScript,在搭載高通的手機上卡頓不明顯,但是在麒麟或者聯發科手機上卡頓嚴重。而如果使用fastBlur,情況差不多相反。其實症結是在於我們在處理高斯模糊的時候,一般就是在主執行緒中,但是圖片的處理確實耗時,因此會有卡頓情況。

        處理的辦法,當然是把高斯模糊的處理放到子執行緒中執行,然後切換到主執行緒顯示。我根據自己的專案需求,選擇fastBlur。現在來處理程式碼邏輯,fastBlur的處理是通過工具類,直接傳入圖片的bitmap,轉換後得到模糊後的bitmap,然後ImageView設定即可。bitmap的獲取:

Glide.with(mContext).load(rankingData.getCoverUrl()).asBitmap().skipMemoryCache(false).placeholder(R.drawable.home_img_cover).dontAnimate().into(new 
SimpleTarget<Bitmap>() { @Override public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { Mylog.e(TAG, " onResourceReady: " + " rankingData.getCoverUrl(): " + rankingData.getCoverUrl() + " position : " + position); viewHolder.rlFrameImage.setTag(resource);
loadBitmaps(viewHolder.ivBlurItemBackground, resource); } });

是的,我是通過Glide來獲取的,但是這裡面有兩個問題,一個是通過setTag的方式標記傳入的bitmap時,如果直接用圖片的ViewHolder來setTag,後面使用findViewWithTag(...)會出現錯誤,找過資料大意是Glide內部也會有這個方法,所以相當於衝突了。這個原因如果大家有碰到可以再找找相關資料,當然解決這個bug可以通過setTag(params1,params2)的方式設定,這個我不去發散了,總之後面這種設定的方法不適合我。我簡單粗暴的把Glide需要顯示的ImageView包裝了一個RelativeLayout,對RelativeLayout設定setTag,然後再通過findViewById去找對應的背景圖片 ,最後顯示即可。

private class BitmapWorkerTask extends AsyncTask<Bitmap, Void, Bitmap> {

    private Bitmap bitmapTask;
@Override
protected Bitmap doInBackground(Bitmap... params) {
        bitmapTask = params[0];
        return BlurUtils.fastBlur(mContext, this.bitmapTask, (float) (1 / (1.0 * 8)), 30);
}

    @Override
protected void onPostExecute(Bitmap bitmap) {
        View view = xRecyclerView.findViewWithTag(bitmapTask);
        if (view != null) {
            ImageView imageView = (ImageView) view.findViewById(R.id.iv_blur_item_background);
            if (imageView != null && bitmap != null) {
                imageView.setImageBitmap(bitmap);
imageView.setVisibility(View.VISIBLE);
}
        }
        taskCollection.remove(this);
}
}

到這裡貌似可以了,其實還沒完。上面說到獲取網路圖片的bitmap的方法,有問題。其實按理說這種方法可以用來直接載入圖片,但是如果你這麼去設定,你會發現你placeholder設定的預設圖是沒有效果的。例如,載入第二頁的時候,它的佔位圖是前面某一張已經載入好的圖,然後實際的圖載入完成後再替換。實際體驗上就是你看到一張圖,然後突然又顯示了另一張圖,很像以前listView載入圖片出現的錯圖問題。這個問題怎麼解決的,我也是用了最笨的方法。從我們需求看,封面位置至少需要兩個ImageView,但因為這個bug,我弄成了三個,佈局程式碼如下:

<RelativeLayout
android:id="@+id/frame_image"
android:layout_width="match_parent"
android:layout_height="wrap_content">
    <ImageView
android:id="@+id/iv_item_background"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop" />
    <ImageView
android:id="@+id/iv_blur_item_background"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:visibility="invisible" />
</RelativeLayout>
<ImageView
android:id="@+id/iv_item_front_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignLeft="@id/frame_image"
android:layout_alignRight="@id/frame_image"
android:layout_centerVertical="true"
android:scaleType="centerCrop"
android:visibility="gone" />

我最後的處理邏輯,如果不需要高斯模糊,只顯示第一個,如果需要高斯模糊,顯示第二、三個。我承認,這不是最優的方案,但是我實現效果基本滿足了需求,而且比同事的iOS還要絲絲順滑。

另外,我看到過另外一些app的處理方式,更加簡單,他們的高斯模糊的圖片是後臺實實在在的一張模糊的圖片,其實我也很樂意,如果可以的話。

固然還有一些不足,就是其實載入多頁的時候,其實高斯模糊的圖片還是會變換一次,但是由於只是一張很模糊的圖片的替換,感知已經不那麼明顯了,要想完美解決,估計不要用Glide獲取bitmap吧,自己手寫,未完待續。工具類地址:https://download.csdn.net/download/u012913988/10283828