How can I dim the background when Bottomsheet is displayed, without using Dialog?

AndroidBottom Sheet

Android Problem Overview


I know BottomSheetDialog does so already, but I need to use the regular BottomSheet and behavior generated from BottomSheetBehavior.from(). This BottomSheet doesn't dim the background and also touch outside would not close it. Is there a way to dim the background when BottomSheet is displayed? and maybe dismiss when touch outside. Basically the behavior just like BottomSheetDialog but I must use BottomSheet BottomSheetBehavior directly.

Android Solutions


Solution 1 - Android

You can use this code

  1. MainActivity.xml

    http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/main_content" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true">

     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical"
         android:paddingTop="24dp">
    
         <Button
             android:id="@+id/button_1"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="Button 1"
             android:padding="16dp"
             android:layout_margin="8dp"
             android:textColor="@android:color/white"
             android:background="@android:color/holo_green_dark"/>
         
     </LinearLayout>
    

     <TextView
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:text="aefwea"
         android:padding="16dp"
         android:textSize="16sp"/>
    

  2. MAinActivity.java

     public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
     private static final String TAG = "MainActivity";
     private BottomSheetBehavior mBottomSheetBehavior;
     View bottomSheet;
     View mViewBg;
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         bottomSheet = findViewById(R.id.bottom_sheet);
         mViewBg = findViewById(R.id.mViewBg);
         Button button1 = (Button) findViewById(R.id.button_1);
         button1.setOnClickListener(this);
         mViewBg.setOnClickListener(this);
         mBottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
         mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
             @Override
             public void onStateChanged(@NonNull View bottomSheet, int newState) {
                 if (newState == BottomSheetBehavior.STATE_COLLAPSED)
                     mViewBg.setVisibility(View.GONE);
             }
    
             @Override
             public void onSlide(@NonNull View bottomSheet, float slideOffset) {
                 Log.d(TAG, "onSlide: slideOffset" + slideOffset + "");
                mViewBg.setVisibility(View.VISIBLE);
                 mViewBg.setAlpha(slideOffset);
             }
         });
    
     }
    
    
     @Override
     public void onClick(View v) {
         switch (v.getId()) {
             case R.id.button_1: {
                 mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                 break;
             }
             case R.id.bg: {
                 mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
                 break;
             }
         }
     }
    
    
     @Override
     public boolean dispatchTouchEvent(MotionEvent event) {
         if (event.getAction() == MotionEvent.ACTION_DOWN) {
             if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
                 Rect outRect = new Rect();
                 bottomSheet.getGlobalVisibleRect(outRect);
                 if (!outRect.contains((int) event.getRawX(), (int) event.getRawY())) {
                     mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
                     return true;
                 }
    
             }
         }
         return super.dispatchTouchEvent(event);
     }
    
     }
    

Solution 2 - Android

You can create a custom fragment that has layout (kind of bottomSheet) attached to bottom and make the background transparent_black & when touching on that BG remove that fragment. Ex :

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff2020"
    android:orientation="vertical"
    tools:context="com.example.jiffysoftwaresolutions.copypastesampleapp.MainActivity">

    <Button
        android:id="@+id/show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Show" />

    <FrameLayout
        android:id="@+id/bottom_sheet_fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></FrameLayout>

</RelativeLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private BottomSheetFragment bottomSheetFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.show).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if (bottomSheetFragment == null) {
                    bottomSheetFragment = new BottomSheetFragment();
                }
                getSupportFragmentManager().beginTransaction().add(R.id.bottom_sheet_fragment_container, bottomSheetFragment).addToBackStack(null).commit();

            }
        });

    }


    public void removeBottomSheet() {
        try {
            getSupportFragmentManager().beginTransaction().remove(bottomSheetFragment).addToBackStack(null).commit();
        } catch (Exception e) {
        }
    }

}

BottomSheetFragment.java

public class BottomSheetFragment extends Fragment {


    private View rootView;
    private LayoutInflater layoutInflater;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        layoutInflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    }


    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        rootView = inflater.inflate(R.layout.bottom_sheet_layout, container, false);
        rootView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // remove sheet on BG touch
                ((MainActivity) getActivity()).removeBottomSheet();
            }
        });
        return rootView;
    }

}

bottom_sheet_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#6d000000"
    android:gravity="bottom">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#fff"
        android:orientation="vertical"
        android:padding="5dp">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Button1"
            android:textColor="#000" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Button2"
            android:textColor="#000" />

    </LinearLayout>

</RelativeLayout>

To Add that fragment with bottom_top/animation.. you can follow this link : https://stackoverflow.com/questions/4817900/android-fragments-and-animation

Solution 3 - Android

Using the Interface - onSlide which as a parameter slideOffSet of type float , can be used to dim the background. The new offset of this bottom sheet within [-1,1] range. Offset increases as this bottom sheet is moving upward. From 0 to 1 the sheet is between collapsed and expanded states and from -1 to 0 it is between hidden and collapsed states.

if ( slideOffSet>=0 && slideOffSet<=1 ) {
    mainActivityLayoutView.setAlpha( 1f - slideOffSet ); 
}

where mainActivityLayoutView is the id for NestedScrollView , holding the main contents of the activity.

