I have a layout with two views that should behave like this:
On top is an EditText that should grow (and shrink) dynamically.
Right underneath is another view. The EditText grows until the other
View reaches the bottom of the parent layout, then it stops growing
and becomes scrollable instead.
An image that illustrates the desired behaviour can be found here (cannot upload it) :
The problem, it seems, is to limit the EditText to a certain height so it won't push the other View off the bottom of the screen. Or to give the other View an attribute that it always stays on screen, I don't know.
I tried several different approaches to solve it via xml. The one I attach simply works by limiting the number of lines in the EditText - which I would later have to change programmatically depending on device's resolution. So that's not really desirable.
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="#+id/tripNotes_toolbar_layout"
layout="#layout/toolbar_details" />
<android.support.constraint.ConstraintLayout
android:id="#+id/tripNotes_constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="#style/CardStyle"
android:layout_gravity="top"
android:layout_marginLeft="#dimen/card_outer_margin_left"
android:layout_marginRight="#dimen/card_outer_margin_left">
<EditText
android:id="#+id/tripNotes_editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="#null"
android:hint="Put your notes here"
android:inputType="textMultiLine|textCapWords"
android:lines="12"
android:maxLength="1000"
android:maxLines="12"
android:minLines="1"
android:textColor="#color/grey_12"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/tripNotes_pictureGrid"
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"
app:layout_constraintTop_toBottomOf="#id/tripNotes_editText"
app:layout_constraintVertical_bias="0"
app:layout_constraintVertical_weight="1"/>
</android.support.constraint.ConstraintLayout>
</LinearLayout>
The actual result is that other View is pushed off screen if number of lines in EditText is not limited, but I would prefer a solution with alyout attributes.
You can wrap the edit text into a scroll view
Your layout would look like this :
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="#+id/tripNotes_toolbar_layout"
layout="#layout/toolbar_details" />
<android.support.constraint.ConstraintLayout
android:id="#+id/tripNotes_constraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="#style/CardStyle"
android:layout_gravity="top"
android:layout_marginLeft="#dimen/card_outer_margin_left"
android:layout_marginRight="#dimen/card_outer_margin_left">
<ScrollView
android:id="#+id/tripNotes_editTextContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#+id/tripNotes_pictureGrid">
<EditText
android:id="#+id/tripNotes_editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="#null"
android:hint="Put your notes here"
android:inputType="textMultiLine|textCapWords"
android:textColor="#color/grey_12" />
</ScrollView>
<View
android:id="#+id/tripNotes_pictureGrid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#id/tripNotes_editTextContainer"
app:layout_constraintTop_toBottomOf="#id/tripNotes_editTextContainer"
app:layout_constraintVertical_bias="0"
app:layout_constraintVertical_weight="1"/>
</android.support.constraint.ConstraintLayout>
</LinearLayout>
Changes made :
Constraint layout height changed from match_parent to wrap_content
Edit text
removed app:layout_constraintTop_toTopOf="parent" (no longer needed since it is wrapped in scrollview)
removed maxLines, lines, minLines and maxLength attributes
Added ScrollView
Bottom View :
changed
app:layout_constraintStart_toStartOf="parent" to app:layout_constraintStart_toEndOf="#id/tripNotes_editTextContainer"
changed
app:layout_constraintTop_toBottomOf="#id/tripNotes_editText" to app:layout_constraintTop_toBottomOf="#id/tripNotes_editTextContainer"
The bottom view will be just under the Edittext and will go down until the bottom of the screen and the EditText will become scrollable
Related
How can I add a button on top-end of CardView?
I have a solution but I don't like set a fixed height (ie 50dp) of button and than set margin_top (ie 25dp) of card_view.
Do you have other solutions?
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="#+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:clickable="true"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground"
app:cardCornerRadius="#dimen/corner_radius"
app:cardElevation="2dp"
app:cardUseCompatPadding="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#id/btn">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="#+id/view_cover"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#ddd"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<com.google.android.material.button.MaterialButton
android:id="#+id/btn"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_marginEnd="#dimen/dimen_16"
android:elevation="#dimen/dimen_2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
You can do it like following
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragments.LibraryFragment">
<androidx.cardview.widget.CardView
android:id="#+id/my_card"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>
<ImageView
android:layout_width="0dp"
android:layout_height="0dp"
android:src="#android:drawable/ic_delete"
app:layout_constraintWidth_percent="0.08"
app:layout_constraintDimensionRatio="1:1"
app:tint="#color/redColor"
android:elevation="50dp"
app:layout_constraintTop_toTopOf="#id/my_card"
app:layout_constraintBottom_toTopOf="#id/my_card"
app:layout_constraintStart_toStartOf="#id/my_card"
app:layout_constraintEnd_toEndOf="#id/my_card"
app:layout_constraintHorizontal_bias="0.98"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
This will generate output like this
the image size will be based on the width of the screen which will be differ from device to device and it will resize according to it. and also it will be square due to we provided the ratio as 1:1
Looks like constraint layout doesn't allow negative margin(offset), so all you can do is workarounds.
There is another workaround, which i think is better than your example in terms of "intention". It goes like this:
Assuming your button is 50dp width/height.
Place Space element inside the constraint layout. Let's say width/height is 25dp(half of 50dp).
Align the Space element to the corner of the constraint layout. In case you want to put the button top end, then top of Space to top of constraint layout, and end of Space to end of constraint layout.
At this point, you virtually created "anchor" points where you can align other elements to, that is, the bottom and start of Space.
Align the start of the button to the start of Space, and the bottom of the button to the bottom of Space.
The button is now placed as if you would do layout_marginEnd/layout_marginBottom of -25dp.
The reason I think this is better is that the overlapping detail is no longer the constraint layout's concern, instead, it's the button and its neighboring element's.
I basically have an EditText aligned at the top of the view and there's a RecyclerView on the bottom of the view that can also grow (with the newest item on the bottom)
That's easily doable with a constraint layout but my problem is that when the EditText grows it should start pushing down the list. But the list was initially aligned at the bottom of the parent. (and everything should be scrollable)
I hope this image makes things more clear
The trick here is to avoid forming a vertical chain (so that the EditText at the top always stays in place) and also to leverage the app:layout_constrainedHeight attribute on the RecyclerView in order to make it shrink when the EditText grows.
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="#+id/text"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/text"
app:layout_constraintVertical_bias="1"/>
</androidx.constraintlayout.widget.ConstraintLayout>
The combination of android:layout_height="wrap_content" and app:layout_constrainedHeight="true", along with both top and bottom constraints, means that the RecyclerView will always be only as tall as its items or the available space below the EditText, whichever is smaller.
The app:layout_constraintVertical_bias="1" attribute ensures that, when the list doesn't fill the screen, it sits at the bottom.
I would add to the previous answer and if you want to be able to scroll your layout you can use sth like this:
<androidx.core.widget.NestedScrollView 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"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#id/edit_text"
app:layout_constraintVertical_bias="1.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
This will make everything align as you want to and you are able to scroll down to see your full recyclerView. NestedScrollView will make sure that everything runs smoothly.
This should be similar to an iOS tableview footer, also seen in various websites (sticky-footer).
I want to achieve the following:
A is a RecyclerView with variable number of rows.
When A is smaller than screen (or parent) size, B (footer) should be placed bellow the last row.
When A + B are bigger than screen size, then B becomes fixed at the bottom and A content is scrollable.
We are currently performing this with onMeasure functions that calculate all components heights in order to resize A accordingly.
I was wondering if there is an easier way to do it, maybe with ConstraintLayout.
Put A and B in a vertical packed chain with a bias of 0 to align it to the top. You also need to set app:layout_constrainedHeight="true" for the RecyclerView so that its constraints are taken into account when it gets too big to fit them (parent's height remains match_parent in this case):
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/A"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constrainedHeight="true"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintVertical_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#id/B" />
<TextView
android:id="#+id/B"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Footer"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/A"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
The above solution does not seem to work on Constraintlayout:2.0.0-beta2, looks like a bug introduced in that version. Works on 2.0.0-beta1 and 1.1.3.
Another way would be to set parent's height to wrap_content and then you can use the default chainstyle and remove the bias:
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/A"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constrainedHeight="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#id/B" />
<TextView
android:id="#+id/B"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Footer"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/A"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
This solution works on all versions.
Just try ConstraintLayout, this should very well be possible, just fix Bs height and constrain it to the bottom and A and turn the bias up to top.
I have a screen that contains 2 views, a map(top) and a recycler(bottom)
The rules are simple .
the recycler view is allowed to extend up to the middle of the screen, if more space is needed then it should scroll instead, the map will occupy the rest of the space, Of course if the recycler has less elements then it should shrink leaving more space for the map ..
I am trying to achieve this using constraint layout, also I am trying to avoid solutions that involved calculations .
Check image below for more information on what I am trying to achieve :
Here is my 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:background="#color/white"
tools:context="com.qmic.itraffic.ui.activities.crowdsourcing.CrowdSourceReportingDetailsActivity">
<androidx.constraintlayout.widget.Guideline
android:id="#+id/halfScreenGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5" />
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/categories"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:background="#color/manatee"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/categories"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/halfScreenGuideline" />
</androidx.constraintlayout.widget.ConstraintLayout>
My Question is can I achieve this behaviour using xml only (constraint layout )?or I would need to do calculation ?
Could you please try the following code in your recyclerview xml
android:layout_height="wrap_content"
app:layout_constrainedHeight="true"
Change the height to wrap content and add the second the line. This way the recyclerview should have the height same as its content but never exceeds the guideline/contraint.
The answer by Akhil Soman is correct but missing one thing
app:layout_constraintVertical_bias="1.0"
Without it the RecyclerView was floating above the bottom of the screen leaving an empty space .
for reference here is the complete answer
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/categories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp"
android:clipToPadding="false"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/halfScreenGuideline"
app:layout_constraintVertical_bias="1.0" />
Also the suggestion by the Jaymin seems interesting and can fix this problem, but I haven't applied it because I already used Akil solution .
I have a dialog fragment that contains linear layout that involves a titleText above a RecyclerView, and at the very bottom, there's a button below the recyclerView.
Since a recyclerView expands or collapses based on the number of items the adapter sets, the button sometimes gets truncated and no longer appears to be on screen, since the recyclerView just covers the entire screen.
My question is, is there a way to set the maximum height of the recyclerView without ever hiding the button underneath. I also don't want to just give the view a random height just in case the recyclerView contains no items, and it would just be a blank section.
Please let me know if you've ever run into this issue, and how you resolved this. Thanks!
UPDATED
You can achieve this easily using layout weights. Here's an example:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Title"
android:textSize="21sp"/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="30dp">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="Submit"/>
</FrameLayout>
The Title and RecyclerView will wrap content according to contents and button will always take up bottom place.
I suggest using RelativeLayout as it handles the positioning of views for cases like yours, so that you can actually focus on main design.
<?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:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="Some title" />
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/title"
android:layout_above="#+id/button"/>
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:gravity="center"/>
</RelativeLayout>
Above XML code is the skeleton code for what you need. you can add margins and dimensions to control the spacing. But in any case (until you provide negative margins) your views will never overlap each other.
Main trick of using RelativeLayout is the ability to use XML tags like
android:layout_below or android:layout_above or android:layout_start
or android:layout_end which perfectly aligns your view the way you
want.