Android - Constraint Layout - margins vs layout_constraintWidth_percent - android

This is a question specifically for ConstraintLayout -
We can use margins as an attribute or layout_constraintWidth_percent as an attribute while working with width for an UI element.
Example - I have a button in centre of my UI which have some empty space to its left and right. Say something like this -
Approach1- uses "marginLeft" and "marginRight"
<Button
android:id="#+id/button"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:text="#string/button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
or
Approach2- uses "app:layout_constraintWidth_percent"
<Button
android:id="#+id/button"
android:layout_width="0dp"
android:layout_height="40dp"
app:layout_constraintWidth_percent="0.8"
android:text="#string/button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Which of the following ways will be a more efficient way to render the UI element?

For efficiency of the two methods, there won't be any difference, at least that you will be able to measure, between the two (IMO), but the two methods are not the same.
Settting
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
will place margins of 24dp to the right and left of the ConstraintLayout regardless of the ConstraintLayout's width. The view's width will take up the remainder of the width which will vary according to the width of the ConstraintLayout.
If you set
app:layout_constraintWidth_percent="0.8"
then the view's width will be 80% of the ConstraintLayout's width and each margin (left and right) will effectively be 10% of the ConstraintLayout's width (1/2 of 20%). So, the width of the view and it's margins will vary based upon the width of the ConstraintLayout.
If it really doesn't matter to your design then just pick one. But since there is a difference in how the two layout, I would look at the layout on the smallest screen you support and the largest and include any other alternate layout for landscape/portrait, etc. You may find that there is a good reason to prefer one over the other.

In case you have more complex structure of the ConstraintLayout layout_constraintWidth_percent will continue measure percents from the ConstraintLayout's width, not from the width it occupies.
For instance, I want to draw this picture:
<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">
<View
android:id="#+id/left"
android:layout_width="50dp"
android:layout_height="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/top"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginStart="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#id/left"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/bottom"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="#id/top"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/top"
app:layout_constraintWidth_percent="0.8" />
</androidx.constraintlayout.widget.ConstraintLayout>
Then you will get bottom rectangle wider, than upper.

Related

Constraint layout min width makes view take all available space when its width is below the minimum

I have a CardView inside a ConstraintLayout, and I want the CardView to have a width of 70%, a min width of 300dp, and a max of 450dp.
The max works fine, if 70% is more than 450dp, the CardView width is set to 450dp.
The min width however, behaves very strangely.
When 70% is less than 300dp, instead of making the CardView 300dp wide, it makes it as wide as the available space, so 100%.
Here is the CardView inside the layout:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="#+id/balanceCard"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="40dp"
app:cardElevation="15dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_min="300dp"
app:layout_constraintWidth_percent="0.7"/>
<androidx.constraintlayout.widget.ConstraintLayout/>
This is the layout with min width of 280dp
And this is the layout with min width of 300dp
I don't know what is happening, and I would appreciate any help.
This looks like a bug to me. The width of the CardView is incorrect when the minimum width exceeds 70% of the layout's width. For a Nexus 6 emulator (dp==3.5px), the ConstraintLayout width would be 1440px and 70% of that is 1008px. (I am using px instead of dp because I think that it makes what's happening clearer.)
When the minimum width is set to 288dp (1008px), everything looks good.
<View
android:id="#+id/balanceCard"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/holo_blue_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_min="288dp"
app:layout_constraintWidth_percent="0.7" />
I am using a straight View instead of a CardView for simplicity.
However, if we set the minimum width to 289dp (1011.5px), we see the problem.
Notice that 288dp (1008px) is equal to 70% of the layout's width (1008px) while 289dp (1011.5px) is greater than 70% of the layout's width.
It is interesting that, if we keep the minimum width at 289dp but remove the end constraint, the width is correct:
<View
android:id="#+id/balanceCard"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/holo_blue_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_min="289dp"
app:layout_constraintWidth_percent="0.7" />
I will note that I am using version 2.1.4 of ConstraintLayout.
The only work-around that I can see One work-around is, upon layout, check the width of the view and if it exceeds 70% of the layout's width, set the width to the minimum width that you want.
Another work-around that is more complex but involves just XML is to split the 70% behavior and the minimum width into two separate Space views. You can then set a barrier to the start and end of these two views and constrain the card view to the barriers. Remove the minimum width and 70% width attributes from the card view. This looks like this:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Space
android:id="#+id/spacePct"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.7" />
<Space
android:id="#+id/spaceMinwidth"
android:layout_width="289dp"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrierStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="start"
app:constraint_referenced_ids="spacePct,spaceMinwidth"/>
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrierEnd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="spacePct,spaceMinwidth"/>
<View
android:id="#+id/balanceCard"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/holo_blue_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="#id/barrierEnd"
app:layout_constraintStart_toStartOf="#id/barrierStart"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
If you agree with my assessment, you may want to consider submitting a bug report on the Issue Tracker, but that is totally up to you.

