56

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.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
user1865027
  • 3,505
  • 6
  • 33
  • 71
  • Why must you use `BottomSheetBehavior`? – ianhanniballake Dec 01 '16 at 00:40
  • 3
    Had the same problem. For me the reason to use `BottomSheet` and `BottomSheetBehavior` instead of `BottomSheetDialog(Fragment)` is that the `BottomSheet` plays nice when showing the keyboard. Using `BottomSheetDialogFragment` causes some janky animation. When showing the keyboard the DialogFragment just "snapps" to it's new position. BottomSheet animates smoothly up/down. – tir38 Jan 05 '17 at 16:46
  • 1
    This has background dimming without fragment http://www.hidroh.com/2016/06/17/bottom-sheet-everything/ – Samuel Sep 29 '17 at 09:10

6 Answers6

52

You can use this code 1. MainActivity.xml

<android.support.design.widget.CoordinatorLayout
xmlns:android="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">

<ScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <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>

</ScrollView>

<View
    android:visibility="gone"
    android:id="@+id/bg"
    android:background="#99000000"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

<android.support.v4.widget.NestedScrollView
    android:id="@+id/bottom_sheet"
    android:layout_width="match_parent"
    android:layout_height="350dp"
    android:clipToPadding="true"
    android:background="@android:color/holo_orange_light"
    app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
    >

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

</android.support.v4.widget.NestedScrollView>

 </android.support.design.widget.CoordinatorLayout>
  1. 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);
    }
    
    }
    
Rahul Pareta
  • 786
  • 6
  • 18
  • 3
    I had to use `setPeekHeight(int peekHeight)` just as [this answer shows](https://stackoverflow.com/a/39062055/370798) in order to make this example work – Sam Sep 11 '17 at 17:37
  • 1
    This works well! But isn't it bad for performance to repeatedly use findview? Especially in onSlide. Why not just obtain the reference to the view in oncreate? – behelit Nov 27 '18 at 23:18
  • This is a more proper solution. – Relm Dec 18 '18 at 08:08
  • how can dim status bar color ? – Pranav May 07 '19 at 07:09
  • Just use `addBottomSheetCallback` as `setBottomSheetCallback` is deprecated – Zain Aug 14 '21 at 03:55
2

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 : Android Fragments and animation

Community
  • 1
  • 1
NehaK
  • 2,639
  • 1
  • 15
  • 31
1

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.

Archit
  • 19
  • 2
0

You can use my concept if you want that i used in AlertDialog with Blur background in possition center

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

Community
  • 1
  • 1
Charuක
  • 12,953
  • 5
  • 50
  • 88
0

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.

Harsh Dalwadi
  • 388
  • 7
  • 15
0

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>
TreyWurm
  • 495
  • 5
  • 16