Solution 4 - Android

You can use my concept if you want that i used in https://stackoverflow.com/q/40421723/5188159

My approach

  1. Take a screen shot
  2. Programtically animate dim/blur screen shot
  3. Get currant window using a dialog witch does not have any content
  4. Attach screen shot with effect
  5. Display real view i wanted to display

Here i have a class for take an image of the background as a bitmap

public class AppUtils {

    public static Bitmap takeScreenShot(Activity activity) {
        View view = activity.getWindow().getDecorView();
        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();


        Bitmap b1 = view.getDrawingCache();
        Rect frame = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
        int statusBarHeight = frame.top;

        Display display = activity.getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        int width = size.x;
        int height = size.y;


        Bitmap b = Bitmap.createBitmap(b1, 0, statusBarHeight, width, height - statusBarHeight);
        view.destroyDrawingCache();
        return b;
    }
}

Congrats now you have a darken/dim image same as your background

Then your requirement is to dim not blur like mine so you can pass this bitmap to below method,

public static Bitmap changeBitmapContrastBrightness(Bitmap bmp, float contrast, float brightness) {
        ColorMatrix cm = new ColorMatrix(new float[]
                {
                        contrast, 0, 0, 0, brightness,
                        0, contrast, 0, 0, brightness,
                        0, 0, contrast, 0, brightness,
                        0, 0, 0, 1, 0
                });

        Bitmap ret = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig());

        Canvas canvas = new Canvas(ret);

        Paint paint = new Paint();
        paint.setColorFilter(new ColorMatrixColorFilter(cm));
        canvas.drawBitmap(bmp, 0, 0, paint);

        return ret;
    }

Now use a fake dialog / dialog withOut a background/content only to get the window (Check my questions implementation you can understand that )

Window window = fakeDialogUseToGetWindowForBlurEffect.getWindow();
window.setBackgroundDrawable(draw);  // draw is bitmap that you created 

after this you can show your real view.In my case i display an alert you can display whatever your view and remember to remove/gone that alert when your real view goes out from the screen!

Quick out put : (background can be customized as you want,not only dim)

enter image description here

Solution 5 - Android

Use this style and apply to your dialog.

> PS: this style also works perfectly in Android 6.0, 6.1 and 7.0.

<style name="MaterialDialogSheet" parent="@android:style/Theme.Dialog">
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:backgroundDimEnabled">true</item>
        <item name="android:windowIsFloating">false</item>
        <item name="android:windowAnimationStyle">@style/MaterialDialogSheetAnimation</item>
    </style>
<style name="MaterialDialogSheetAnimation">
        <item name="android:windowEnterAnimation">@anim/popup_show</item>
        <item name="android:windowExitAnimation">@anim/popup_hide</item>
    </style>

And use as:

final Dialog mBottomSheetDialog = new Dialog(mActivity, R.style.MaterialDialogSheet);

Thanks.

Solution 6 - Android

In case the view implementing the BottomSheetBehavior is inside a distinct activity you can use the following solution

For making the background of your activity transparent + dimmed add following style to your activity

<style name="Your.Transparent.Style">
    <!-- Found no solution for setting the status bar color to fully transparent 
         from within the style. Had to resort to programmatically setting -->
    <item name="android:windowBackground">@color/transparent</item>
    <item name="android:backgroundDimEnabled">true</item>
</style>

Inside your AndroidManifest.xml set the activity theme

<activity
   android:name="your.activity"
   android:theme="@style/Your.Transparent.Style" />

Setting the status bar color to fully transparent (only working for API >= 21)

// Inside your activity's onCreate 
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setStatusBarTransparent(window)

fun setStatusBarTransparent(window: Window) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
        window.statusBarColor = Color.TRANSPARENT
    }
}

And finally for hiding the bottom sheet when clicking outside of sheet content, extend the BottomSheetBehavior

class AutoCloseBottomSheetBehavior<V : View>(
    context: Context,
    attrs: AttributeSet
) : BottomSheetBehavior<V>(context, attrs) {
    
    @SuppressLint("ClickableViewAccessibility")
    override fun onLayoutChild(parent: CoordinatorLayout, child: V, layoutDirection: Int): Boolean {
        parent.setOnTouchListener { _, event ->
            if (event.action == MotionEvent.ACTION_DOWN) {
                val outRect = Rect()
                child.getGlobalVisibleRect(outRect)
                if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
                    state = STATE_HIDDEN
                    return@setOnTouchListener true
                }
            }

            return@setOnTouchListener false

        }
        return super.onLayoutChild(parent, child, layoutDirection)
    }
}

And set it for your view's behavior

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <Your.View.Acting.As.BottomSheet
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior=".your.path.AutoCloseBottomSheetBehavior" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser1865027View Question on Stackoverflow
Solution 1 - AndroidRahul ParetaView Answer on Stackoverflow
Solution 2 - AndroidNehaKView Answer on Stackoverflow
Solution 3 - AndroidArchitView Answer on Stackoverflow
Solution 4 - AndroidCharuකView Answer on Stackoverflow
Solution 5 - AndroidHarsh DalwadiView Answer on Stackoverflow
Solution 6 - AndroidTreyWurmView Answer on Stackoverflow