How to Set a ConstraintLayout View to Percent Min Height

I am using a ConstraintLayout and I want to create a TextView that takes up 30% of the height of the screen or the size of its content, whichever is greater.
I can use constraints to make it take up 30% of the screen via a horizontal guideline, and I can leave off those constraints to make it grow/shrink with the size of the text. However, I can't get it to do both --- that is, be 30% as a minimum but grow beyond that if needed.
I tried using layout_constraintHeight_min and setting it to a percent, to a guideline, and to a view with a set height, but none of those works. I know it will work if I put in a hardcoded value like 200dp, but I want the value to be proportional --- that is, a percent.
I essentially want something like this, though as I mentioned above, this code doesn't work because technically a guideline has a height of 0:
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintHeight_min="#+id/guideline8"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:text="MyText" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/guideline8"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.7" />
I also tried
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintHeight_min="#+id/heightGuide"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:text="MyText" />
<View
android:id="#+id/heightGuide"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="#+id/guideline8"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="invisible"/>
<androidx.constraintlayout.widget.Guideline
android:id="#+id/guideline8"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.7" />
but that doesn't work either.
How can I make a view have a minimum height that is a proportion of the parent, but also allow it to grow beyond that height if needed?
I had to deal with a similar situation and finally managed to solve it by adding the following to my view:
android:layout_height="0dp"
app:layout_constraintHeight_min="wrap"
app:layout_constraintHeight_percent="0.66"
Hope this works for you

How to set size of layouts in Android as a percentage of the sceen size

