Applying max height to BottomSheetDialogFragment and apply height change transitions - android

I have a BottomSheetDialogFragment that holds multiple Fragments of different sizes as a content in the FrameLayout.
The max height of the BottomSheetDialogFragment container needs to set at 60% of the total height of the current window viewport.
In addition, I also need to apply transitions when the content displayed within the BottomSheetDialogFragment changes its height such that the height changes smoothens out while rendering.
In other words, the height of the container should stay fixed no matter whatever the content displayed be in BottomSheetDialogFragment. And once the height of the content exceeds the height of the specified height, the content becomes scrollable. Said that, there can be content whose height is less than the max height and hence the transition should be applied to layout height changes.
Here's a layout hierarchy that matches what I have:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="#dimen/toolbar_short_height" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="#id/button_container"
app:layout_constraintTop_toTopOf="parent" />
<!-- This layout always sticks to the bottom of the BottomSheet when opened -->
<RelativeLayout
android:id="#+id/button_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
PS: The view hierarchy still can be simplified but I am trying to use BottomSheetDialogFragment which can be used without specifying BottomSheetBehavior and so it's intended to avoid using CoordinatorLayout.
As per https://material.io/develop/android/components/bottom-sheet-behavior/
Using BottomSheetDialogFragment provides a similar UI and interactions as BottomSheetBehavior, however BottomSheetDialogFragment does not require CoordinatorLayout and creates a modal bottom sheet (essentially a dialog).

Related

Constraint layout Not constraining correctly

I have a layout activity in my project in which I am using a constraint layout and basically only 3 view, first is custom toolbar second a Recycler View and last one is a fragment view. I constrained recycler view to top toolbar and top of fragment view but I goes below fragment and even under screen.
This is my layout code.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="#style/Theme.Music.Font">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="#+id/header_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="#font/bold"
android:text="#string/app_name"
android:textSize="26sp" />
<androidx.appcompat.widget.SearchView
android:id="#+id/searchView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
app:searchIcon="#drawable/search_icon" />
</androidx.appcompat.widget.Toolbar>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbarThumbVertical="#drawable/scroll_bar"
android:scrollbars="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/toolbar" />
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nowPlaying"
android:name="com.example.music.NowPlaying"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:layout="#layout/fragment_now_playing" />
</androidx.constraintlayout.widget.ConstraintLayout>
And when I constrained with fragment also it goes on top overriding toolbar.
As shown in picture.
When you use wrap_content for the height or width, you're saying the view should expand to be as large as necessary to "display" the content. "Display" in air quotes because if there's a lot of content, parts of the view might end up off the screen.
Your constraints don't limit the size here, just the positioning. If you constrain one edge to something, you'll pin it, and it will expand off the screen at the other end. If you constrain opposite edges, then you're basically centering it around those constraints.
If you want to match those constraints, you need to set the height to 0dp. That way your constrained edges will actually be pinned where you want them, and that makes the view a certain height. It fits the space between its constrained edges.
Looking at your layout, I'm guessing the "now playing" part at the bottom is your FragmentContainerView. That's set to wrap_content but its contents seem to be a fixed size, so that's fine - exactly what you want! Same goes for the toolbar - it's wrap_content, but its contents are a fixed size.
So your RecyclerView also needs its bottom to be constrained to the top of nowPlaying, and it needs a layout_height of 0dp. That way, it's constrained to the space between the toolbar and the now playing bit, and its size fills that area. The other two take up as much space as they need, and the RecyclerView fills what's left - which is what you want, right?
Generally this is how it works for anything that scrolls, like a RecyclerView or ScrollView - the view in the layout is a window into the scrolling content, so the actual size of the view (layout_height and layout_width) controls the size of that window. The content just scrolls up and down behind it. If that window is the same size as the content, wrap_content, then the whole thing is visible (but possibly off the screen) and there's nothing to scroll! So for scrolling stuff, you always want to limit the size of the view.

Android Persistent Botom Sheet Height Related to Parent

In android, the Bottom Sheet we use for development requires a peek height to be specified which decides the height of the bottom sheet visible initially to the user. I want this to be related to another view in my Layout ( in this case I want the layout to be matched with the coordinator layout). Actually, I want the view to be like of MyAirtel / Paytm App which has the bottom sheet adjusted according to screen height and width.
Here is the coordinator layout code: -
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="#id/button_github_emoji"
app:layout_constraintBottom_toBottomOf="parent">
<FrameLayout
android:id="#+id/home_screen_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/home_screen_bottom_sheet_color"
app:behavior_hideable="false"
app:behavior_peekHeight="600dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<androidx.fragment.app.FragmentContainerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.daggerfirst.fragments.HomeFragment"
tools:layout="#layout/fragment_home" />
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
The coordinator layout is stretched in its parent.
Just remove the app:behavior_peekHeight="600dp" and it will use the default height for the provided content. If the content covers the entire screen, then the peek height will be 50% of the screen height

ScrollView inside of a ConstraintLayout exceeds bounds of parent

I have the following structure for a fragment:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#+id/credential_save_button">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
..................
</LinearLayout>
</ScrollView>
<TextView
android:id="#+id/credential_save_button"
android:background="#android:color/transparent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:textColor="#color/white"
android:textSize="#dimen/textSize"
android:text="#string/save_initialize"
android:drawableEnd="#drawable/button_right_arrow_blue"
tools:ignore="UseCompatTextViewDrawableXml"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
My expectation is that the save button would be locked to the bottom of the fragment, taking up as much height as it needs to, then the rest of the fragment would be filled with the scrollview and its fields.
Instead the scroll view exceeds the bounds of the Fragment, overlapping the save button and other elements on the page. It seems to always exceed the bounds by about half an element at the top and bottom (would guesstimate it as 20-30dp), no matter how many elements I add to it. If it has few elements in it such that it does not need to scroll, then everything appears correct and no elements are exceeding the bounds of the Fragment.
Changing it to a nested scroll view or adding the fill fillViewport attribute to it does not change anything.
Try setting
android:layout_height="0dp"
for the ScrollView to get it to expand from the top of the layout to the top of the TextView.
Also, don't specify match_parent for any child of ConstraintLayout. Always use 0dp and the appropriate constraints.

