实现目标
相信大家都知道毛玻璃效果是怎样的,也可以说是高斯模糊效果。效果图如下:
这是一个透明,且会对背景进行高斯模糊的效果,看起来就像是毛玻璃一样,其实不光是侧滑菜单,只要是view,理论上都可以实现这样的效果,接下来我们就来实现这个效果。
我使用的android studio,所以要创建这样一个带侧滑菜单的项目非常简单,在新建项目的步骤中,执行到这一步,选择Navigation Drawer Activity就可以了:
android studio会自动创建带有这种侧滑覆盖当前Activity菜单的项目,如果是Eclipse,就得自己去实现了,因为重点不在侧滑菜单上,就不说侧滑菜单的实现了,可以去下面的源码地址看看源码,就能够实现了。
实现之前,得先确认,要通过什么样的方式来实现这个效果。
因为Drawer侧滑菜单背后的View实时都在变动,所以只能将View的图实时的截取下来,进行高斯模糊,再作为Drawer的背景。
这个操作只能在Drawer进行绘制之前完成,因为如果在绘制的时候再进行截取和处理,不仅会卡顿,而且截取的图像不会是现在所需要的。
好在Android中有这样一个事件,叫PreDraw,故名思议,就是绘制之前调用的事件,Android的View在绘制的时候,会首先绘制下一层,那么,拿到View的层级绘制的观察者,当绘制之前,给需要截图的View进行截图然后高斯模糊,设置为Drawer的背景,这样就实现了我们需要的功能。
也许我没有表达清楚,不过没关系,看下面的代码解析,就能够明白了。
首先,需要拿到这个侧滑菜单的控件。android studio生成的侧滑菜单是用Fragment实现的,所以使用如下的代码就能够获取到它:
- mNavigationDrawerFragment = (NavigationDrawerFragment)
- getSupportFragmentManager().findFragmentById(R.id.navigation_drawer);
然后需要获取到这个Fragment中的View,获取View的观察者并且注册PreDraw监听事件:
- mNavigationDrawerFragment.getView().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- });
首先找到根控件,并创建一个Bitmap对象来保存截图。
对view初始化后,开始对view进行截图,对view截图的方式如下:
- private FrameLayout view;
- private Bitmap bitmap;
- view.buildDrawingCache();
- bitmap = view.getDrawingCache();
- public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) {
- // 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;
- if (canReuseInBitmap) {
- bitmap = sentBitmap;
- } else {
- 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);
- }
好了,有了处理好的图片,就可以设置到Drawer的背景里了。
。。。
真的吗?
仔细一想,当然不对,现在截的图是整个view的图,而我们的Drawer是慢慢侧滑出来的,随时都可能停止,所以,使用完整的图肯定是不行的。
那么就需要计算出截取view的图片的位置,和设置到Drawer的位置,具体可以参考下图:
从图上可以看的出来,其中需要的参数都是有联系的,也就是说,只要能得到其中的一个参数,其他的就能同时拿到了。
Drawer的宽度可以定死,例子中定的是240,现在就只需要获取到侧滑到哪一个位置就能够算出所有的参数。
在View中,有这样一个方法,叫做getLocationInWindow,可以获取到当前View在整个Window中的位置,可以想象的到,获取到的肯定是一个负数,通过Drawer的宽度和这个负数,可以很简单的算出来显示的宽度。(View中除了getLocationInWindow,还有很多其他的方法,获取相对于各种视图的位置,大家可以研究一下)。
首先获取到位置:
blur函数就是我们处理位置的细节了,传的listView是Fragment里的唯一一个view:
- int[] location = new int[2];
- mNavigationDrawerFragment.getView().getLocationInWindow(location);
- blur(bitmap, listView, location[0]);//只传x坐标
- private void blur(Bitmap bkg, View view,int width) {
- float scaleFactor = 4;//缩放图片,缩放之后模糊效果更好
- float radius = 2;
- 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()/scaleFactor, -view.getTop()/scaleFactor);
- canvas.scale(1 / scaleFactor, 1 / scaleFactor);
- Paint paint = new Paint();
- paint.setFlags(Paint.FILTER_BITMAP_FLAG);
- float visibleWidth = slideMenuWidth +width;//可见宽
- int visibleHeight = view.getHeight();//可见高
- //从view的截图中截取的区域,+10和下面-10的原因是,高斯模糊的边有时会有黑影,所以增大模糊区域
- t src = new Rect(0,0, (int)(visibleWidth)+10, visibleHeight);
- RectF dest = new RectF(-width - 10, 0, slideMenuWidth, visibleHeight);//设置Drawer背景的区域
- canvas.drawBitmap(bkg, src, dest, paint);
- overlay = ImageUtils.doBlur(overlay, (int)radius, true);//进行高斯模糊操作
- if (Build.VERSION.SDK_INT < 16) {//16level以前使用这个方法,在16中被废弃
- view.setBackgroundDrawable(new BitmapDrawable(getResources(), overlay));
- } else {
- view.setBackground(new BitmapDrawable(getResources(), overlay));
- }
- }
- mNavigationDrawerFragment.getView().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- if (bitmap == null) {
- view.buildDrawingCache();
- bitmap = view.getDrawingCache();
- }
- int[] location = new int[2];
- mNavigationDrawerFragment.getView().getLocationInWindow(location);
- blur(bitmap, listView, location[0]);//只传x坐标
- return true;
- }
- });
如果能够理解我上面使用的方法,我相信大家一定能够举一反三,实现所有view的毛玻璃效果,这个例子的代码,我会放到github上,
这里就是链接地址:https://github.com/xjyaikj/GlassSlideMenuSample,如果代码有什么不对,或者值得优化的地方,非常希望您能跟我交流,我的联系方式在左边个人资料的下面。