9

How would I go about create a live blur on a Navigation Drawer, so when you pull out the drawer the background of the list is blurred background of the fragment displayed behind it?

I have looked but mainly found BitMap blurs, and nothing that is live.

SquiresSquire
  • 2,404
  • 4
  • 23
  • 39
  • Do you really want to blur the background and not just make it semitransparent? Blur is an expensive operation and doing it on the fly without tricks might be too costly! – LukaCiko Feb 17 '14 at 23:03
  • 1
    Could you just create a transparent .png image with some blur on it? And then set the drawer background equal to that? – Phoenix Feb 17 '14 at 23:05
  • You can't use a blur .png because to create a blur it needs a image to sample and then blur – SquiresSquire Feb 24 '14 at 21:54

1 Answers1

14

The process involves the following stages:

  1. Get a snapshot of the whole drawer layout.
  2. Crop it to the exact size of your menu.
  3. Downscale it quite a bit (a factor of 8 is pretty good).
  4. Blur that image.
  5. Have a custom view that displays part of an image right behind of where your menu will appear.
  6. As you slide the drawer show a larger part of that image.

Here's a code sample (it's all done in the BlurActionBarDrawerToggle and the BlurAndDimView):

public class BlurActionBarDrawerToggle extends ActionBarDrawerToggle {

    private static final int DOWNSAMPLING = 8;

    private final DrawerLayout drawerLayout;
    private final BlurAndDimView blurrer;
    private Bitmap drawerSnapshot;
    private final ColorDrawable imageBackgroundDrawable;

    public BlurActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, int drawerImageRes, int openDrawerContentDescRes, int closeDrawerContentDescRes, BlurAndDimView blurrer) {
        super(activity, drawerLayout, drawerImageRes, openDrawerContentDescRes, closeDrawerContentDescRes);

        //this should be roughly the same color as your window background
        imageBackgroundDrawable = new ColorDrawable(activity.getResources().getColor(R.color.dark_blue_2));
        this.drawerLayout = drawerLayout;
        this.blurrer = blurrer;
    }

    @Override
    public void onDrawerSlide(final View drawerView, final float slideOffset) {
        super.onDrawerSlide(drawerView, slideOffset);
        if (slideOffset > 0.0f) {
            setBlurAlpha(slideOffset);
        } else {
            clearBlurImage();
        }
    }

    @Override
    public void onDrawerClosed(View view) {
        clearBlurImage();
    }

    private void setBlurAlpha(float slideOffset) {
        if (!blurrer.hasImage()) {
            setBlurImage();
        }
        blurrer.handleScroll(slideOffset);
    }

    public void setBlurImage() {
        blurrer.setVisibility(View.VISIBLE);
        drawerSnapshot = drawViewToBitmap(drawerSnapshot, drawerLayout, DOWNSAMPLING, imageBackgroundDrawable);
        blurrer.setImage(drawerSnapshot, DOWNSAMPLING);
    }

    public void clearBlurImage() {
        blurrer.clearImage();
        blurrer.setVisibility(View.INVISIBLE);
    }

    private Bitmap drawViewToBitmap(Bitmap dest, View view, int downSampling, Drawable background) {
        float scale = 1f / downSampling;
        int viewWidth = view.getWidth();
        int viewHeight = view.getHeight();
        int bmpWidth = (int) (viewWidth * scale);
        int bmpHeight = (int) (viewHeight * scale);
        if (dest == null || dest.getWidth() != bmpWidth || dest.getHeight() != bmpHeight) {
            dest = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
        }
        Canvas c = new Canvas(dest);
        background.setBounds(new Rect(0, 0, viewWidth, viewHeight));
        background.draw(c);
        if (downSampling > 1) {
            c.scale(scale, scale);
        }

        view.draw(c);
        view.layout(0, 0, viewWidth, viewHeight);
        return dest;
    }
}

The BlurAndDim view Blurs the part below the menu, and dims the rest (you can tweak the numbers to get the exact effect that you want):

public class BlurAndDimView extends View {

    private static final int BLUR_RADIUS = 4;
    private static final int BLUE_ALPHA = 178;
    private static final int MAX_DIM_ALPHA = 127;
    private Paint bitmapPaint;
    private Paint dimPaint;
    private Paint blueSemiTransparentPaint;
    private int menuWidth;
    private int titleHeight;

