1. 程式
拖動紅色地區,可以顯示出清晰的汽車部分。拖動下面的滑塊,可以更改模糊程度。
2. 程式實現方法
實現思路,用FrameLayout搞了三層,最底下一層是清晰的圖片,中間一層是模糊的圖片,最上面的一層,是紅色地區,這一層是清晰的圖片。
public static class PlaceholderFragment extends Fragment { // 新版android adt-bundle預設在activity中帶一個fragment,據說android stdio早就這樣了private ImageView mOriginIv;private ImageView mBlurIv;private ImageView mClearIv;private SeekBar mRadiusSb;public PlaceholderFragment() {}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {View rootView = inflater.inflate(R.layout.fragment_main, container,false);return rootView;}@Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);mOriginIv = (ImageView) getActivity().findViewById(R.id.origin_image);mBlurIv = (ImageView) getActivity().findViewById(R.id.blur_image);mClearIv = (ImageView) getActivity().findViewById(R.id.clear_image);mRadiusSb = (SeekBar) getActivity().findViewById(R.id.radius_seekbar);drawBlurImage(); // 初始化模糊層。setSeekBarChangeListen(); // 設定SeekBar回調,滑塊位置變化的時候,更新模糊層。 // 延遲是為了保證view.getX,view.getWidth 這類方法能夠去到數值,這裡只是為了初始化,所以順延強制比較好。 // 如果要是每次可視化的時候,都要讀weidht和x,那麼可以再在Activity#onWindowFocusChange中調用。Runnable runnable = new Runnable() {@Overridepublic void run() {OnMoveListener listener = new OnMoveListener() {@Overridepublic void onMoved() {mOriginIv.buildDrawingCache();clear(mOriginIv.getDrawingCache(), mClearIv, 10); // 這是拿到View繪製映像的好辦法}};MoveUtils.enableMove(mClearIv, listener);}};mClearIv.postDelayed(runnable, 500);}private void drawBlurImage() {mOriginIv.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {@Overridepublic boolean onPreDraw() {mOriginIv.getViewTreeObserver().removeOnPreDrawListener(this);mOriginIv.buildDrawingCache();float radius = mRadiusSb.getProgress();if (radius < 0.1) { // RenderScript要求radius必須在0和25之間,不能等於radius = 0.1f;}if (radius > 24.9) {radius = 24.9f;}blur(mOriginIv.getDrawingCache(), mBlurIv, radius);clear(mOriginIv.getDrawingCache(), mClearIv, 10); // 這裡為了顯示邊框,偷懶了直接用了10px,實際上是5dip,在My Phonegalaxy nexus上,1dip=2px,實際上應該換算一下的。return true; // 這個是參考文章中要求的,沒試過false。}});}private void setSeekBarChangeListen() {mRadiusSb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {@Overridepublic void onStopTrackingTouch(SeekBar arg0) {}@Overridepublic void onStartTrackingTouch(SeekBar arg0) {}@Overridepublic void onProgressChanged(SeekBar arg0, int arg1,boolean arg2) {drawBlurImage();}});}// 首先根據view的大小,從bkg產生一個剪裁後的映像;然後根據radius,將剪裁後的映像模糊處理;最後將模糊處理的映像設定到view上。 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)private void blur(Bitmap bkg, View view, float radius) { // 剪裁圖片的過程Bitmap overlay = Bitmap.createBitmap((int) (view.getMeasuredWidth()),(int) (view.getMeasuredHeight()), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(overlay);canvas.translate(-view.getX(), -view.getY()); // 這裡是設定座標系原點canvas.drawBitmap(bkg, 0, 0, null); // // 這裡直接在新的座標系的原點上繪製映像,如果不設定座標系的話,相當於在(view.getX(),view.getY)上繪製映像,android向右是x軸正方形,向下時y軸正方向。 // 模糊圖片的過程RenderScript rs = RenderScript.create(getActivity()); // RenderScript要求apilevel 17,這個比較噁心,v8支援包也不是特別好用,真的要搞模糊的話,還是opencv jni來搞吧。Allocation overlayAlloc = Allocation.createFromBitmap(rs, overlay);ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs,overlayAlloc.getElement());blur.setInput(overlayAlloc);blur.setRadius(radius);blur.forEach(overlayAlloc);overlayAlloc.copyTo(overlay); // 設定圖片view.setBackground(new BitmapDrawable(getResources(), overlay));rs.destroy();} // 首先根據view的大小,從bkg產生一個剪裁後的映像;然後將剪裁後的映像設定到view上。private void clear(Bitmap bkg, ImageView view, int paddingPx) {Bitmap overlay = Bitmap.createBitmap((int) (view.getMeasuredWidth() - paddingPx * 2),(int) (view.getMeasuredHeight() - paddingPx * 2),Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(overlay);canvas.translate(-view.getX() - paddingPx, -view.getY() - paddingPx);canvas.drawBitmap(bkg, 0, 0, null);view.setImageDrawable(new BitmapDrawable(getResources(), overlay));}}
3. 代碼下載
萬惡的CSDN上傳了代碼,好幾個小時了還沒審核完。。。注意代碼的minsdk我設定的比較高,是API Level17,沒辦法,RenderScript的支援庫沒搞定。
4. 幾個問題
RenderScript 雖然有support-v8支援庫,但是我搞了會,也沒編譯成功。也看到有文章說在2.3.5上RenderScript有問題的。所以感覺不是特別靠譜,還是jni+opencv自己搞起來比較好,網上opencv相關的模糊演算法很多。另外如果映像很大,模糊處理比較耗時,最好是非同步進行。
getWidth,getHeight,getLeft的調用時機 onStart、onReusme這些都不行,只能在onWindowFoucsChange。本文的樣本是初始化的時候調用,所以可以延遲一會執行,如果要是每次從後台切換到前台,就要調用的話,那麼要在onWindowFoucsChange中調用。
使用canvas剪裁bitmap 注意座標系,android向右是x軸正方形,向下時y軸正方向。
private void clear(Bitmap bkg, ImageView view, int paddingPx) {Bitmap overlay = Bitmap.createBitmap((int) (view.getMeasuredWidth() - paddingPx * 2),(int) (view.getMeasuredHeight() - paddingPx * 2),Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(overlay);canvas.translate(-view.getX() - paddingPx, -view.getY() - paddingPx);canvas.drawBitmap(bkg, 0, 0, null);view.setImageDrawable(new BitmapDrawable(getResources(), overlay));}
擷取圖片繪製緩衝
mOriginIv.buildDrawingCache();clear(mOriginIv.getDrawingCache(), mClearIv, 10);
讓View可以拖動
寫了一個簡單的方法,通過View的Tag+onTouchEvent,實現View可以拖動。
package com.example.blurtest;import android.view.MotionEvent;import android.view.View;import android.view.View.OnTouchListener;public class MoveUtils {private static final int STATE_IDLE = 0;private static final int STATE_MOVING = 1;private static final int MIN_GAP = 5;private static class Info {public int state = STATE_IDLE;public float lastX = -1;public float lastY = -1;public OnMoveListener listener;}private static Info getInfo(View view) {if (view.getTag() == null) {view.setTag(new Info());}return (Info) (view.getTag());}private static void tryToMove(View view, MotionEvent ev) {Info info = getInfo(view);if (info.state != STATE_MOVING) {return;}float x = ev.getX() - info.lastX;float y = ev.getY() - info.lastY;if (Math.abs(x) < MIN_GAP && Math.abs(y) < MIN_GAP) {return;}view.setX(view.getX() + x);view.setY(view.getY() + y);view.invalidate();info.listener.onMoved();}public static void enableMove(View target, OnMoveListener listener) {Info info = new Info();info.listener = listener;target.setTag(info);target.setOnTouchListener(new OnTouchListener() {@Overridepublic boolean onTouch(View view, MotionEvent ev) {Info info = getInfo(view);int action = ev.getAction() & MotionEvent.ACTION_MASK;switch (action) {case MotionEvent.ACTION_DOWN:info.state = STATE_MOVING;info.lastX = ev.getX();info.lastY = ev.getY();view.setTag(info);break;case MotionEvent.ACTION_MOVE:tryToMove(view, ev);break;case MotionEvent.ACTION_UP:info.state = STATE_IDLE;info.lastX = ev.getX();info.lastY = ev.getY();view.setTag(info);default:break;}return true;}});}public static interface OnMoveListener {public void onMoved();}}