I have countered a situation that needs to implement a scrollable modal bottom sheet. Basically, it will show half off the screen at first, then when scrolling up, it will stretch to 90% of the screen. If keep swiping up, it will scroll the content inside bottom sheet.
What I have tried
layout.xml
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/design_bottom_sheet"
style="#style/RootLayout"
android:background="#drawable/rounded_shape"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<...Content.../>
</android.core.widget.NestedScrollView>
</FrameLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
Bottom Sheet Dialog class
I followed the answer here accepted answer
Result
It only shows half of the screen when opened, and not scroll the bottom sheet to Expanded State when I scroll the contents up like this result.
Expectation
I expected the above code should behave like this expectation
Does any one have the solution for this situation?
Thank you
So after more than a week, I found a solution for this problem,
I override the onCreateDialog method like below
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// This will disable the behavior of bottom sheet to have a flat corner
//https://github.com/material-components/material-components-android/pull/437#issuecomment-678742683
val dialog = BottomSheetDialog(requireContext(), theme)
dialog.behavior.disableShapeAnimations()
dialog.setOnShowListener { dialogInterface ->
val bottomSheetDialog = dialogInterface as BottomSheetDialog
val parentLayout =
bottomSheetDialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
parentLayout?.let { it1 ->
val behaviour = BottomSheetBehavior.from(it1)
it1.layoutParams.also {
it.height =
context?.resources?.displayMetrics?.heightPixels?.times(0.9)?.toInt()!!
}
behaviour.peekHeight =
context?.resources?.displayMetrics?.heightPixels?.times(0.6)?.toInt()!!
}
}
return dialog
}
What is happening in this code?
I was NOT use the dialog provider by BottomSheetDialog neither super.onCreateDialog()
Instead I used BottomSheetDialog(context,theme) in order to create my custom bottom sheet theme.
Then, I set listener for dialog, get parent layout and bottom sheet dialog like below. The most important part is dealing with behavior.
After achieved the behavior correctly, you are done. Setting it's state to be expanded as recommended by #Nitish Chaudhary and setting the layout height to the height that you want. That's all I need, also this code below is reusable to me wherever you follow the modal bottom sheet XML layout rule.
Happy coding.
Related
My applications have a Toast. But if the soft keyboard is displayed the Toast sort of merged with the soft keyboard and is hard to distinguish. So I looked up how to set a custom view to a Toast. But the View property (SetView and GetView methods) is deprecated.
Instead I was told to use a Snackbar.
I have managed to create a Snackbar with a custom view. I have these problems:
The Snackbar's position is in the lower left corner of my app.
I would like to display the Snackbar as a Toast is displayed:
At the bottom and in the middle of the screen.
Some distance between the Snackbar's bottom and the screen's bottom.
The Snackbar is displayed behind the soft keyboard.
I would like to display the Snackbar above the soft keyboard.
I can hide the soft keyboard to show the Snackbar. But I would prefer not to hide it.
I have googled a lot on these two problems, but have not found any solutions.
For problem #1 I have tried
How can you adjust Android SnackBar to a specific position on screen
I was not able to get the CoordinatorLayout to compile.
Setting the margin on a Layout didn't work.
For problem #2 I have tried
Show Android SnackBar above keyboard? but it didn't work.
Here is my code:
res/layout/snackbar.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView style="#style/SomeStyle"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/tvSnackbarText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Snackbar" />
Fragment
private fun showSnackbarMessage(message: String) {
val view: View = LayoutInflater
.from(context)
.inflate(R.layout.snackbar, null)
val tvSnackbarText = view.findViewById<TextView>(R.id.tvSnackbarText)
tvSnackbarText.text = message
tvSnackbarText.gravity = Gravity.CENTER
val snackbar = Snackbar.make(requireView(), "", Snackbar.LENGTH_LONG)
snackbar.view.setBackgroundResource(R.drawable.rounded_corner_box_grey_stroke);
val params = snackbar.view.layoutParams as FrameLayout.LayoutParams
params.height = FrameLayout.LayoutParams.WRAP_CONTENT
params.width = FrameLayout.LayoutParams.WRAP_CONTENT
snackbar.view.layoutParams = params
val layout = snackbar.view as Snackbar.SnackbarLayout
layout.addView(view)
snackbar.show()
}
Is there any way i can design a bottom sheet fragment as follows:
I need to set the width of the bottom sheet to 1/4 of the screen. IS it possible to implement this?
Thanks for your help.
You can achieve the same result with this library. Or just make the same CornerSheetBehavior. As you can see it's just a BottomSheetBehavior with managing of translationX of view
Create a layout file for your bottom sheet as normal, and set the width to what you need:
<?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"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="#+id/sheet_background"
android:layout_width="100dp"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="100dp"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
app:behavior_peekHeight="150dp"
android:id="#+id/drag_up_from_here">
<!-- Bottom sheet contents here -->
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Then to get it to stick to the right hand side, you can programmatically append the bottom sheet to your layout. This is how I add my bottom sheets:
val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
val bottomSheet = inflater.inflate(R.layout.bottomsheet_layout, null)
val bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet.findViewById<View>(R.id.drag_up_from_here)
// Allow user to drag part of bottom sheet
bottomSheet.findViewById<LinearLayout>(R.id.drag_up_from_here).setOnClickListener {
if (bottomSheetBehavior.getState() != BottomSheetBehavior.STATE_EXPANDED) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED)
} else {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED)
}
}
// Create popup window
val bottomSheetPopup = PopupWindow(popupViewLower,
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.MATCH_PARENT,
false)
// Get the view you want the bottom sheet added to
val currentView = findViewById<ConstraintLayout>(R.id.main_view)
// Display the popup window
bottomSheetPopup.showAtLocation(currentView, Gravity.BOTTOM, currentView.measuredWidth, 0)
The key for getting it on the right of the screen is this part:
bottomSheetPopup.showAtLocation(currentView, Gravity.BOTTOM, currentView.measuredWidth, 0)
Where currentView.measueredWidth is passed as the x value, moving the bottom sheet all the way to the right.
Edit: I realized you're also asking for the width to be exactly 1/4 the width of the screen. To do this, programmatically set the width of the bottom sheet layout to currentView.measuredWidth / 4. This post explains this well: Android view layout_width - how to change programmatically?
I have a video application - a video list and tapping on an item goes to video details. I want video details to be a full screen dialog fragment (to be able to do the motion layout animation from here https://medium.com/vrt-digital-studio/picture-in-picture-video-overlay-with-motionlayout-a9404663b9e7). But the previous dialog is not preserved behind (a blank screen is displayed). Weird enough BottomSheetDialogFragment works but DialogFragment does not.
So the question is - Should a dialog fragment have the previous fragment displayed under it when using jetpack navigation? Why bottom sheet works and dialog not?
Navigation is done using android jetpack navigation:
<action
android:id="#+id/action_videosFragment_to_video_details_nav_graph"
app:destination="#id/video_details_nav_graph"/>
and details nav graph:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/video_details_nav_graph"
app:startDestination="#id/videoDetailsFragment">
<dialog
android:id="#+id/videoDetailsFragment"
android:name="com.myapp.mobile.ui.video.VideoDetailsFragment"
android:label="VideoDetailsFragment"
tools:layout="#layout/fragment_video_details">
</dialog>
</navigation>
Am I missing something? Thanks
After looking more into it, managed to make it work with:
override fun onStart() {
super.onStart()
val dialog: Dialog? = dialog
if (dialog != null) {
val width = ViewGroup.LayoutParams.MATCH_PARENT
val height = ViewGroup.LayoutParams.MATCH_PARENT
dialog.window?.setLayout(width, height)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
}
}
I am able to open my BottomSheetDialogFragment with
val bottomSheet = BottomSheetFragment()
bottomSheet.show(fragmentManager!!, "BottomSheet")
but it only opens to show half of its content - I would like it to expand on opening to the full height of the screen without having to drag it up.
I have looked around and it seems one way is to set the BottomSheetBehavior state to STATE_EXPANDED, but I have not been able to find a solution on how to do this in Kotlin.
Any help would be appreciated!
You can set the BottomSheetBehavior state by placing this inside
onViewCreated of your BottomSheetDialogFragment.
dialog.setOnShowListener { dialog ->
val d = dialog as BottomSheetDialog
val bottomSheet = d.findViewById<View>(R.id.design_bottom_sheet) as FrameLayout
val bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
You may also want to set the peek height to the height of your dialog to prevent the dialog getting stuck half way when attempting to dismiss it.
bottomSheetBehavior.peekHeight = bottomSheet.height
I'm having a problem where Dialogs don't scroll when their content is too large for the screen. I assume this is because Dialogs are not displayed within Scrollable containers.
Screenshot contains content wrapped in a ScrollView - You can see only the content is scrollable
Extra fields added to artificially increase dialog size for this example
You can see from the Android Developer Documentation that Dialogs should be wrapped within DialogFragments (This gives the benefit of having your dialogs survive an orientation change and response to lifecycle events) and this is the set up I'm trying to make work.
Much of the other answers I've found are all similar to one another and revolve around ensuring the window is set to "adjustResize". However, this would only make the parent view smaller on keyboard opening, it wouldn't make a View scrollable if it wasn't in a Scrollable container.
If someone could let me know if they have info to make a Dialog scrollable or to confirm that you cannot make a Dialog scrollable I'd appreciate it.
I have achieved scrolling in dialog fragment,
Dialog layout design
<LinearLayout>
<NestedScrollView>
<ConstrainLayout>
</ConstrainLayout>
</NestedScrollView>
</LinearLayout>
In DialogFrament class ,
override fun onStart() {
super.onStart()
dialog.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZ)
}
This is possibly the dirtiest and most likely to break solution to this problem:
After creating the Dialog in onCreateDialog() but before returning it you can achieve scrolling by adding this code:
final ViewGroup content = (ViewGroup) dialog.findViewById(android.R.id.content);
content.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
content.getViewTreeObserver().removeGlobalOnLayoutListener(this);
View inner = content.getChildAt(0);
content.removeViewAt(0);
ScrollView scrollView = new ScrollView(getContext());
scrollView.addView(inner);
content.addView(scrollView);
}
});
I had the same problem with DialogFragment, but I changed:
<fragment android:layout_height="425dp"
...
to this:
<fragment android:layout_height="wrap_content"
...
And now it's scrolling when the keyboard is open, or when the screen is in landscape mode.
NOTE: I have ScrollView as a rootView in DialogFragment layout, of course.