    private Bitmap image;
    private Rect rectDst;
    private Rect rectSrc;
    private int downSampling;
    private Rect rectRest;

    public BlurAndDimView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        postConstruct();
    }

    public BlurAndDimView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        postConstruct();
    }

    public BlurAndDimView(Context context) {
        this(context, null);
        postConstruct();
    }

    private void postConstruct() {
        bitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        dimPaint = new Paint();
        blueSemiTransparentPaint = new Paint();
        //You might want to also have a semitransparent overlay over your blurred image, since you can't control what's behind your menu.
        blueSemiTransparentPaint.setColor(getResources().getColor(R.color.dark_blue));
        blueSemiTransparentPaint.setAlpha(BLUE_ALPHA);
        menuWidth = getResources().getDimensionPixelSize(R.dimen.browse_menu_width);
    }
}

    @Override
    protected void onDraw(Canvas canvas) {
        if (image != null) {
            canvas.drawBitmap(image, rectSrc, rectDst, bitmapPaint);
            canvas.drawRect(rectDst, blueSemiTransparentPaint);
            canvas.drawRect(rectRest, dimPaint);
        }
    }

    public void handleScroll(float xOffset) {
        if(image != null) {
            rectSrc.right = (int) (image.getWidth() * xOffset);
            rectDst.right = rectSrc.right * downSampling;
            rectRest.left = rectDst.right;
            dimPaint.setAlpha((int) (xOffset * MAX_DIM_ALPHA));
            invalidate();
        }
    }

    public void setImage(Bitmap bmp, int downSampling) {
        Bitmap cropped = Bitmap.createBitmap(bmp, 0, 0, menuWidth / downSampling, getHeight() / downSampling);
        this.image = Blur.blur(getContext(), cropped, BLUR_RADIUS);
        rectSrc = new Rect(0, 0, bmp.getWidth(), bmp.getHeight());
        rectDst = new Rect(0, 0, menuWidth, getHeight());
        rectRest = new Rect(menuWidth, 0, getWidth(), getHeight());
        this.downSampling = downSampling;
        invalidate();
    }

    public boolean hasImage() {
        return image != null;
    }

    public void clearImage() {
        image = null;
    }
}

For a good blur algorithm (that uses Renderscript where possible), you can use this.

The Layout would be something like this:

<android.support.v4.widget.DrawerLayout
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <android.support.v4.widget.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.bla.bla.BlurAndDimView
        android:id="@+id/blurrer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="?android:attr/actionBarSize"
        android:visibility="invisible" />

    <ListView
        android:id="@+id/menu_list"
        android:layout_width="@dimen/browse_menu_width"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:choiceMode="singleChoice" />
</android.support.v4.widget.DrawerLayout>
janakagamini
  • 237
  • 3
  • 11
Tas Morf
  • 3,065
  • 1
  • 12
  • 8
  • Keep in mind, depending on how much downscaling/blurring you do, this might take longer than 40-50ms on some devices. But, if you stick to the numbers I gave, you should be able to use this in most cases without any problems. – Tas Morf Mar 06 '14 at 16:12
  • i tried this one. but unable to show drawer. how can i use it ( open drawer ) from MainActivity? – Ajay Aug 13 '14 at 07:24
  • Where did you get "Blur.blur" can you specify the library that you use or the class? thanks – Cjames Sep 10 '14 at 10:47
  • I've already provided the link in the answer: https://github.com/PomepuyN/BlurEffectForAndroidDesign/blob/master/BlurEffect/src/com/npi/blureffect/Blur.java – Tas Morf Sep 11 '14 at 12:23
  • How to use this with Navigation Drawer opening from Right? – Sanjeet A Oct 27 '15 at 07:56
  • Set the menu Listview gravity to end, and then make sure the Rect's in the BlurAndDimView are starting from the right. There should be enough code in my original answer to figure this out for yourself. – Tas Morf Oct 27 '15 at 12:17
  • A query, is there an explanation of how to use this or implement this in the Drawable view by default in Android studio? – Carlos Quintero Dec 03 '18 at 21:52