Is it possible to have non modal blocking behaviour by using BottomSheetDialogFragment? - android

Currently, we are figuring how to implement such a bottom sheet, with the following requirements.
Round corner bottom sheet.
Fixed height bottom sheet.
Non-draggable bottom sheet.
Content in the bottom sheet is scrollable.
Hide bottom sheet when we tap on non-bottom sheet item.
Hide sheet when we press on back button.
A non-blocking bottom sheet. When we tap on non-bottom sheet item, the tapped item will get focus and bottom sheet will hide.
We are considering, whether to use BottomSheetBehavior or BottomSheetDialogFragment.
So far, we manage to implement all the requirements, by using BottomSheetBehavior.
Implementation using BottomSheetBehavior
However, we do not really like the solution as
It increases the complexity of our Activity's layout, where additional CoordinatorLayout is required.
Manual touch event code handling is required at Activity, to achieve requirement 5, 6 & 7 (Hide bottom sheet).
Here's the code snippet by using BottomSheetBehavior.
public class MainActivity extends AppCompatActivity {
private BottomSheetBehavior bottomSheetBehavior;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.image_button_0).setOnClickListener(view -> demo0());
findViewById(R.id.image_button_1).setOnClickListener(view -> demo1());
// 7) A non-blocking bottom sheet. When we tap on non-bottom sheet item, the tapped item
// will get focus and bottom sheet will hide.
findViewById(R.id.edit_text_0).setOnFocusChangeListener((view, b) -> {
if (b) {
hideBottomSheet();
}
});
// 7) A non-blocking bottom sheet. When we tap on non-bottom sheet item, the tapped item
// will get focus and bottom sheet will hide.
findViewById(R.id.edit_text_1).setOnFocusChangeListener((view, b) -> {
if (b) {
hideBottomSheet();
}
});
}
public void demo0() {
DemoBottomDialogFragment demoBottomDialogFragment = DemoBottomDialogFragment.newInstance();
demoBottomDialogFragment.show(getSupportFragmentManager(), "demoBottomDialogFragment");
}
public void demo1() {
// 1) Round corner bottom sheet.
View view = findViewById(R.id.bottom_sheet_layout_2);
/*
2) Fixed height bottom sheet.
3) Non-draggable bottom sheet.
4) Content in the bottom sheet is scrollable.
*/
this.bottomSheetBehavior = BottomSheetBehavior.from(view);
bottomSheetBehavior.setPeekHeight(900, true);
bottomSheetBehavior.setDraggable(false);
}
private boolean hideBottomSheet() {
if (this.bottomSheetBehavior != null) {
this.bottomSheetBehavior.setPeekHeight(0, true);
this.bottomSheetBehavior = null;
return true;
}
return false;
}
#Override
public void onBackPressed() {
// 5) Hide bottom sheet when we tap on non-bottom sheet item.
if (hideBottomSheet()) {
return;
}
super.onBackPressed();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// 6) Hide sheet when we press on back button.
hideBottomSheet();
return super.onTouchEvent(event);
}
}
If we were using BottomSheetDialogFragment, the code will be way more simpler. We can achieve all requirements, except number 7
A non-blocking bottom sheet. When we tap on non-bottom sheet item, the tapped item will get focus and bottom sheet will hide.
Here's the outcome of BottomSheetDialogFragment.
Implementation using BottomSheetDialogFragment
The good thing of using BottomSheetDialogFragment is that,
Will not increase the complexity of Activity's layout.
No code required at Activity, to hide the bottom sheet (Requirement 5, 6. Requirement 7 still not achievable)
Here's the code snippet.
public class DemoBottomDialogFragment extends BottomSheetDialogFragment {
public static DemoBottomDialogFragment newInstance() {
return new DemoBottomDialogFragment();
}
#NonNull
#Override public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
// https://stackoverflow.com/questions/58651661/how-to-set-max-height-in-bottomsheetdialogfragment
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
#Override public void onShow(DialogInterface dialogInterface) {
BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialogInterface;
FrameLayout bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
ViewGroup.LayoutParams layoutParams = bottomSheet.getLayoutParams();
// !!!
layoutParams.height = 900;
bottomSheet.setLayoutParams(layoutParams);
}
});
return dialog;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Make the bottom sheet non drag-able.
setStyle(DialogFragment.STYLE_NORMAL, R.style.BottomSheetDialogStyle);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater,
#Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_layout, container,
false);
// get the views and attach the listener
return view;
}
}
I was wondering, if we were using BottomSheetDialogFragment, is there a way to achieve
A non-blocking bottom sheet. When we tap on non-bottom sheet item, the tapped item will get focus and bottom sheet will hide.
As you can see, when I tap on EditText region, the bottom sheet is hidden. But, the EditText is not getting focus.
Here's the complete workable demo for testing purpose - https://github.com/yccheok/wediary-sandbox/tree/master/bottom-sheet
Thank you.