I have a ConstraintLayout with an inner ConstraintLayout and a inner linear layout in Android. Here you can see 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:id="#+id/outerConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/linearLayout"
android:layout_marginTop="#dimen/_5sdp"
android:layout_marginBottom="#dimen/_8sdp"
android:layout_width="#dimen/_235sdp"
android:layout_height="0dp"
android:orientation="vertical"
android:background="#drawable/rim"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.93"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.75">
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/innerConstraintLayout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="#+id/linearLayout"
app:layout_constraintEnd_toStartOf="#+id/linearLayout"
android:layout_marginLeft="#dimen/_10sdp"
android:layout_marginRight="#dimen/_30sdp"
android:background="#drawable/rim"
android:padding="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/linearLayout"
>
<TextView
android:id="#+id/textView_Name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Inner Constrained Layout"
android:textColor="#000000"
android:textSize="#dimen/_10ssp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/button_okay"
android:layout_width="match_parent"
android:layout_height="#dimen/_25sdp"
android:background="#color/colorGreen"
android:text="Okay"
android:textAllCaps="false"
android:textColor="#121212"
android:textSize="#dimen/_8ssp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.553"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
The problem is that the two layouts have different proportions when being displayed on different screens (see screenshot):
This does not look good as depending on the used device the layouts look quite different regarding their proportions (width and height). Now my question is whether there is a way of just setting the layouts' width and height generally as a percentage of the screen size and thus make the proportions of the width and height of layouts independent of the screen size? Or how else would you design layouts sucht that their proportions between width and height is more or less independant of the screen size?
I'd appreciate every comment and will be happy if you share your experience on that issue?
Now my question is whether there is a way of just setting the layouts' width and height generally as a percentage of the screen size
It can be done using constarintLayout like this:
Take any view, give it android:layout_height="0dp" - now it will respect your constraints, in additions add to it app:layout_constraintHeight_percent="0.15"
Now the view got the height of 15% of the parent size.
the same can be done for the width android:layout_width="0dp"
together with app:layout_constraintWidth_percent="0.5" and now your view will take 50% of your parent size.
You can combine those 2 like this:
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintWidth_percent="0.5"
app:layout_constraintHeight_percent="0.15"
Now your view is both 15% of the parent Width and 50% the parent height, change those numbers and you can control your view size according to your needs.
<LinearLayout
...
android:layout_width="#dimen/_235sdp"
...
>
I assume in #dimen/_235sdp you have a value of 235dp. If so, that line gives your LinearLayout fixed width of 235dp on all devices, so you can't expect it to scale percentage-wise.
Try using a vertical Guideline with percentage value and remove all other fixed sizes:
<?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:id="#+id/outerConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/guideline"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.3"/>
<LinearLayout
android:id="#+id/linearLayout"
android:layout_marginTop="#dimen/_5sdp"
android:layout_marginBottom="#dimen/_8sdp"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
android:background="#android:color/holo_red_dark"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="#id/guideline"
app:layout_constraintEnd_toEndOf="parent">
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/innerConstraintLayout"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="#dimen/_10sdp"
android:layout_marginEnd="#dimen/_30sdp"
android:background="#android:color/holo_blue_dark"
android:padding="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="#id/guideline">
<TextView
android:id="#+id/textView_Name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Inner Constrained Layout"
android:textColor="#000000"
android:textSize="#dimen/_10ssp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/button_okay"
android:layout_width="match_parent"
android:layout_height="#dimen/_25sdp"
android:background="#android:color/holo_green_light"
android:text="Okay"
android:textAllCaps="false"
android:textColor="#121212"
android:textSize="#dimen/_8ssp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
In the updated code I added a vertical guidline splitting the screen to 0.3 and 0.7, then I constrained the inner layouts against the guideline to keep the ratio. Now on any device, you should see the left inner layout taking always 0.3 of the width, and the right inner layout taking always 0.7 of the width.
Tip: try avoiding ConstraintLayout inside ConstraintLayout, it should be fully possible to constrain the inner content (i.e. TextView, Button) against the parent ConstraintLayout without breaking the layout.
I had almost the same problem and to solve this problem, I divided each layer into two modes (mobile and tablet) (the Android system itself can recognize the sizes) and expanded both.
In this way, my work was more regular and changes in the next modes were easily possible for me.
my solution:
Make your resource like this.
res/layout/my_layout.xml
layout for normal screen size ("default")
res/layout-large/my_layout.xml
layout for large screen size
If you do not know how to create a layout-large folder, all you have to do is create a folder with the same name in the Project Explorer path and place your layer there.
Then Android itself will automatically be placed in a separate folder for each layer you have in the Android Studio Live folder.
For example, the activity_main folder consists of two layers as shown below
First of all simplify views tree. Use virtual layouts instead of nesting. You can use Guideline, Flow, attribute app:layout_constraintDimensionRatio or chains with weight for ConstraintLayout's children to use needed proportions.
This article may help you
Another way is to create different layout with qualifiers. Would be better to use single layout for better design management.

Make ViewPager Fill The Remaining Space In a ConstraintLayout

