本文链接 http://blog.csdn.net/xiaodongrush/article/details/31031411
参考链接 Android高级模糊技术 http://stackoverflow.com/questions/14879439/renderscript-via-the-support-library
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() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); return rootView; } @Override public 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() { @Override public void run() { OnMoveListener listener = new OnMoveListener() { @Override public 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() { @Override public 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,在我的手机galaxy nexus上,1dip=2px,实际上应该换算一下的。 return true; // 这个是参考文章中要求的,没试过false。 } }); } private void setSeekBarChangeListen() { mRadiusSb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar arg0) { } @Override public void onStartTrackingTouch(SeekBar arg0) { } @Override public 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上传了代码,好几个小时了还没审核完。。。http://download.csdn.net/detail/u011267546/7502603 注意代码的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() { @Override public 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(); }}