There are two window flags that allow passing touch events to the background windows:
FLAG_NOT_TOUCH_MODAL
FLAG_WATCH_OUTSIDE_TOUCH
But those flags can work only for touches outside the dialog window; so setting them alone won't work if the dialog window expands to obscure the EditText's.
So, we need to limit the dialog window to the bottom sheet desired layout height which it's hard coded as 900px. Doing this can prevent the window from obscuring the EditText's; and hence the flags do their job.
Now, we'll hard code the window height to that value; and set the bottom sheet to the expanded state to expand to the entire window:
So, instead of layoutParams.height = 900; We'd use:
WindowManager.LayoutParams params = window.getAttributes();
params.height = 900;
params.gravity = Gravity.BOTTOM; // bias the dialog to the bottom
getDialog().getWindow().setAttributes(params);
This will achieve the desired behavior but now the rounded corners are gone as the expanded state is designed to expand to the entire available space. To solve this we'd set the rounded corner in the BottomSheet style instead of the layout.
Here is the modified version:
<resources>
<style name="BottomSheetDialogStyle" parent="Theme.Material3.Light.BottomSheetDialog">
<item name="behavior_draggable">false</item>
<item name="bottomSheetStyle">#style/BottomSheetStyle</item>
</style>
<style name="BottomSheetStyle">
<item name="android:background">#drawable/bottom_sheet_background</item>
</style>
</resources>
Now we can safely remove android:background="#drawable/bottom_sheet_background" from the layout.
BottomSheetDialogFragment:
public class DemoBottomDialogFragment extends BottomSheetDialogFragment {
public static DemoBottomDialogFragment newInstance() {
return new DemoBottomDialogFragment();
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
// https://stackoverflow.com/questions/58651661/how-to-set-max-height-in-bottomsheetdialogfragment
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
#Override
public void onShow(DialogInterface dialogInterface) {
BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialogInterface;
FrameLayout bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
});
return dialog;
}
#Override
public void onStart() {
super.onStart();
Window window = getDialog().getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
window.setFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
WindowManager.LayoutParams params = window.getAttributes();
params.height = 900;
params.gravity = Gravity.BOTTOM;
window.setAttributes(params);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Make the bottom sheet non drag-able.
setStyle(DialogFragment.STYLE_NORMAL, R.style.BottomSheetDialogStyle);
}
#Nullable
#Override
#SuppressLint("RestrictedApi")
public View onCreateView(LayoutInflater inflater,
#Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_layout, container,
false);
return view;
}
}

Related

BottomSheetDialogFragment exposed height

It seems BottomSheetDialogFragment is coded with an anchor, where if your fragment layout exceeds 360dp in height onShow() will cause the dialog to peek to 360dp and you have to manually drag the sheet up to show all of your layout.
any way to bypass this behavior or any other recommendations for a modal bottom dialog where I can use a fragment?
you may check the behavior as follows
Activity.java
MyDialog myDialog = new MyDialog();
mtDialog.show(getChildFragmentManager(),"my_dialog_fragment");
MyDialog.java
public class MyDialog extends BottomSheetDialogFragment{
public View onCreate(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState){
return inflater.inflate(R.layout.dialog, container, false);
}
}
dialog.xml
<FrameLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="432dp"
android:backgroud="#color/blue"/>
that's pretty much the code. I've tried using setStyle and creating a BottomSheetDialog style and all those permutations and nope. but setting the height to 360dp is where it expands fully, but I need some more area.
OP here answering the Q.
public class MyDialogFragment extends BottomSheetDialogFragment {
#Override
public void setupDialog(Dialog dialog, int style) {
View v = LayoutInflater.from(getActivity()).inflate(R.layout.dialog, null);
dialog.setContentView(v);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) ((View) v.getParent()).getLayoutParams();
CoordinatorLayout.Behavior behavior = params.getBehavior();
((BottomSheetBehavior) behavior).setState(BottomSheetBehavior.STATE_EXPANDED);
}
The above fixed the issue of not expanding to the full height declared in the layout. Might want to add a check on the behavior to make sure its not null. Now just need to add my arithmetic, not sure if it needs to be on oncreateview or here in setup dialog... we shall see.
Happy coding :)