I have the following XML code , I couldn't make 'summaryViewPager' fill the remaining space vertically, until the bottom of the screen :
<?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">
<com.google.android.material.card.MaterialCardView
android:id="#+id/mainCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:background="#666266"
android:padding="10dp"
app:cardCornerRadius="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:layout_marginLeft="3dp"
android:layout_marginTop="2dp"
android:text="#string/category"
android:textColor="#FFBA93"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="#+id/cardView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:background="#666266"
android:padding="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/mainCardView">
<Button
android:id="#+id/shareButton"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="#drawable/ic_share"
app:layout_constraintEnd_toStartOf="#+id/copyButton"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="#+id/seenImageView"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="3dp"
android:layout_marginLeft="3dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:contentDescription="#string/todo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/favoriteButton"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/unseen" />
</com.google.android.material.card.MaterialCardView>
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/summaryViewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/cardView2" />
</androidx.constraintlayout.widget.ConstraintLayout>
The above code doesn't show the PageViewer at all below cardView2 , however when I set android:layout_height="500dp it does show PageViewer, but it doesn't fill the whole screen.
Initial Thoughts
I copy pasted your Layout into Android Studio 4.x, replaced all #string references with "something", and all #drawables with an existing one (from #android:drawable/) so my layout would render.
From what I see, it looks correct:
I noticed some inconsistencies in your Layout.
CardView Number 1 (top)
Careful with Padding on a CardView (don't know your min/max API so, not sure if this applies to you).
Your CardView has no constrain for its BOTTOM, it's left to "whatever size you compute after wrapping" (Height = wrap, Bottom_To = null).
This ^ means that for the CardView to have a height, all its innerwidgets must compute their sizes, so the top card view knows how much size + margin + padding it needs. It doesn't depend on OTHER widgets (that are not children) because its constrains are all to the parent. (rather it only needs its parent).
CardView Number 2 (bottom)
This cardView wraps all its dimensions but has the same issue as the one above, it doesn't constraint its height (except to be pinned at the bottom of the previous one, CardView 1). Again, this is fine, so as long as nobody depends on this (which is not true). This one has to wait on more widgets to know its size, because it has to wait on the CardView1 to know how much space is left, and also needs to know how much its children need. It's not too hard because both (the ImageView and the Button) have fixed sizes (20 and 30 dp respectively) + margins/padding.
The children of a MaterialCardView cannot/should not/must not use constraints because a CardView is not a ConstraintLayout, rather a glorified FrameLayout, which can only hold ONE child (or if it has more, it will put them on on top of the other). So all those constraints (for the shareButton and seenImageView) are ignored.
The solution to the above, is to have an inner ConstraintLayout as the sole child of a CardView, and inside this inner CL, put all your children and their constraints. The inner ConstraintLayout should have its width/height as either wrap_content or match_parent so they use the constraints of the parent. Since htey have fixed sizes, this is not an issue. (and if they didn't it wouldn't be an issue, but would need another measure pass).
Regardless of this, this second CardView is able to calculate its height, because its children are reporting a size of 20+30 (overlapped) + 15 margin top (image) so.. all this combined is likely 45dp~ of height (since they overlap only the biggest numbers apply here).
All the constraints are ignored for these two children.
ViewPager (bottom)
Finally we reach the VPager. This viewPager is match_parent for its width (since you have constraints to the start/end, you should just use 0dp). And it has wrap_content for its height.
layout_height=wrap_content -> this is a problem here. Because the viewPager doesn't (at the time of layout pass/measure) yet know what its contents are going to be. So you probably want this to be 0dp and let the ViewPager use all available space after the above has been calculated.
The marginTop you have here, will not work as it is, because the CardView 1 and 2 have no bottom constrain, so this one would have to create yet another layout/measure pass after it's all said and done to be able to apply a margin (that's how it works).
Ok Enough Rant - what can you do?
I'd "fix" the layout by adding the correct constraints and -if needed- use a VerticalChain and biasing for all widgets.
I'd fix the middle (Cardview 2) contents by wrapping the textview and imageView in a ConstraintLayout.
I'd remove left/right and replace with start/end (unless you target API 16 or below).
I'd set the ViewPager to 0dp on both dimensions.
The children of your CardView2 refer to copyButton and favoriteButton but these don't exist in the Layout you pasted, so I assume you have more buttons there).
Full Version (modified)
And in case you wonder, here's what I did ( I added a color to the VP's background so it was easier to "see").
<?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">
<com.google.android.material.card.MaterialCardView
android:id="#+id/mainCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
app:layout_constraintVertical_chainStyle="packed"
android:background="#666266"
android:padding="10dp"
app:cardCornerRadius="20dp"
app:layout_constraintBottom_toTopOf="#id/cardView2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:layout_marginTop="2dp"
android:text="Category"
android:textColor="#FFBA93"
android:textSize="20sp"
android:textStyle="bold" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="#+id/cardView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:background="#666266"
android:padding="10dp"
app:layout_constraintBottom_toTopOf="#id/summaryViewPager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/mainCardView">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/innerCardView2Root"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="#+id/shareButton"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="5dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="5dp"
android:background="#android:drawable/ic_menu_share"
app:layout_constraintEnd_toStartOf="#+id/seenImageView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="#+id/seenImageView"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="3dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="5dp"
android:contentDescription="#null"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/shareButton"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#android:drawable/ic_menu_search" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/summaryViewPager"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:background="#color/colorSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/cardView2" />
</androidx.constraintlayout.widget.ConstraintLayout>

ConstraintLayout relative to ImageView dimensions

