Android 動態(tài)高斯模糊效果教程
寫在前面
最近一直在做畢設(shè)項目的準(zhǔn)備工作,考慮到可能要用到一個模糊的效果,所以就學(xué)習(xí)了一些高斯模糊效果的實現(xiàn)。比較有名的就是 FastBlur 以及它衍生的一些優(yōu)化方案,還有就是今天要說的RenderScript 。
因為這東西是現(xiàn)在需要才去學(xué)習(xí)的,所以關(guān)于一些圖像處理和渲染問題就不提了。不過在使用的過程中確實能感受到,雖然不同的方案都能實現(xiàn)相同的模糊效果,但是效率差別真的很大。
本篇文章實現(xiàn)的高斯模糊是根據(jù)下面這篇文章學(xué)習(xí)的,先推薦一下。本文內(nèi)容與其內(nèi)容差不多,只是稍微講的詳細(xì)一點,并修改了代碼中部分實現(xiàn)邏輯和細(xì)節(jié)上的處理。不過主體內(nèi)容不變,所以選擇哪篇文章去學(xué)都是一樣的。
下面就來看一下,如何去實現(xiàn)這樣的高斯模糊效果。
簡單聊聊 Renderscript
因為效果的實現(xiàn)是基于 Renderscript 的,所以有必要先來了解一下。
從它的官方文檔來看,說的很是玄乎。我們只需要知道一點就好了:
RenderScript is a framework for running computationally intensive tasks at high performance on Android.
Renderscript 是 Android 平臺上進(jìn)行高性能計算的框架。
既然是高性能計算,那么說明 RenderScript 對圖像的處理非常強大,所以用它來實現(xiàn)高斯模糊還是比較好的選擇。
那么如何使用它呢?從官方文檔中可以看到,如果需要在 Java 代碼中使用 Renderscript 的話,就必須依賴 android.renderscript 或者android.support.v8.renderscript 中的 API 。既然有 API 那就好辦多了。
下面簡單說一下使用的步驟,這也是官方文檔中的說明:
- 首先需要通過 Context 創(chuàng)建一個 Renderscript ;
- 其次通過創(chuàng)建的 Renderscript 來創(chuàng)建一個自己需要的腳本( ScriptIntrinsic ),比如這里需要模糊,那就是 ScriptIntrinsicBlur ;
- 然后至少創(chuàng)建一個 Allocation 類來創(chuàng)建、分配內(nèi)存空間;
- 接著就是對圖像進(jìn)行一些處理,比如說模糊處理;
- 處理完成后,需要剛才的 Allocation 類來填充分配好的內(nèi)存空間;
- 最后可以選擇性的對一些資源進(jìn)行回收。
文檔中的解釋永遠(yuǎn)很規(guī)矩,比較難懂,我們結(jié)合原博主 湫水長天 的代碼來看一看步驟:
/** * @author Qiushui * @description 模糊圖片工具類 * @revision Xiarui 16.09.05 */ public class BlurBitmapUtil { //圖片縮放比例 private static final float BITMAP_SCALE = 0.4f; /** * 模糊圖片的具體方法 * * @param context 上下文對象 * @param image 需要模糊的圖片 * @return 模糊處理后的圖片 */ public static Bitmap blurBitmap(Context context, Bitmap image,float blurRadius) { // 計算圖片縮小后的長寬 int width = Math.round(image.getWidth() * BITMAP_SCALE); int height = Math.round(image.getHeight() * BITMAP_SCALE); // 將縮小后的圖片做為預(yù)渲染的圖片 Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height, false); // 創(chuàng)建一張渲染后的輸出圖片 Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap); // 創(chuàng)建RenderScript內(nèi)核對象 RenderScript rs = RenderScript.create(context); // 創(chuàng)建一個模糊效果的RenderScript的工具對象 ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); // 由于RenderScript并沒有使用VM來分配內(nèi)存,所以需要使用Allocation類來創(chuàng)建和分配內(nèi)存空間 // 創(chuàng)建Allocation對象的時候其實內(nèi)存是空的,需要使用copyTo()將數(shù)據(jù)填充進(jìn)去 Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap); Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap); // 設(shè)置渲染的模糊程度, 25f是最大模糊度 blurScript.setRadius(blurRadius); // 設(shè)置blurScript對象的輸入內(nèi)存 blurScript.setInput(tmpIn); // 將輸出數(shù)據(jù)保存到輸出內(nèi)存中 blurScript.forEach(tmpOut); // 將數(shù)據(jù)填充到Allocation中 tmpOut.copyTo(outputBitmap); return outputBitmap; } }
上面就是處理高斯模糊的代碼,其中注釋寫的十分詳細(xì),而且已經(jīng)將圖片縮放處理了一下。結(jié)合剛才說的步驟,大家應(yīng)該能有一個大概的印象,實在不懂也沒關(guān)系,這是一個工具類,直接 Copy 過來即可。
當(dāng)然,原博主將代碼封裝成輪子了,也可以直接在項目中引用 Gradle 也是可以的,但是我覺得源碼還是要看一看的。
簡單的模糊
好了,有了一個大概的印象后,來看一下如何實現(xiàn)高斯模糊效果吧!
首先你可以在項目中直接引用原博主封裝的輪子:
compile 'com.qiushui:blurredview:0.8.1'
如果不想引用的話,就必須在當(dāng)前 Module 的 build.gradle 中添加如下代碼:
defaultConfig { renderscriptTargetApi 19 renderscriptSupportModeEnabled true }
等構(gòu)建好就可以使用了。如果構(gòu)建失敗的話,只需要把 minSdkVersion 設(shè)置成 19 就好了,暫時不知是何原因。不過從 StackOverflow 中了解到這是個Bug ,那就不必深究。
現(xiàn)在來看代碼實現(xiàn),首先布局文件中就一個 ImageView ,沒啥好說的,從上面的模糊圖片工具類可以看出,要想獲得一個高斯模糊效果的圖片,需要三樣?xùn)|西:
Context:上下文對象
Bitmap:需要模糊的圖片
BlurRadius:模糊程度
這里需要注意一下:
目前這種方案只適用于 PNG 格式的圖片,而且圖片大小最好小一點,雖然代碼中已經(jīng)縮放了圖片,但仍然可能會出現(xiàn)卡頓的情況。
現(xiàn)在只要設(shè)置一下圖片和模糊程度就好了:
/** * 初始化View */ @SuppressWarnings("deprecation") private void initView() { basicImage = (ImageView) findViewById(R.id.iv_basic_pic); //拿到初始圖 Bitmap initBitmap = BitmapUtil.drawableToBitmap(getResources().getDrawable(R.raw.pic)); //處理得到模糊效果的圖 Bitmap blurBitmap = BlurBitmapUtil.blurBitmap(this, initBitmap, 20f); basicImage.setImageBitmap(blurBitmap); }
來看一下運行圖:
可以看到,圖片已經(jīng)實現(xiàn)了模糊效果,而且速度還蠻快的,總的來說通過 BlurBitmapUtil.blurBitmap()就能得到一張模糊效果的圖 。
自定義模糊控件
原博主的輪子里給我們封裝了一個自定義的 BlurredView ,剛開始我覺得沒必要自定義。后來發(fā)現(xiàn)自定義的原因是需要實現(xiàn)動態(tài)模糊效果。
那為什么不能手動去設(shè)置模糊程度呢?他給出的解釋是:
“如果使用上面的代碼進(jìn)行實時渲染的話,會造成界面嚴(yán)重的卡頓?!?/strong>
我也親自試了一試,確實有點卡。他實現(xiàn)動態(tài)模糊處理的方案是這樣的:
“先將圖片進(jìn)行最大程度的模糊處理,再將原圖放置在模糊后的圖片上面,通過不斷改變原圖的透明度(Alpha值)來實現(xiàn)動態(tài)模糊效果?!?/strong>
這個方案確實很巧妙的實現(xiàn)動態(tài)效果,但是注意如果要使用這種方式,就必須有兩張一模一樣的圖片。如果在代碼中直接寫,就需要兩個控件,如果圖片多的話,顯然是不可取的。所以輪子里有一個自定義的 BlurredView 。
不過這個 BlurredView 封裝的不是太好,我刪減了一部分內(nèi)容,原因稍后再說。先來看一下核心代碼。
首先是自定義的 BlurredView 繼承于 RelativeLayout ,在布局文件中可以看到,里面有兩個 ImageView,且是疊在一起的。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/blurredview_blurred_img" .../> <ImageView android:id="@+id/blurredview_origin_img" .../> </FrameLayout>
同時也定義了一些屬性:
<resources> <declare-styleable name="BlurredView"> <attr name="src" format="reference"/> <attr name="disableBlurred" format="boolean"/> </declare-styleable> </resources>
一個是設(shè)置圖片,一個是設(shè)置是否禁用模糊。最后就是 BlurredView 類,代碼如下,有大量刪減,只貼出核心代碼:
/** * @author Qiushui * @description 自定義模糊View類 * @revision Xiarui 16.09.05 */ public class BlurredView extends RelativeLayout { /*========== 全局相關(guān) ==========*/ private Context mContext;//上下文對象 private static final int ALPHA_MAX_VALUE = 255;//透明最大值 private static final float BLUR_RADIUS = 25f;//最大模糊度(在0.0到25.0之間) /*========== 圖片相關(guān) ==========*/ private ImageView mOriginImg;//原圖ImageView private ImageView mBlurredImg;//模糊后的ImageView private Bitmap mBlurredBitmap;//模糊后的Bitmap private Bitmap mOriginBitmap;//原圖Bitmap /*========== 屬性相關(guān) ==========*/ private boolean isDisableBlurred;//是否禁用模糊效果 ... /** * 以代碼的方式添加待模糊的圖片 * * @param blurredBitmap 待模糊的圖片 */ public void setBlurredImg(Bitmap blurredBitmap) { if (null != blurredBitmap) { mOriginBitmap = blurredBitmap; mBlurredBitmap = BlurBitmapUtil.blurBitmap(mContext, blurredBitmap, BLUR_RADIUS); setImageView(); } } ... /** * 填充ImageView */ private void setImageView() { mBlurredImg.setImageBitmap(mBlurredBitmap); mOriginImg.setImageBitmap(mOriginBitmap); } /** * 設(shè)置模糊程度 * * @param level 模糊程度, 數(shù)值在 0~100 之間. */ @SuppressWarnings("deprecation") public void setBlurredLevel(int level) { //超過模糊級別范圍 直接拋異常 if (level < 0 || level > 100) { throw new IllegalStateException("No validate level, the value must be 0~100"); } //禁用模糊直接返回 if (isDisableBlurred) { return; } //設(shè)置透明度 mOriginImg.setAlpha((int) (ALPHA_MAX_VALUE - level * 2.55)); } ... }
從代碼中可以看到,最核心的就是下面三個方法:
setBlurredImg(Bitmap blurredBitmap):設(shè)置圖片,并復(fù)制兩份;
setImageView():給兩個ImageView設(shè)置相應(yīng)的圖片,內(nèi)部調(diào)用;
setBlurredLevel(int level):設(shè)置透明程度;
思路就是先選定一張圖片,一張作為原圖,一張作為模糊處理過的圖。再分別將這兩張圖設(shè)置給自定義 BlurredView 中的兩個 ImageView ,最后處理模糊過后的那張圖的透明度。
好了,現(xiàn)在來寫一個自定義的模糊效果圖,首先是布局,很簡單:
<com.blurdemo.view.BlurredView android:id="@+id/bv_custom_blur" android:layout_width="match_parent" android:layout_height="match_parent" app:src="@raw/pic" app:disableBlurred="false" />
可以看到,設(shè)置了圖片,設(shè)置了開啟模糊,那么我們在Activity中只需設(shè)置透明程度即可:
private void initView() { customBView = (BlurredView) findViewById(R.id.bv_custom_blur); //設(shè)置模糊度 customBView.setBlurredLevel(100); }
效果圖與上圖一樣,這里就不重復(fù)貼了??梢钥吹?,代碼簡單了很多,不過僅僅因為方便簡單可不是自定義 View 的作用,作用在于接下來要說的 動態(tài)模糊效果 的實現(xiàn)。
動態(tài)模糊
我們先來看一下啥叫動態(tài)模糊效果:
從圖中可以看到,隨著我們觸摸屏幕的時候,背景的模糊程度會跟著變化。如果要直接設(shè)置其模糊度會及其的卡頓,所以正如原博主所說,可以用兩張圖片來實現(xiàn)。
大體思路就是,上面的圖片模糊處理,下面的圖片不處理,然后通過手勢改變上面模糊圖片的透明度即可。
所以跟前面的代碼幾乎一樣,只需要重寫 onTouchEvent 方法即可:
/** * 初始化View */ private void initView() { customBView = (BlurredView) findViewById(R.id.bv_dynamic_blur); //設(shè)置初始模糊度 initLevel = 100; customBView.setBlurredLevel(initLevel); } /** * 觸摸事件 */ @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: downY = ev.getY(); break; case MotionEvent.ACTION_MOVE: float moveY = ev.getY(); //手指滑動距離 float offsetY = moveY - downY; //屏幕高度 十倍是為了看出展示效果 int screenY = getWindowManager().getDefaultDisplay().getHeight() * 10; //手指滑動距離占屏幕的百分比 movePercent = offsetY / screenY; currentLevel = initLevel + (int) (movePercent * 100); if (currentLevel < 0) { currentLevel = 0; } if (currentLevel > 100) { currentLevel = 100; } //設(shè)置模糊度 customBView.setBlurredLevel(currentLevel); //更改初始模糊等級 initLevel = currentLevel; break; case MotionEvent.ACTION_UP: break; } return super.onTouchEvent(ev); }
從代碼中可以看到,這里是通過手指滑動距離占屏幕的百分比來計算改變后的透明等級的,代碼應(yīng)該不難,很容易理解。當(dāng)然原博主博客中是通過進(jìn)度條來改變的,也是可以的,就不在贅述了。
與 RecylcerView 相結(jié)合
先來看一張效果圖,這個圖也是仿照原博主去實現(xiàn)的,但是還是有略微的不同。
本來的自定義 BlurredView 中還有幾段代碼是改變背景圖的位置的,因為希望上拉下拉的時候背景圖也是可以移動的,但是從體驗來看效果不是太好,上拉的過程中會出現(xiàn)留白的問題。
雖然原博主給出了解決方案:手動給背景圖增加一個高度,但這并不是最好的解決方式,所以我就此功能給刪去了,等找到更好的實現(xiàn)方式再來補充。
現(xiàn)在來看如何實現(xiàn)?首先布局就是底下一層自定義的 BlurredView ,上面一個 RecylcerView,RecylcerView 有兩個 Type ,一個是頭布局,一個是底下的列表,很簡單,就不詳細(xì)說了。
重點仍然是動態(tài)模糊的實現(xiàn),在上面的動態(tài)模糊中,我們采取了重寫 onTouchEvent 方法,但是這里剛好是 RecylcerView ,我們可以根據(jù)它的滾動監(jiān)聽,也就是 onScrollListener 來完成動態(tài)改變透明度,核心方法如下:
//RecyclerView 滾動監(jiān)聽 mainRView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); //滾動距離 mScrollerY += dy; //根據(jù)滾動距離控制模糊程度 滾動距離是模糊程度的十倍 if (Math.abs(mScrollerY) > 1000) { mAlpha = 100; } else { mAlpha = Math.abs(mScrollerY) / 10; } //設(shè)置透明度等級 recyclerBView.setBlurredLevel(mAlpha); } });
代碼很簡單,就是在 onScrolled 方法中計算并動態(tài)改變透明度,只要掌握了原理,實現(xiàn)起來還是很容易的。
總結(jié)
從前面所有的動態(tài)圖可以看到,運行起來還是比較快的,但是我從 Android Monitor 中看到,在每一次剛開始渲染模糊的時候,GPU 渲染的時間都很長,所以說可能在性能方面還是有所欠佳。
當(dāng)然也可能跟模擬器有關(guān)系,真機上測試是很快的。而且貌似比 FastBlur 還快一點,等有空測試幾個高斯模糊實現(xiàn)方法的性能,來對比一下。
到此,這種實現(xiàn)高斯模糊的方法已經(jīng)全部講完了,感謝原博主這么優(yōu)秀的文章,再次附上鏈接:
湫水長天 – 教你一分鐘實現(xiàn)動態(tài)模糊效果
其他參考資料
RenderScript – Android Developers
Android RenderScript入門(1)
高斯模糊效果實現(xiàn)方案及性能對比 – lcyFox
項目源碼
BlurDemo – IamXiaRui – Github
以上就是對Android 動態(tài)高斯模糊效果教程的示例,謝謝大家對本站的支持!
- Android關(guān)于Glide的使用(高斯模糊、加載監(jiān)聽、圓角圖片)
- Android實現(xiàn)圖片的高斯模糊(兩種方式)
- Android圖片特效:黑白特效、圓角效果、高斯模糊
- Android實現(xiàn)動態(tài)高斯模糊效果
- Android 實現(xiàn)圖片模糊、高斯模糊、毛玻璃效果的三種方法
- Android 高仿微信語音聊天頁面高斯模糊(毛玻璃效果)
- Android項目實戰(zhàn)之Glide 高斯模糊效果的實例代碼
- Android RenderScript實現(xiàn)高斯模糊
- Android實現(xiàn)動態(tài)高斯模糊效果示例代碼
- Android實現(xiàn)圖片高斯模糊
相關(guān)文章
android使用SkinManager實現(xiàn)換膚功能的示例
本篇文章主要介紹了android使用SkinManager實現(xiàn)換膚功能的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02詳解Android中Glide與CircleImageView加載圓形圖片的問題
本篇文章主要介紹了詳解Android中Glide與CircleImageView加載圓形圖片的問題,具有一定的參考價值,有興趣的可以了解一下2017-09-09Android點擊事件之多點觸摸與手勢識別的實現(xiàn)
這篇文章主要介紹了Android點擊事件之多點觸摸與手勢識別的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05Adnroid 自定義ProgressDialog加載中(加載圈)
這篇文章主要介紹了Adnroid 自定義ProgressDialog加載中(加載圈),需要的朋友可以參考下2017-06-06RecyclerView+SnapHelper實現(xiàn)無限循環(huán)篩選控件
這篇文章主要為大家詳細(xì)介紹了RecyclerView+SnapHelper實現(xiàn)無限循環(huán)篩選控件,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-10-10解決AndroidStudio無法運行java中的mian方法問題
這篇文章主要介紹了解決AndroidStudio無法運行java中的mian方法問題,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10Android實現(xiàn)自動輪詢的RecycleView
這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)自動輪詢的RecycleView,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-10-10解決genymotion模擬器無法聯(lián)網(wǎng)的正確方法100%成功
android 5.1版不能聯(lián)網(wǎng),三個步驟的設(shè)置就可以解決你的genymotion模擬器無法聯(lián)網(wǎng)的問題2018-03-03