BottomSheetDialogFragment opens half [duplicate]

This question already has answers here:
Set state of BottomSheetDialogFragment to expanded
(20 answers)
Closed 4 years ago.
My BottomSheetDialogFragment opens half (mean not fully) when I open it.
fragment.show(supportFragmentManager, "my_frag")
I tried NestedScrollView with behavior_peekHeight but did not work.
Tried without NestedScrollView. with only LinearLayout.
Tried switching height between match_parent & wrap_content
I have simple RecyclerView in BottomSheetDialogFragment layout.
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
...
>
<android.support.v7.widget.RecyclerView
...
/>
By BottomSheetFragment you mean BottomSheetDialogFragment . To open expended sheet you need to make some changes in onCreateDialog().
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
BottomSheetDialog bottomSheetDialog=(BottomSheetDialog)super.onCreateDialog(savedInstanceState);
bottomSheetDialog.setOnShowListener(new DialogInterface.OnShowListener() {
#Override
public void onShow(DialogInterface dialog) {
BottomSheetDialog dialog = (BottomSheetDialog) dialog;
FrameLayout bottomSheet = dialog .findViewById(android.support.design.R.id.design_bottom_sheet);
BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
BottomSheetBehavior.from(bottomSheet).setSkipCollapsed(true);
BottomSheetBehavior.from(bottomSheet).setHideable(true);
}
});
return bottomSheetDialog;
}
Just keep the layout match_parent no need to use NestedScrollView. It worked for me . Let me know if you still face problem .
In case someone is using New Material library . Which is
implementation 'com.google.android.material:material:1.0.0'.
Then you need change the id of Parent FrameLayout. So it will be .
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
BottomSheetDialog bottomSheetDialog=(BottomSheetDialog)super.onCreateDialog(savedInstanceState);
bottomSheetDialog.setOnShowListener(new DialogInterface.OnShowListener() {
#Override
public void onShow(DialogInterface dia) {
BottomSheetDialog dialog = (BottomSheetDialog) dia;
FrameLayout bottomSheet = dialog .findViewById(com.google.android.material.R.id.design_bottom_sheet);
BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
BottomSheetBehavior.from(bottomSheet).setSkipCollapsed(true);
BottomSheetBehavior.from(bottomSheet).setHideable(true);
}
});
return bottomSheetDialog;
}
Make sure all your imports from import com.google.android.materialin this case.
You are accessing your parent view so use below code to expand it into Full screen.
View parent = (View) inflatedView.getParent();
parent.setFitsSystemWindows(true);
BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(parent);
inflatedView.measure(0, 0);
DisplayMetrics displaymetrics = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int screenHeight = displaymetrics.heightPixels;
bottomSheetBehavior.setPeekHeight(screenHeight);
if (params.getBehavior() instanceof BottomSheetBehavior) {
((BottomSheetBehavior)params.getBehavior()).setBottomSheetCallback(mBottomSheetBehaviorCallback);
}
params.height = screenHeight;
parent.setLayoutParams(params);
Hope it helps you.

BottomSheetDialogFragment - How to set expanded height (or min top offset)