I'm currently developing a small Android application and use the new ConstraintLayout.
I have an ImageView which contains a vector graphic image which is supposed to take the maximum space available with respect to its aspect ratio. I got this to work with the following code:
<ImageView
android:id="#+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="#dimen/margin"
android:src="#drawable/image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
Now, I want to place multiple custom views (buttons) at exact positions. Using Constraint Guidelines, I came up with the following:
<android.support.constraint.Guideline
android:id="#+id/guideline_x"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.20"
tools:layout_editor_absoluteX="..."
tools:layout_editor_absoluteY="..."
/>
<android.support.constraint.Guideline
android:id="#+id/guideline_y"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.20"
tools:layout_editor_absoluteX="..."
tools:layout_editor_absoluteY="..."
/>
<com.example.android.myCustomView
android:id="#+id/myCustomView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doSomething"
app:layout_constraintLeft_toLeftOf="#+id/guideline_x"
app:layout_constraintTop_toTopOf="#+id/guideline_y"
/>
This works great for the specific device that I've been testing with initially. But: As soon as the device dimensions vary, the custom views are placed at wrong positions.
I am looking for a way to place a custom view n% relative to the x coordinate of the image view. I've tried adding app:layout_constraintLeft_toLeftOf="#+id/imageView" to the guidelines but this changes nothing. Do you have any ideas how I can resolve this issue? Thanks a lot!
Edit:
Here are 2 images which illustrate the issue.
Samsung Galaxy S8:
Google Pixel XL:
The little red square should always be at the exact same position relative to the Android icon.
Here is a easier way to place a widget precisely on an ImageView or any other view that will retain it placement regardless of any change in size of the ImageView as long as the aspect ratio remains constant. In the first proposed solution of my other answer, I suggested using bias to place the widget within the ImageView. Unfortunately, a change in the relative size of the widget to to the size of the ImageView causes the widget to shift on the image which is not desireable.
To get around this, I suggest placing a 1x1 pixel Space view on the ImageView using bias. Because the Space view is just 1x1 pixel in size, it can be placed anywhere within the ImageView by using bias. It's relative position will remain constant as the ImageView changes size due to rotation on a single device or because the app is being hosted on devices with different screen sizes. It is just important that the aspect ratio of the image remain constant regardless of the size of the screen.
Once the 1x1 pixel view is placed, the widget can be attached through constraints. In the example below, I have chosen to center the widgets for the eyes on the placement pixels by constraining all sides of each eye widget to the corresponding sides of its placement pixel.
Here are some images showing the effect. Screen with varying sizes that I have tested show the same result.
Nexus 6 Portrait
Nexus 6 Landscape
Because the ImageViews for the eyes are not scaled, they appear smaller on the Nexus 10 screen. The eyes are placed in the same location as on the Nexus 6. Scaling the eyes is a different matter.
Nexus 10 Landscape
Here is the layout showing the constraints:
position_view.xml
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/androidGreen"
android:layout_width="0dp"
android:layout_height="0dp"
android:src="#drawable/android_image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<Space
android:id="#+id/pinpoint1"
android:layout_width="1px"
android:layout_height="1px"
app:layout_constraintBottom_toBottomOf="#id/androidGreen"
app:layout_constraintEnd_toEndOf="#id/androidGreen"
app:layout_constraintHorizontal_bias="0.373"
app:layout_constraintStart_toStartOf="#id/androidGreen"
app:layout_constraintTop_toTopOf="#id/androidGreen"
app:layout_constraintVertical_bias="0.19" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/circle"
app:layout_constraintBottom_toBottomOf="#id/pinpoint1"
app:layout_constraintEnd_toEndOf="#id/pinpoint1"
app:layout_constraintStart_toStartOf="#id/pinpoint1"
app:layout_constraintTop_toTopOf="#id/pinpoint1"
app:srcCompat="#drawable/circle"
tools:ignore="ContentDescription" />
<Space
android:id="#+id/pinpoint2"
android:layout_width="1px"
android:layout_height="1px"
android:background="#android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="#id/androidGreen"
app:layout_constraintEnd_toEndOf="#id/androidGreen"
app:layout_constraintHorizontal_bias="0.625"
app:layout_constraintStart_toStartOf="#id/androidGreen"
app:layout_constraintTop_toTopOf="#id/androidGreen"
app:layout_constraintVertical_bias="0.19" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/circle"
app:layout_constraintBottom_toBottomOf="#id/pinpoint2"
app:layout_constraintEnd_toEndOf="#id/pinpoint2"
app:layout_constraintStart_toStartOf="#id/pinpoint2"
app:layout_constraintTop_toTopOf="#id/pinpoint2"
tools:ignore="ContentDescription" />
</android.support.constraint.ConstraintLayout>
You can also achieve the same goal by wrapping the ImageView in a ConstraintLayout and setting the width and height of the ImageView to match_parent. You can then use percent guidelines to position a widget on the ImageView. This method introduces another level in the layout hierarchy but may be easier to understand.
Here is XML that will demonstrate this method.
XML using guidelines within nested ConstraintLayout
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="#+id/androidGreen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/android_image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<android.support.constraint.Guideline
android:id="#+id/guidelineHorizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.19" />
<android.support.constraint.Guideline
android:id="#+id/guidelineVerticalLeft"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.375" />
<android.support.constraint.Guideline
android:id="#+id/guidelineVerticalRight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.625" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/circle"
app:layout_constraintBottom_toBottomOf="#id/guidelineHorizontal"
app:layout_constraintEnd_toEndOf="#id/guidelineVerticalLeft"
app:layout_constraintStart_toStartOf="#id/guidelineVerticalLeft"
app:layout_constraintTop_toTopOf="#id/guidelineHorizontal"
app:srcCompat="#drawable/circle"
tools:ignore="ContentDescription" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/circle"
app:layout_constraintBottom_toBottomOf="#id/guidelineHorizontal"
app:layout_constraintEnd_toEndOf="#id/guidelineVerticalRight"
app:layout_constraintStart_toStartOf="#id/guidelineVerticalRight"
app:layout_constraintTop_toTopOf="#id/guidelineHorizontal"
tools:ignore="ContentDescription" />
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
If you want to position a view within another view, take a look at Centering positioning and bias in ConstraintLayout.
Bias
The default when encountering such opposite constraints is to center the widget; but you can tweak the positioning to favor one side over another using the bias attributes:
Here is a TextView positioned 20% from the left side of the image and 70% from the top. The positioning will remain constant with different screen sizes, but you will have to make sure the aspect ratio stays constant; otherwise, the TextView will drift.
Here is the XML for the image above:
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:src="#drawable/ic_android_green_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:text="TextView here"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="#id/imageView"
app:layout_constraintEnd_toEndOf="#id/imageView"
app:layout_constraintHorizontal_bias="0.2"
app:layout_constraintStart_toStartOf="#id/imageView"
app:layout_constraintTop_toTopOf="#id/imageView"
app:layout_constraintVertical_bias="0.7" />
</android.support.constraint.ConstraintLayout>
Another way to handle positioning within a view that provides more precise targeting is to define a horizontal and vertical chain of Space views that use weights to divide up the container view. Here is the same layout as above but with a vertical boundary between two Space views at 20% of the width of the ImageView. An additional two Space views have a horizontal boundary at 70% of the container view. See Chains in the documentation for ConstraintLayout.
Here is the XML:
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:src="#drawable/ic_android_green_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Space
android:id="#+id/space1"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/holo_blue_light"
app:layout_constraintBottom_toBottomOf="#id/imageView"
app:layout_constraintEnd_toStartOf="#id/space2"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintHorizontal_weight="20"
app:layout_constraintStart_toStartOf="#id/imageView"
app:layout_constraintTop_toTopOf="#id/imageView" />
<Space
android:id="#+id/space2"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="#id/imageView"
app:layout_constraintEnd_toEndOf="#id/imageView"
app:layout_constraintHorizontal_weight="80"
app:layout_constraintStart_toEndOf="#id/space1"
app:layout_constraintTop_toTopOf="#id/imageView" />
<Space
android:id="#+id/space3"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/holo_blue_light"
app:layout_constraintBottom_toTopOf="#id/space4"
app:layout_constraintEnd_toEndOf="#id/imageView"
app:layout_constraintStart_toStartOf="#id/imageView"
app:layout_constraintTop_toTopOf="#id/imageView"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintVertical_weight="70" />
<Space
android:id="#+id/space4"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="#id/imageView"
app:layout_constraintEnd_toEndOf="#id/imageView"
app:layout_constraintStart_toStartOf="#id/imageView"
app:layout_constraintTop_toBottomOf="#id/space3"
app:layout_constraintVertical_weight="30" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView here"
android:textSize="24sp"
app:layout_constraintStart_toEndOf="#id/space1"
app:layout_constraintTop_toBottomOf="#id/space3" />
</android.support.constraint.ConstraintLayout>

Categories

Resources