模糊透明效果这几年在UI开发中非常常见,最初是Windows在Vista中使用在开始菜单、标题栏和边框上,随后其它的操作系统都纷纷跟进,像Ubuntu等在随后的版本中大量的使用,但在手机上使用也不过是近两三年的事,毕竟手机上有个性能的问题在里面。所谓模糊透明都是通过CPU的运算即时生成的,这其中透明效果还算好处理,很久以前XP就已经使用了这种特效,相比模糊处理要简单许多。而模糊效果需要占用到大量的CPU资源进行计算,如果要对1000个像素进行计算的话,可不是仅仅循环1000次那么简单。因为每一个像素都需要计算出此像素与周围像素之间的平均值,而随着模糊半径的增大计算量也成倍的增大。而手机的CPU速度毕竟不是台式机可比拟的,而且大量的运算对移动设备来说也是非常耗电的,这是Windows在笔记本上默认是关闭此功能的原因。不过既然客户有要求,作为开发人员的我们还是要尽量去满足。来了能实现模糊透明效果我特意写了一个工具类。
/**
* 快速模糊
*
* @param sentBitmap
* @param radius
* @return
*/
public static Bitmap fastBlur(Bitmap sentBitmap, int radius) {
// Stack Blur v1.0 from
// http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
//
// Java Author: Mario Klingemann <mario at quasimondo.com>
// http://incubator.quasimondo.com
// created Feburary 29, 2004
// Android port : Yahel Bouaziz <yahel at kayenko.com>
// http://www.kayenko.com
// ported april 5th, 2012
// This is a compromise between Gaussian Blur and Box blur
// It creates much better looking blurs than Box Blur, but is
// 7x faster than my Gaussian Blur implementation.
//
// I called it Stack Blur because this describes best how this
// filter works internally: it creates a kind of moving stack
// of colors whilst scanning through the image. Thereby it
// just has to add one new block of color to the right side
// of the stack and remove the leftmost color. The remaining
// colors on the topmost layer of the stack are either added on
// or reduced by one, depending on if they are on the right or
// on the left side of the stack.
//
// If you are using this algorithm in your code please add
// the following line:
//
// Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
if (radius < 1) {
return (null);
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] pix = new int[w * h];
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;
int r[] = new int[wh];
int g[] = new int[wh];
int b[] = new int[wh];
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
int vmin[] = new int[Math.max(w, h)];
int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[] = new int[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}
yw = yi = 0;
int[][] stack = new int[div][3];
int stackpointer;
int stackstart;
int[] sir;
int rbs;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;
for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++) {
p = pix[yi + Math.min(wm, Math.max(i, 0))];
sir = stack[i + radius];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
}
p = pix[yw + vmin[x]];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[(stackpointer) % div];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = Math.max(0, yp) + x;
sir = stack[i + radius];
sir[0] = r[yi];
sir[1] = g[yi];
sir[2] = b[yi];
rbs = r1 - Math.abs(i);
rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (x == 0) {
vmin[y] = Math.min(y + r1, hm) * w;
}
p = x + vmin[y];
sir[0] = r[p];
sir[1] = g[p];
sir[2] = b[p];
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi += w;
}
}
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
return (bitmap);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static Bitmap fastblur16(Bitmap source, int radius, Context ctx) {
Bitmap bitmap = source.copy(source.getConfig(), true);
RenderScript rs = RenderScript.create(ctx);
Allocation input = Allocation.createFromBitmap(rs, source, Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
Allocation output = Allocation.createTyped(rs, input.getType());
ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
script.setRadius(radius);
script.setInput(input);
script.forEach(output);
output.copyTo(bitmap);
return bitmap;
}
/**
* 对View控件的背景进行模糊处理
*
* 模糊效果会大量占用CPU资源,不推荐使用在动态界面中,会非常耗电
*
* @param context
* @param bkg
* @param view
* @param radius模糊的半径
* @param scaleFactory缩放因子主要是为了优化算法加快速度
* @param marginLeft
* @param marginTop
*/
@SuppressLint("NewApi")
public static void blurViewBg(Context context, Bitmap bkg, View view, int radius, float scaleFactor,
int marginLeft, int marginTop) {
// 参数的有效性判断
radius = radius < 0 || radius > 25 ? 2 : radius;
scaleFactor = scaleFactor <= 0 ? 1 : scaleFactor;
// 数据单位转换
marginLeft = (int) (ScreenUtils.dp2Px(context, marginLeft));
marginTop = (int) (ScreenUtils.dp2Px(context, marginTop));
// 根据缩放因子创建一个相应比例的位图,从背景位图中截取相应位置的画面到此
Bitmap overlay = Bitmap.createBitmap((int) ((view.getMeasuredWidth()) / scaleFactor),
(int) ((view.getMeasuredHeight()) / scaleFactor), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(overlay);
canvas.translate((-view.getLeft() - marginLeft) / scaleFactor, (-view.getTop() - marginTop) / scaleFactor);
canvas.scale(1 / scaleFactor, 1 / scaleFactor);
Paint paint = new Paint();
paint.setFlags(Paint.FILTER_BITMAP_FLAG);
canvas.drawBitmap(bkg, 0, 0, paint);
// 将创建的位图做模糊处理并做为参数view的背景
overlay = BitmapUtils.fastBlur(overlay, (int) radius);
view.setBackground(new BitmapDrawable(context.getResources(), overlay));
}
<span style="white-space:pre"> </span>/**
<span style="white-space:pre"> </span> * 截取可见屏幕部分的view视图
<span style="white-space:pre"> </span> */
<span style="white-space:pre"> </span>public static Bitmap shotViewBitmap(View v) {
<span style="white-space:pre"> </span>v.clearFocus();
<span style="white-space:pre"> </span>v.setPressed(false);
<span style="white-space:pre"> </span>Bitmap bmp = null;
<span style="white-space:pre"> </span>try {
<span style="white-space:pre"> </span>// 允许当前窗口保存缓存信息,这样getDrawingCache()方法才会返回一个Bitmap
<span style="white-space:pre"> </span>v.setDrawingCacheEnabled(true);
<span style="white-space:pre"> </span>v.buildDrawingCache();
<span style="white-space:pre"> </span>bmp = Bitmap.createBitmap(v.getDrawingCache());
<span style="white-space:pre"> </span>} catch (Exception e) {
<span style="white-space:pre"> </span>e.printStackTrace();
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>return bmp;
<span style="white-space:pre"> </span>}
上面的三个方法都不是我自己写的,是从网上找到后略微修改了下。
fastBlur传入的是一个需要被模糊化的位图,参数radius就是模糊半径,半径不能大于25,返回值已经模糊化后的位置。
fastblur16方法的效果和fastBlur相同,只是使用一个API16才开始有的一些方法,但处理后的模糊效果都是一样的。
blurViewBg方法就用来实现模糊透明的,参数view指出需要对这种效果的控件,bkg指出需要被作为模糊透明的位图,radius为模糊半径,scaleFactor是缩小因子,这个参数是种优化算法,主要是将位图按倍数缩小以减少像素加快计算速度的。如果view控件有marginTop和marginLeft的话也要作为参数传入。
shotViewBitmap方法是对控件进行截图并保存为Bitmap对象。我们需要制作模糊透明效果的话必然是在一个控件后面还要有一个为背景的控件,此方法就是对这个背景控件进行截图并将返回的Bitmap作为参数传给blurViewBg方法。注意如果是把一个布局对象传给此方法的话是会报错的。
好了!下面给出如果调用这些工具类的方法演示
package com.androidcustomlibrarydemo.fragment;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.android.common.utils.BitmapUtils;
import com.androidcustomlibrarydemo.R;
import com.androidcustomlibrarydemo.adapter.ItemAdapter;
public class BlurViewFragment extends Fragment {
private View view;
private ListView listView;
private ImageView imgView;
private TextView tvBottom;
private Bitmap bg;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_blur, container, false);
initViewController();
return view;
}
private void initViewController() {
imgView = (ImageView) view.findViewById(R.id.img_view);
listView = (ListView) view.findViewById(R.id.listView1);
tvBottom = (TextView) view.findViewById(R.id.textView1);
tvBottom.setText("模糊化处理是一种非常耗资源的处理,对于电量和CPU的使用率较高,因此不建议在界面中大量的使用,尤其是在列表项的背景中动态的模糊会出现界面卡顿现象,此界面已做了一定的优化");
// 因为需要获取ImageView里的图像所以在此调用控件的观察者,并添加监听器以便于当控件加载完毕做相应的处理
imgView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (bg == null) {
// 首先对ImageView进行截图并保存为位图
bg = BitmapUtils.shotViewBitmap(imgView);
// 对底部的TextView的背景进行模糊化处理
// 参数中的2代表模糊半径,4代表缩小因子
BitmapUtils.blurViewBg(getActivity(), bg, tvBottom, 2, 4, 0, 0);
// 对背景图片进行模糊,并将背景图片传给列表的适配器
bg = BitmapUtils.fastBlur(bg, 10);
listView.setAdapter(new ItemAdapter(BlurViewFragment.this.getActivity(), bg));
// 移除ImageView的观察器,如果不移除的话控件会在每次重绘时频繁的回调onPreDraw方法
imgView.getViewTreeObserver().removeOnPreDrawListener(this);
}
return true;
}
});
}
}
fragment_blur.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/RelativeLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.blurdemo.MainActivity" >
<ImageView
android:id="@+id/img_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:scaleType="fitXY"
android:src="@drawable/act_bg" />
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:padding="10dp"
android:text="TextView"
android:textColor="#fff" />
<LinearLayout
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/textView1"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:orientation="horizontal" >
<View
android:layout_width="50dp"
android:layout_height="match_parent" />
<ListView
android:id="@+id/listView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:background="@null"
android:dividerHeight="10dp" >
</ListView>
<View
android:layout_width="50dp"
android:layout_height="match_parent" />
</LinearLayout>
</RelativeLayout>
ItemAdapter.java
package com.androidcustomlibrarydemo.adapter;
import com.android.common.utils.BitmapUtils;
import com.android.common.utils.ScreenUtils;
import com.androidcustomlibrarydemo.R;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.widget.BaseAdapter;
public class ItemAdapter extends BaseAdapter {
private Context context;
private Bitmap bg;
public ItemAdapter(Context context, Bitmap bg) {
this.context = context;
this.bg = bg;
}
@Override
public int getCount() {
return 110;
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.list_item_blur, null);
final View view = convertView;
view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
// 不建议在列表的适配器中调用blurViewBg方法,因为无论在性能还是在界面效果上都不理想
// 因此参数bg是直接处理已经模糊好的背景位图,在此只是根据列表的每一项的位置坐标进行截取显示
showViewBg(bg, view, 0, 10, 10, 50, 0);
// 在列表的适配中不能移除观察者的监听
return true;
}
});
}
return convertView;
}
@SuppressLint("NewApi")
public void showViewBg(Bitmap bkg, View view, int radius, int marginLeft, int marginTop, int offx, int offy) {
radius = radius < 0 || radius > 25 ? 2 : radius;
marginLeft = (int) (ScreenUtils.dp2Px(context, marginLeft));
marginTop = (int) (ScreenUtils.dp2Px(context, marginTop));
offx = (int) ScreenUtils.dp2Px(context, offx);
offy = (int) ScreenUtils.dp2Px(context, offy);
Bitmap overlay = Bitmap
.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(overlay);
canvas.translate((-view.getLeft() - marginLeft - offx), (-view.getTop() - marginTop - offy));
Paint paint = new Paint();
paint.setFlags(Paint.FILTER_BITMAP_FLAG);
canvas.drawBitmap(bkg, 0, 0, paint);
view.setBackground(new BitmapDrawable(context.getResources(), overlay));
}
}
list_item_blur.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textColor="#fff" />
</LinearLayout>
最后看一下效果图