I create a BottomSheetDialogFragment and I want to adjust it's maximum expanded height. How can I do that? I can retrieve the BottomSheetBehaviour but all I can find is a setter for the peek height but nothing for the expanded height.
public class DialogMediaDetails extends BottomSheetDialogFragment
{
#Override
public void setupDialog(Dialog dialog, int style)
{
super.setupDialog(dialog, style);
View view = View.inflate(getContext(), R.layout.dialog_media_details, null);
dialog.setContentView(view);
...
View bottomSheet = dialog.findViewById(R.id.design_bottom_sheet);
BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setPeekHeight(...);
// how to set maximum expanded height???? Or a minimum top offset?
}
}
EDIT
Why do I need that? Because I show a BottomSheet Dialog in a full screen activity and it looks bad if the BottomSheet leaves a space on top...
The height is being wrapped because the inflated view is added to the FrameLayout which has layout_height=wrap_content. See FrameLayout (R.id.design_bottom_sheet) at https://github.com/dandar3/android-support-design/blob/master/res/layout/design_bottom_sheet_dialog.xml.
The class below makes the bottom sheet full screen, background transparent, and fully expanded to the top.
public class FullScreenBottomSheetDialogFragment extends BottomSheetDialogFragment {
#CallSuper
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
}
#Override
public void onStart() {
super.onStart();
Dialog dialog = getDialog();
if (dialog != null) {
View bottomSheet = dialog.findViewById(R.id.design_bottom_sheet);
bottomSheet.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
}
View view = getView();
view.post(() -> {
View parent = (View) view.getParent();
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) (parent).getLayoutParams();
CoordinatorLayout.Behavior behavior = params.getBehavior();
BottomSheetBehavior bottomSheetBehavior = (BottomSheetBehavior) behavior;
bottomSheetBehavior.setPeekHeight(view.getMeasuredHeight());
((View)bottomSheet.getParent()).setBackgroundColor(Color.TRANSPARENT)
});
}
}
--- EDIT Aug 30, 2018 ---
I realized a year later that the background was colored on the wrong view. This dragged the background along with the content while a user was dragging the dialog.
I fixed it so that the parent view of the bottom sheet is colored.
I found a much simpler answer; in your example where you obtain the FrameLayout for the bottom sheet using this code
View bottomSheet = dialog.findViewById(R.id.design_bottom_sheet);
you can then set the height on the layout params for that View to whatever height you want to set the expanded height to.
bottomSheet.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
BIG UPDATE
Avoiding duplicated code I'm giving a link to the full answer in where you can find all the explanations about how to get full behavior like Google Maps.
I want to adjust its maximum expanded height. How can I do that?
Both BottomSheet and BottomSheetDialogFragment use a BottomSheetBehavior that you can found in Support Library 23.x
That Java class has 2 different uses for mMinOffset, one of them is used to define the area of the parent it will use to draw his content (maybe a NestedScrollView). And the other use is for defining the expanded anchor point, I mean, if you slide it up to form STATE_COLLAPSEDit will animate your BottomSheetuntil he reached this anchor point BUT if you can still keep sliding up to cover all parent height (CoordiantorLayout Height).
If you took a look at BottomSheetDialog you will see this method:
private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
final CoordinatorLayout coordinator = (CoordinatorLayout) View.inflate(getContext(),
android.support.design.R.layout.design_bottom_sheet_dialog, null);
if (layoutResId != 0 && view == null) {
view = getLayoutInflater().inflate(layoutResId, coordinator, false);
}
FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(android.support.design.R.id.design_bottom_sheet);
BottomSheetBehavior.from(bottomSheet).setBottomSheetCallback(mBottomSheetCallback);
if (params == null) {
bottomSheet.addView(view);
} else {
bottomSheet.addView(view, params);
}
// We treat the CoordinatorLayout as outside the dialog though it is technically inside
if (shouldWindowCloseOnTouchOutside()) {
final View finalView = view;
coordinator.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (isShowing() &&
MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP &&
!coordinator.isPointInChildBounds(finalView,
(int) event.getX(), (int) event.getY())) {
cancel();
return true;
}
return false;
}
});
}
return coordinator;
}
No idea which one of those 2 behaviors you want but if you need the second one follow those steps:
Create a Java class and extend it from CoordinatorLayout.Behavior<V>
Copy paste code from the default BottomSheetBehavior file to your new one.
Modify the method clampViewPositionVertical with the following code:
#Override
public int clampViewPositionVertical(View child, int top, int dy) {
return constrain(top, mMinOffset, mHideable ? mParentHeight : mMaxOffset);
}
int constrain(int amount, int low, int high) {
return amount < low ? low : (amount > high ? high : amount);
}
Add a new state
public static final int STATE_ANCHOR_POINT = X;
Modify the next methods: onLayoutChild, onStopNestedScroll, BottomSheetBehavior<V> from(V view) and setState (optional)
And here is how it looks like
[]
Its works for me. Add code on BottomSheetDialogFragment's onViewCreated() methode
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
val dialog = dialog as BottomSheetDialog
val bottomSheet = dialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet) as FrameLayout?
val behavior = BottomSheetBehavior.from(bottomSheet!!)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
val newHeight = activity?.window?.decorView?.measuredHeight
val viewGroupLayoutParams = bottomSheet.layoutParams
viewGroupLayoutParams.height = newHeight ?: 0
bottomSheet.layoutParams = viewGroupLayoutParams
}
})
dialogView = view
}
Don't forget to remove viewTreeObserver.
override fun onDestroyView() {
dialogView?.viewTreeObserver?.addOnGlobalLayoutListener(null)
super.onDestroyView()
}
Get reference to sheet behavior,
private val behavior by lazy { (dialog as BottomSheetDialog).behavior }
turn fitToContents off and set expandedOffset to desired pixels.
behavior.isFitToContents = false
behavior.expandedOffset = 100
Kotlin
In my case I need to define a fixed height and I did the following:
val bottomSheet: View? = dialog.findViewById(R.id.design_bottom_sheet)
BottomSheetBehavior.from(bottomSheet!!).peekHeight = 250
this way you also have access to any property of the BottomSheetBehavior such as halfExpandedRatio
I would advise against using ids to find views. In the BottomSheetDialogFragment the dialog is a BottomSheetDialog which exposes the behavior for the bottom sheet. You can use that to set the peek height.
(dialog as BottomSheetDialog).behavior.peekHeight = ...

