I'm migrating some projects to AndroidX with Kotlin. I'm having some issues with ConstraintLayout, I already know that, according to the documentation:
Negative margins will not be supported in ConstraintLayout.
[...]
But I have the following situation:
I need to move up in 5dp the LinearLayout, however I need the height to continue to match the lower limit of the screen. That is, I move up 5dp and increment the height by 5dp.
As it's in the image, I've already tried translateY, but it just moves the entire view (not what I need). Also I can not create a view inside the id#top with height of 5dp and align with constraintTop_toTopOf, since they are part of different groups.
Is there any solution for this case?
It's tricky to get views to overlap in ConstraintLayout, but you can do it by adding an invisible view and constraining the overlapping view to the invisible view.
In this case the invisible view's bottom could be constrained to the bottom of the green LinearLayout, with a bottom margin of 5dp. The red LinearLayout can then have its top constrained to the bottom of the invisible view. This should give you 5dp of overlap.
Try copy-pasting the following into your constraint layout
<LinearLayout
android:id="#+id/green"
android:layout_width="0dp"
android:layout_height="100dp"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="#android:color/holo_green_light" />
<View
android:id="#+id/dummyView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="5dp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="#id/green"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<LinearLayout
android:id="#+id/red"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="#id/dummyView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="#android:color/holo_red_light" />
Note that a dimension of "0dp" means "match constraints" when set on a child view of a ConstraintLayout. This is not obvious, but is in fact documented here https://developer.android.com/reference/android/support/constraint/ConstraintLayout
The whole purpose of ConstraintLayout is to have a flat view hierarchy. Therefore, having LinearLayouts nested in defeats the purpose.
I suggest you get rid of the nested LinearLayouts and do everything using constraints.
Related
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.
I have a View in a Constraint Layout and I would like that at the very beginning it should be outside of the screen (and then later slowly move into the scree from right to left). Now, I kind of need something like negative bias or margins.
I had a look at this question How to achieve overlap/negative margin on Constraint Layout?. The accepted answer using android:layout_marginTop="-25dp" does not have any effect (altough the top of the view is constrained and I use"androidx.constraintlayout:constraintlayout:2.1.3").
I tried the second most upvoted answer and used the code:
view.setTranslationX(view.getWidth() - 20);
This actually works. However, the problem is that when the Fragment is created you first see that the view is not on the left for a short period of time. This is not what I want. I would like to have the view beyond the right rim of the layout at the very very beginning such that it can later move into the layout.
Do you have any idea how I can do that? Ideally I would like to do this programmatically.
Update: Here is the code of the XML layout where a negative margin does not have any effect:
<?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="#drawable/game_test_background"
tools:context=".MainActivity"
android:id="#+id/constraintLayout">
<ImageView
android:id="#+id/imageView_RedRectange_Test"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_marginTop="-1250dp"
app:layout_constraintWidth_percent="0.25"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.048"
app:srcCompat="#drawable/red_rectangle" />
<Button
android:id="#+id/button"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.102"
app:layout_constraintHorizontal_bias="0.373"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.745"
app:layout_constraintWidth_percent="0.12" />
</androidx.constraintlayout.widget.ConstraintLayout>
Okay so to have a negative margin you can use translateX, translateY or TranslationZ.
in xml like so:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World!"
android:translationX="-60dp"
android:translationY="-90dp"
android:translationZ="-420dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
or programmatically like so:
View view = ...;
view.setTranslationX(-60);
view.setTranslationY(-90);
view.setTranslationZ(-420);
Then in order to slowly bring it in from right to left you can use the animate() method like so:
View view = ...;
view.animate().setDuration(1000).translationX(-600).start();
There is a problem with setting the width of the button using app:layout_constraintWidth_percent when the ImageView has a negative margin. The problem should go away if you can set a definite width to the button (instead of 0dp).
The problem should also resolve if you set app:layout_constraintWidth_percent to a value such that the text of the button shows completely on one line.
Here is a simplified layout to demonstrate this issue. The ConstraintLayout has two views that are simply constrained to the parent to appear in vertical center of the layout. These two views have no dependencies on each other. In addition, the ImageView has a top margin of -250dp, so it should appear above the layout's vertical center.
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/holo_green_light">
<ImageView
android:id="#+id/redRectangle"
android:layout_width="100dp"
android:layout_height="30dp"
android:layout_marginTop="-250dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/red_rectangle" />
<Button
android:id="#+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="0.12" />
</androidx.constraintlayout.widget.ConstraintLayout>
Here is what happens then the width of the ImageView is changed from 0dp to a non-zero value of 1dp. When the width is set to 0dp, the negative margin seems to be ignored. It is only when the width is set to a non-zero value that the ImageView is correctly placed.
Changing the width of the button should have no effect on the placement of the ImageView; however, the button only appears in the proper position when the button has a non-zero width.
Here is what happens when the app:layout_constraintWidth_percent is increased so that the word "Button" is not cutoff.
Again, the placement of the ImageView should be independent of the width of the button. Instead, the button only appears in the correct position when the app:layout_constraintWidth_percent is set such that the word "Button" is not cutoff.
This is only an issue with negative margins. Positive margins work as expected.
This is a strange problem, so you may want to use one of the other solutions mentioned.
(ConstraintLayout version 2.1.3)
I have what I think is a pretty common use case: I have multiple rows of information on the screen. I'm hoping to implement these views without having to use nested ViewGroups. Each row should have a minimum height, but expand if the contents are larger than the minimum height. The contents should be nested vertically.
It seems like this simplified example should work:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Row"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#+id/view"
app:layout_constraintBottom_toBottomOf="#+id/view"/>
<View
android:id="#+id/view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#8800ff00"
app:layout_constraintHeight_min="100dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="#id/textView"/>
</android.support.constraint.ConstraintLayout>
Instead of creating a ViewGroup for each row, I have a flat layout with constraints. A View for each row sets the row height and can be clickable. The views inside the row are siblings in the layout hierarchy, but center themselves vertically in the middle of the View for that row. Since I've specified app:layout_constraintHeight_min and anchor the bottom of the View to the bottom of the contents, it should grow with the contents.
But there's a problem:
ConstraintLayout adds undesired spacing above the row! Note that the unwanted spacing above the row is equal to the correct spacing between the bottom of the contents and the bottom of the row.
My theory is this: since the View's bottom is anchored to the bottom of the contents (the TextView) it wants to stick tightly to that and be right next to it. If I force it to move further away, it adds something like a bottom margin to accomplish that, it adds a similar top margin to be symmetrical.
How do I make it stop? If it wasn't for that unwanted spacing on the top, I'd have exactly what I need. Perhaps there's some special ConstraintLayout trick, some magical attribute to fix this behavior. Or maybe there's a completely different way to use ConstraintLayout to accomplish the UI I want.
I realize that using fixed-height rows would make this much simpler, but I don't like doing that if the contents can grow.
I could change my UI to have a nested ConstraintLayout for each row, but I'd rather not do that after working so hard to make a complex layout completely flat, without multiple layers of ViewGroups. But that's what I'll do if I can't find a better solution, which I hope to find here.
I think removing the min height and adding some margin in the TextView will do the thing and instead of using match_parent you can use constraints if possible
<?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">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="50dp"
android:text="Row is not column"
android:textColor="#283858"
app:layout_constraintBottom_toBottomOf="#+id/view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/view"/>
<View
android:id="#+id/view"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#8800ff00"
app:layout_constraintBottom_toBottomOf="#id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
]I'm using constraint layout in version 1.1.0 beta 6 (Which is way better than the stable 1.0.2 by the way...)
However, using constraintHeight_percent doesn't work for me in some case.
Here's what I want to achieve:
Place a button(bottom-button) with a fixed height at the bottom of the constraint layout
Place another button(top-button) between the top of the constraint layout, and above bottom-button.
Make the top-button final height to be half of its height.
Here's the xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="300dp"
android:background="#71B9DE">
<Button
android:id="#+id/bottomButton"
android:layout_width="0dp"
android:layout_height="150dp"
android:text="BOTTOM BUTTON"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<Button
android:layout_width="0dp"
android:layout_height="0dp"
android:text="TOP BUTTON - The height should be 75dp!!"
app:layout_constraintBottom_toTopOf="#id/bottomButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
Here's the preview:
And here's how I expected it to be:
I don't understand why after using app:layout_constraintHeight_percent="0.5", the height of the top button is still 150DP and not 75DP.
It seems like app:layout_constraintHeight_percent="0.5" calculation is done related to the parent height, and not related to the button height itself, after it got constrained.
Btw, I'm not looking for other solutions (Using guidelines, barriers). I really try to figure out why it doesn't work.
Thanks for the help!
According to official reference document in the section 'MATCH_CONSTRAINT dimensions (Added in 1.1)':
layout_constraintWidth_percent and layout_constraintHeight_percent : will set the size of this dimension as a percentage of the parent
provided that the corresponding constraints are set to MATCH_CONSTRAINT (0dp)
You shoud use android:layout_width="match_parent" instead of 0dp
I want to position views outside of a ConstraintLayout to animate them with a sliding animation. I've tried setting contraints like constraintBottom_toTopOf="parent" but the View stays inside the container.
Note that I want to achieve this with constraints to use built-in animations, not with in-code animations.
Any idea how I could do this ?
I'm using compile 'com.android.support.constraint:constraint-layout:1.1.0-beta1'
with Android Studio 3.0 Beta 7
This is a simple xml file that should place the view outside of the container :
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#color/colorAccent">
<View
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#color/colorPrimary"
app:layout_constraintBottom_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
But this is the result
This appears to be an issue with ConstraintLayout 1.1.0-beta1; It works as expected in ConstraintLayout 1.1.0-beta3.
Update to ConstraintLayout 1.1.0-beta3. I will also note that you need to constrain your view horizontally by doing something like the following.
<View
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#color/colorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toTopOf="parent" />
On a side note, negative margins are not accepted in ConstraintLayout. See this Stack Overflow question regarding negative margins and ConstraintLayout.
In every view you can use negative margin, which will put the view outside of the parent view, and then set the clipping parameters.
android:clipChildren="false"
android:clipToPadding="false"
this will make the view not to clip.
I got another way to solve the problem:
1.Add a anchor(anchor_left) layout_constraintStart_toStartOf="parent".
2.Add YourView layout_constraintEnd_toStartOf="#+id/anchor_left"
That's it!
code:
<android.support.constraint.ConstraintLayout>
<View
android:id="#+id/anchor_left"
app:layout_constraintStart_toStartOf="parent"/>
<YourView
android:id="#+id/ll_left"
app:layout_constraintEnd_toStartOf="#+id/anchor_left"/>
</android.support.constraint.ConstraintLayout>
What I did is:
created a view of 0dp height inside the ConstraintLayout, e.g. "fakeView"
placed the new fakeView anchored at Top of the ConstraintLayout
when I need to hide a View, translate it outside the constraint..
change the constraint of the view you want to hide, in order to have BOTTOM connected to the Top of the FakeView.
I think you can use same technique to move object on the left of the fakeview or on the right.
One trick would be to set negative margin for the side you want, in the ConstraintLayout itself. This requires that other views that have constraint to that side be offset:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
...
android:layout_marginBottom="-48dp">
<ImageButton
android:id="#+id/leftButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="72dp"
android:background="#drawable/shape_next_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageButton
android:id="#+id/rightButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="24dp"
android:background="#drawable/shape_previous_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>