How can I force an empty ConstraintLayout to fill its parent container?

I am using a ConstraintLayout as an overlay. It should be as wide and as tall as its parent container. However, for some strange reason it does not fill its parent vertically (but does horizontally). Here is the relevant XML:
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.home.HomeFragment">
<androidx.constraintlayout.widget.ConstraintLayout <-- This is the overlay
android:id="#+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80000000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
With the above code the overlay is not visible either (its height is 0dp). When I place a TextView inside, it expands to fit the TextView. In short, it is behaving as if its height was set to wrap_content.
How do I force it to fill its container while leaving it empty?
The problem is most likely your size specifications. From the documentation for ConstraintLayout:
Important: MATCH_PARENT is not recommended for widgets contained in a ConstraintLayout. Similar behavior can be defined by using MATCH_CONSTRAINT with the corresponding left/right or top/bottom constraints being set to "parent".
There really should be a lint rule for this or ConstraintLayout should reject match_parent.

ConstraintLayout wrapped in ScrollView is still resized when keyboard is opened

I'd like to be able to open the keyboard without my layout resizing. I don't want to use adjustNothing, because I need one of my bottom views to raise up with the keyboard. I'd also prefer not to use adjustPan, because that raises the entirety of the layout off of the screen which is kind of ugly.
So, I wrapped my layout (ConstraintLayout) in a ScrollView like so:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.mypackage.MyViewModel"/>
</data>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:scrollbars="none">
<!-- I don't want anything in this ConstraintLayout to move or resize when I open the keyboard, hence why I wrapped it in a ScrollView. -->
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/scene_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="#+id/scene"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"
android:background="#color/black"
android:scaleType="fitCenter"
android:src="#drawable/grumpy"/>
</FrameLayout>
<android.support.constraint.Guideline
android:id="#+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent=".7"/>
<View
android:id="#+id/status_bar"
android:layout_width="0dp"
android:layout_height="48dp"
android:background="#color/colorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/guideline"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/my_recycler"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="48dp"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/status_bar"/>
</android.support.constraint.ConstraintLayout>
</ScrollView>
<!-- The bottom sheet contains a 48dp high peekable area (a bar with an EditText) that I need to raise with the keyboard. -->
<include
android:id="#+id/bottom_sheet"
layout="#layout/bottom_sheet"
app:viewModel="#{viewModel}"/>
</android.support.design.widget.CoordinatorLayout>
This works fine for the first two times I bring up the keyboard. Here's the bizarre problem I'm having. Every THIRD time I bring up the keyboard, my layout resizes and squishes together as if the ScrollView isn't even there. This occurs consistently every THIRD time I open the keyboard like so:
1) Open keyboard. Layout remains same size.
Close keyboard.
2) Open keyboard. Layout remains same size.
Close keyboard.
3) Open keyboard. *LAYOUT RESIZES/SQUISHES TOGETHER!*
Close keyboard.
The above cycle repeats as I continue to open/close the keyboard.
Any ideas how to solve this? Why does the layout resize every third time I open the keyboard?
Actually, if you use match_parent as the size of ScrollView child, it will also resize when keyboard will appear and fit the ScrollView size.
To understand how ScrollView work, you can think about a Browser. On every link you go, if the page doesn't fit your browser windows, you will see scrollbar to let you navigate in the page.
ScrollView are the same, you just need to give them the size you want to occupy on the screen (like the browser window) and you can put everything in it (like the website page), even if it's bigger than the ScrollView size.
According to this, your ConstraintLayout need a fixed size. It could be wrap_content if you only want the user to access all the component in it.
This is not your case. You want it to keep his original size, that is, as I can see in your code, also the size of your first CoordinatorLayout.
The only way I found to fix an inner ConstraintLayout to the original view size is by code.
Let's simplify your xml for the exemple :
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:id="#+id/parentView"
android:layout_height="match_parent">
<ScrollView
android:id="#+id/scrollViewItem"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout
android:id="#+id/childView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!--ALL YOUR ITEMS-->
</android.support.constraint.ConstraintLayout>
</ScrollView>
</android.support.design.widget.CoordinatorLayout>
You need to fix childView size at loading of the screen.
On the onCreate, you can add this code :
private int first = 0;
public class ConstaintLayoutCanScroll extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
first = childView.getMeasuredHeight();
final CoordinatorLayout parentView = findViewById(R.id.parentView);
parentView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if (first < parentView.getMeasuredHeight()) {
ConstraintLayout childView = findViewById(R.id.childView);
childView.setMinHeight(parentView.getMeasuredHeight());
first = childView.getMeasuredHeight();
}
return true;
}
});
}
}
I use a listener because at OnCreate, the size is not already init.
first is here because onPreDraw isn't called only once by android. It's an variable of the class that I fix to 0 by default.
Using match_parent as the size of ScrollView child isn't ideal. You should rather use fixed size or wrap_content. In your current setup the ConstraintLayout tries to set its height to the ScrollView's height and this changes according to keyboard being displayed or not.
You could try to set your ConstraintLayout to a fixed height as soon as it is displayed on the screen.
To test if that approach works, you can try to set e.g. android:layout_height="400dp" on your ConstraintLayout and see if the "cycle" you mentioned will be happening or not.
If that works, you can move on to using a real dimension of the ConstraintLayout matching the size of the screen.

Categories

Resources