Dynamically change height of BottomSheetBehavior

I'm using the BottomSheetBehavior from Google recently released AppCompat v23.2. The height of my bottom sheet depends on the content displayed inside of the bottom sheet (similar to the what Google does themselves in their Maps app).
It works fine with the data loaded initially, but my application changes the content displayed during runtime and when this happens the bottom sheet retains at it's old height, which either leads to unused space at the bottom or a cut of view.
Is there any way to inform the bottom sheet layout to recalculate the height used for expanded state (when height of the ViewGroup is set to MATCH_HEIGHT) or any way to manually set the required height?
EDIT: I also tried to manually call invalidate() on the ViewGroup and the parent of it but without any success.
I had the same problem with RelativeLayout as my bottom sheet. The height won't be recalculated. I had to resort to setting the height by the new recalculated value and call BottomSheetBehavior.onLayoutChild.
This is my temporary solution:
coordinatorLayout = (CoordinatorLayout)findViewById(R.id.coordinator_layout);
bottomSheet = findViewById(R.id.bottom_sheet);
int accountHeight = accountTextView.getHeight();
accountTextView.setVisibility(View.GONE);
bottomSheet.getLayoutParams().height = bottomSheet.getHeight() - accountHeight;
bottomSheet.requestLayout();
behavior.onLayoutChild(coordinatorLayout, bottomSheet, ViewCompat.LAYOUT_DIRECTION_LTR);
You can use BottomSheetBehavior#setPeekHeight for that.
FrameLayout bottomSheet = (FrameLayout) findViewById(R.id.bottom_sheet);
BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setPeekHeight(newHeight);
This does not automatically move the bottom sheet to the peek height. You can call BottomSheetBehavior#setState to adjust your bottom sheet to the new peek height.
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
Though the issue has been resolved in >=24.0.0 support library, if for some reason you still have to use the older version, here is a workaround.
mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull final View bottomSheet, int newState) {
bottomSheet.post(new Runnable() {
#Override
public void run() {
//workaround for the bottomsheet bug
bottomSheet.requestLayout();
bottomSheet.invalidate();
}
});
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
}
});
For bottom sheet dialog fragment, read this: Bottom Sheet Dialog Fragment Expand Full Height
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
BottomSheetDialog dialog = (BottomSheetDialog) getDialog();
FrameLayout bottomSheet = dialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
behavior.setPeekHeight(0);
}
I faced the same issue, when trying to update the peek height based on its contents, the height from a previous layout was found. This makes sense as the new layout had not taken place yet. By posting on the UI thread the layout height is calculated after the new layout, and another layout request is made to update the bottom sheet to the right height.
void show() {
setVisibility(View.VISIBLE);
post(new Runnable() {
#Override
public void run() {
mBottomSheetBehavior.setPeekHeight(findViewById(R.id.sheetPeek).getHeight());
requestLayout();
}
})
}
I was facing the same issue when I used a recyclerview inside a BottomSheet and the items changed dynamically. As #sosite has mentioned in his comment, the issue is logged and they have fixed it in the latest release.
Issue log here
Just update your design support library to version 24.0.0 and check.
I've followed #HaraldUnander advice, and it gave me an idea which has actually worked. If you run a thread (couldn't make it work with the post method as him) after the BottomSheetBehavior.state is set up programmatically to STATE_COLLAPSED, then you can already obtain the height of your views and set the peekHeight depending on it's content.
So first you set the BottomSheetBehavior:
BottomSheetBehavior.from(routeCaptionBottomSheet).state = BottomSheetBehavior.STATE_COLLAPSED
And then you set the peekHeight dynamically:
thread {
activity?.runOnUiThread {
val dynamicHeight = yourContainerView.height
BottomSheetBehavior.from(bottomSheetView).peekHeight = dynamicHeight
}
}
If using Java (I'm using Kotlin with Anko for threads), this could do:
new Thread(new Runnable() {
public void run() {
int dynamicHeight = yourContainerView.getHeight();
BottomSheetBehavior.from(bottomSheetView).setPeekHeight(dynamicHeight);
}
}).start();
Below code snippet helped me solve this issue where i am toggling between visibility of different views in layout and height is automatically changing for my bottom sheet.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.your_bottom_sheet_layout, container, false)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
dialog.setContentView(R.layout.your_bottom_sheet_layout)
dialog.setOnShowListener {
val castDialog = it as BottomSheetDialog
val bottomSheet = castDialog.findViewById<View?>(R.id.design_bottom_sheet)
val behavior = BottomSheetBehavior.from(bottomSheet)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_DRAGGING) {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {}
})
}
return dialog
}
I've been struggling with a problem similar to yours.
Manually setting the height of the bottomSheet was the solution for me.
Having a view viewA that has the BottomSheetBehaviour and a custom method modifyHeight() that modifies the height of the view:
viewA?.modifyHeight()
viewA?.measure(
MeasureSpec.makeMeasureSpec(
width,
MeasureSpec.EXACTLY
),
MeasureSpec.makeMeasureSpec(
0,
MeasureSpec.UNSPECIFIED
)
)
val layoutParams = LayoutParams(viewA.measuredWidth, viewA.measuredHeight)
val bottomSheet = BottomSheetBehavior.from(viewA)
layoutParams.behavior = bottomSheet
viewA.layoutParams = layoutParams
My layout would be something like:
<com.yourpackage.ViewA
android:id="#+id/viewA"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:behavior_peekHeight="50dp"
app:layout_behavior="#string/bottom_sheet_behavior" />
It is important to reuse the bottomSheetBehaviour of the old layoutParams because it contains the peekHeight and listeners you may have attached.
Here is the toggle button click listener I have implement to set pick height of bottom sheet with animation
FrameLayout standardBottomSheet = findViewById(R.id.standardBottomSheet);
BottomSheetBehavior<FrameLayout> bottomSheetBehavior = BottomSheetBehavior.from(standardBottomSheet);
btnToggleBottomSheet.setOnClickListener(new HPFM_OnSingleClickListener() {
#Override
public void onSingleClick(View v) {
if (bottomSheetBehavior.getPeekHeight() == 0) {
ObjectAnimator.ofInt(bottomSheetBehavior, "peekHeight", 200).setDuration(300).start();
}
else {
ObjectAnimator.ofInt(bottomSheetBehavior, "peekHeight", 0).setDuration(300).start();
}
}
});

