一分鐘實現動態模糊效果(毛玻璃)
本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出
本文轉載自 http://wl9739.github.io/ 湫水長天的部落格
現在,越來越多的App裡面使用了模糊效果,我尤其喜歡雅虎天氣的介面,上滑的時候背景圖片會跟著移動,最重要的是背景圖片會根據手指上下移動的距離來進行不同程度的模糊,感覺甚為驚奇,畢竟大家都知道,在Android平臺上進行模糊渲染是一個相當耗CPU也相當耗時的操作,一旦處理不好,卡頓是在所難免的。雖然我並不知道雅虎天氣是怎麼做出這種效果的,但是簡單的模仿一下的話,還是能做到的。
一般來說,考慮到效率,渲染一張圖片最好的方法是使用OpenGL,其次是使用C++/C,使用Java程式碼是最慢的。但是Android推出RenderScript之後,我們就有了新的選擇,測試表明,使用RenderScript的渲染效率和使用C/C++不相上下,但是使用RenderScript卻比使用JNI簡單地多!同時,Android團隊提供了RenderScript
不過在使用RenderScript之前,對於模糊一張圖片,需要注意的是,我們應該儘量不要使用原尺寸解析度的圖片,最好將圖片縮小比例,這小渲染的效率要高一些。
動態模糊的實現
如何使用RenderScript來模糊一張圖片呢?廢話不多說,先上核心程式碼:
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 |
public |
完成上面的程式碼後,需要在app的gradle檔案中新增如下的支援:
1 2 3 4 5 |
defaultConfig { ...... renderscriptTargetApi 19 renderscriptSupportModeEnabled true } |
程式碼做了簡單的註釋以幫助理解,如果需要詳細瞭解,可以查閱官方文件:RenderScript
然後,我們可以看一下模糊前和模糊後的效果對比:
將圖片模糊後,接下來要考慮的是怎麼實現動態模糊
效,有一點需要注意的是,即使我們使用了RenderScript
這種高效的渲染方式,但是在實際測試中,渲染一張500*700解析度的PNG格式圖片,在我的Pro
6手機上,仍然需要50ms左右的時間,顯然如果使用上面的程式碼進行實時渲染的話,會造成介面嚴重的卡頓。
既然實時渲染這條路走不通,那麼就需要我們另闢蹊徑了,我這裡可以提供一種方法:先將圖片進行最大程度的模糊處理,再將原圖放置在模糊後的圖片上面,通過不斷改變原圖的透明度(Alpha值)來實現動態模糊效果。
簡單的程式碼如下:
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
public class MainActivity extends AppCompatActivity { /** * 原始圖片控制元件 */ private ImageView mOriginImg; /** * 模糊後的圖片控制元件 */ private ImageView mBluredImage; /** * 進度條SeekBar */ private SeekBar mSeekBar; /** * 顯示進度的文字 */ private TextView mProgressTv; /** * 透明度 */ private int mAlpha; /** * 原始圖片 */ private Bitmap mTempBitmap; /** * 模糊後的圖片 */ private Bitmap mFinalBitmap; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化檢視 initViews(); // 獲取圖片 mTempBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dayu); mFinalBitmap = BlurBitmap.blur(this, mTempBitmap); // 填充模糊後的影象和原圖 mBluredImage.setImageBitmap(mFinalBitmap); mOriginImg.setImageBitmap(mTempBitmap); // 處理seekbar滑動事件 setSeekBar(); } /** * 初始化檢視 */ private void initViews() { mBluredImage = (ImageView) findViewById(R.id.activity_main_blured_img); mOriginImg = (ImageView) findViewById(R.id.activity_main_origin_img); mSeekBar = (SeekBar) findViewById(R.id.activity_main_seekbar); mProgressTv = (TextView) findViewById(R.id.activity_main_progress_tv); } /** * 處理seekbar滑動事件 */ private void setSeekBar() { mSeekBar.setMax(100); mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { mAlpha = progress; mOriginImg.setAlpha((int) (255 - mAlpha * 2.55)); mProgressTv.setText(String.valueOf(mAlpha)); } public void onStartTrackingTouch(SeekBar seekBar) { } public void onStopTrackingTouch(SeekBar seekBar) { } }); } } |
xml佈局檔案程式碼如下:
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 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:layout_width="match_parent" android:layout_weight="1" android:layout_height="0dp"> <ImageView android:id="@+id/activity_main_blured_img" android:scaleType="centerCrop" android:src="@drawable/dayu" android:layout_width="match_parent" android:layout_height="match_parent"/> <ImageView android:id="@+id/activity_main_origin_img" android:scaleType="centerCrop" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="80dp"> <SeekBar android:layout_marginTop="@dimen/activity_vertical_margin" android:id="@+id/activity_main_seekbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginRight="16dp"/> <TextView android:id="@+id/activity_main_progress_tv" android:text="0" android:textSize="24sp" android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout> </LinearLayout> |
效果如下:
怎麼樣?是不是很簡單的樣子?只需要呼叫模糊處理方法,並在SeekBar的滑動監聽裡面呼叫原影象的setAlpha()
方法,來實現動態模糊效果。
你以為這樣就完了?不不不,我們的目的並不是這麼單純,哦,不對,並不是這麼簡單。還記得文章開頭的時候說了嗎?我們的終極目的是要簡單地模仿一下雅虎天氣的介面效果。
仿雅虎天氣介面
有了上面的基礎,就可以很容易地模仿雅虎天氣的介面效果。簡單來說,在上面製作出的效果基礎上,有以下兩點需要注意的地方:
- 需要要監聽滑動事件,然後再將背景圖片呼叫
setTop()
方法,將圖片向上平移一段距離。 - 要向上平移圖片,還需要手動增加圖片的高度,不然圖片向上平移後,底部就會有留白。設定圖片高度的核心程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); Point point = new Point(); display.getSize(point); // 獲取到ImageView的高度 int height = point.y; ViewGroup.LayoutParams params = imageView.getLayoutParams(); params.width = ViewGroup.LayoutParams.MATCH_PARENT; // 將ImageView的高度增加100 params.height = height + 100; // 應用更改設定 imageView.requestLayout(); |
完成上面兩點的內容後,基本就可以模仿出雅虎天氣的首頁了。
結合第一個例子的demo,效果如下:
相關程式碼已上傳至Github:BlurredView,歡迎Star,Fork。
本著不重複造輪子的原則,我將模糊影象的程式碼上傳至JCenter,大家只需要在專案中新增Gradle引用就行了,具體使用方法請參考上面Github倉庫的ReadMe。