Open an activity or fragment with Bottom Sheet Deep Linking

I want to open a Bottom Sheet (Deep Linking way) but inside of it instead of share options or just a layout, I want to have an activity with its layout or a fragment with its layout.
Known libraries that open Bottom Sheet Like Flipboard/BottomSheet can open layout, not whole activity.
Is there any possibility to achieve that with a Coordinator Layout?
I found a Google's Photo on Bottom Sheet Component Page that shows what exactly I have in mind. Google's description says:
The app on the right displays a bottom sheet containing content from the app on the left. This allows the user to view content from another app without leaving their current app.
I am not an expert, but after some research, I've found a simple way to do this. In your activity_main.xml first, make sure that your root layout is the android.support.design.widget.CoordinatorLayout.
Just inside that CoodrdinatorLayout add an include to your Bottom Sheet Layout:
<include layout="#layout/bottom_sheet_main" />
Then, and this is probably the most important step, you need to specify the behavior of the Bottom Sheet layout, so here is a sample code:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/bottomSheet"
android:layout_width="match_parent"
android:layout_height="300dp"
android:orientation="vertical"
android:background="#FFFFFF"
app:layout_behavior="#string/bottom_sheet_behavior"
app:behavior_hideable="true"
app:behavior_peekHeight="64dp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="#style/TextAppearance.AppCompat.Title"
android:padding="16dp"
android:text="BOTTOM SHEET" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="#style/TextAppearance.AppCompat.Body1"
android:padding="16dp"
android:text="Here goes text" />
</LinearLayout>
Okay, so that was all the XML code. Notice that we applied an app:layout_behavior so that it has the properties we want. Another important thing is to specify app:behavior_hideable="true" if we want to have the option of hiding the whole layout. The attribute app:behavior_peekHeight="64dp" means that the view will be 64dp high when it is collapsed (but not hidden).
There are 3 main stages of this view:
Expanded (STATE_EXPANDED): when the Bottom Sheet is completely open.
Collapsed (STATE_COLLAPSED): when the user only sees a small part from the top of the view. The attribute app:behavior_peekHeight determines this height.
Hidden(STATE_HIDDEN): When it is completely hidden (SURPRISE HAHA!).
We also have STATE_SETTLING and STATE_DRAGGING which are transitory, but they are not that important.
Now, if you run your app (you don't have to write any JAVA code) you will see that if you swipe up the title that will appear at the bottom of your layout, the Sheet will expand, and the same in the other way.
But you may notice that if you click on the Bottom Sheet, nothing happens. You can play with some Java code to manage the state of the Bottom Sheet:
Declare the view: LinearLayout bottomSheet = (LinearLayout)findViewById(R.id.bottomSheet);
Declare the behavior "manager":
final BottomSheetBehavior bsb = BottomSheetBehavior.from(bottomSheet);
And then you can get state changes:
bsb.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
String strNewState = "";
switch(newState) {
case BottomSheetBehavior.STATE_COLLAPSED:
strNewState = "STATE_COLLAPSED";
break;
case BottomSheetBehavior.STATE_EXPANDED:
strNewState = "STATE_EXPANDED";
break;
case BottomSheetBehavior.STATE_HIDDEN:
strNewState = "STATE_HIDDEN";
break;
case BottomSheetBehavior.STATE_DRAGGING:
strNewState = "STATE_DRAGGING";
break;
case BottomSheetBehavior.STATE_SETTLING:
strNewState = "STATE_SETTLING";
break;
}
Log.i("BottomSheets", "New state: " + strNewState);
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
Log.i("BottomSheets", "Offset: " + slideOffset);
}});
And there it is!
You can also use a Modal Bottom Sheet, which lets you create a Bottom Sheet as if it was a fragment.
For create that you need to have a BottomSheetDialogFragment from com.google.android.material library like this :
public class FragmentBottomSheetDialogFull extends BottomSheetDialogFragment {
private BottomSheetBehavior mBehavior;
private AppBarLayout app_bar_layout;
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);
final View view = View.inflate(getContext(), R.layout.fragment_bottom_sheet_dialog_full, null);
dialog.setContentView(view);
mBehavior = BottomSheetBehavior.from((View) view.getParent());
mBehavior.setPeekHeight(BottomSheetBehavior.PEEK_HEIGHT_AUTO);
mBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
if (BottomSheetBehavior.STATE_EXPANDED == newState) {
// View is expended
}
if (BottomSheetBehavior.STATE_COLLAPSED == newState) {
// View is collapsed
}
if (BottomSheetBehavior.STATE_HIDDEN == newState) {
dismiss();
}
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
}
});
return dialog;
}
#Override
public void onStart() {
super.onStart();
mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
then in your activity call that to open
// display sheet
FragmentBottomSheetDialogFull fragment = new FragmentBottomSheetDialogFull();
fragment.show(getSupportFragmentManager(), fragment.getTag());

Categories

Resources