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>
Related
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.
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>
I am currently facing a trouble regarding size change.
I have coded my xml files using dp's, but when I change the device screen size, the layout would change.
For example, when I go into Settings - Display - Screen zoom and font and set screen zoom from medium to large, some of the elements in my layout such as imageview or button would grow and go beyond the screen.
Since I'm using a complex layout containing LinearLayouts and FrameLayouts, I am not clear about how I should make this layout look alright in any other devices or screen sizes. It seems like when I change the screen zoom setting, the value of dp changes.
Is there a way to avoid this problem, or maybe to programmatically set screen zoom setting to medium?
Here is my code:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:layout_gravity="center"
android:layout_marginTop="65dp">
<android.support.constraint.ConstraintLayout
android:id="#+id/frameLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="95dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.3">
<ImageView
android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/home_map" />
<Button
android:id="#+id/pick1"
android:layout_width="34dp"
android:layout_height="56dp"
android:background="#drawable/top_deselected"
app:layout_constraintBottom_toBottomOf="#+id/imageView"
app:layout_constraintEnd_toEndOf="#+id/imageView"
app:layout_constraintHorizontal_bias="0.17"
app:layout_constraintStart_toStartOf="#+id/imageView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.18" />
<Button
android:id="#+id/pick2"
android:layout_width="34dp"
android:layout_height="56dp"
android:background="#drawable/jungle_deselected"
app:layout_constraintBottom_toBottomOf="#+id/imageView"
app:layout_constraintEnd_toEndOf="#+id/imageView"
app:layout_constraintHorizontal_bias="0.28"
app:layout_constraintStart_toStartOf="#+id/imageView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.367" />
<Button
android:id="#+id/pick3"
android:layout_width="34dp"
android:layout_height="56dp"
android:background="#drawable/mid_deselected"
app:layout_constraintBottom_toBottomOf="#+id/imageView"
app:layout_constraintEnd_toEndOf="#+id/imageView"
app:layout_constraintHorizontal_bias="0.424"
app:layout_constraintStart_toStartOf="#+id/imageView"
app:layout_constraintTop_toTopOf="#+id/imageView"
app:layout_constraintVertical_bias="0.43" />
<Button
android:id="#+id/pick4"
android:layout_width="34dp"
android:layout_height="56dp"
android:background="#drawable/support_deselected"
app:layout_constraintBottom_toBottomOf="#+id/imageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.615"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/imageView"
app:layout_constraintVertical_bias="0.71" />
<Button
android:id="#+id/pick5"
android:layout_width="34dp"
android:layout_height="56dp"
android:background="#drawable/adc_deselected"
app:layout_constraintBottom_toBottomOf="#+id/imageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.733"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/imageView"
app:layout_constraintVertical_bias="0.71" />
</android.support.constraint.ConstraintLayout>
<Button
android:layout_width="335dp"
android:layout_height="42.5dp"
android:layout_marginBottom="5.5dp"
android:background="#drawable/home_mode"
android:id="#+id/home_mode"/>
<Button
android:layout_width="335dp"
android:layout_height="42.5dp"
android:background="#drawable/home_match"
android:id="#+id/home_match"/>
</LinearLayout>
The constraint layout used to be a frame layout, but I was just now trying out the constraint layout.
You can also use this library, it is working on all devices, no need to make any dimen file and all. but use this only for the size of widgets, for text size use "sp" only.
https://github.com/intuit/sdp
I have a ConstraintLayout where its height is wrap_content.
I want its height to be able to collapse or expanding, according to its Child's height. Simple and common enough, right?
Now I have a layout which looks like this:
(First of all, please ignore the abnormal super large margin at the bottom. As you can see, the margin is just 16dp but the preview renders a very large margin.)
My problem is, if the big rectangle's visibility is set to gone,
According to the documentation of ConstraintLayout, if I set its goneMarginTop to a certainValue, it will retain that margin even when its visibility is gone. So that my Request Date will have some space to the bottom of parent.
However, this does not work as expected. Request Date sticked to the bottom of its parent:
(This is again a broken preview. In my real app, I am able to see a complete Request Date)
Have I done something wrong? Here is my complete code:
<?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="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="#color/colorBasicGrey"
android:layout_marginBottom="2dp">
<View
android:id="#+id/item_indicator"
android:layout_width="8dp"
android:layout_height="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:background="#color/white"
android:layout_marginTop="24dp"
android:layout_marginLeft="24dp"/>
<TextView
android:id="#+id/group_member_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/group_member_join_group_request"
app:layout_constraintTop_toBottomOf="#id/item_indicator"
app:layout_constraintBottom_toTopOf="#id/item_indicator"
app:layout_constraintLeft_toRightOf="#id/item_indicator"
android:layout_marginLeft="8dp"
style="#style/general45"/>
<TextView
android:id="#+id/group_member_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="#style/general45"
android:textAllCaps="true"
app:layout_constraintBaseline_toBaselineOf="#id/group_member_label"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="180dp"
tools:text="ABCDEFGHIJK"/>
<TextView
android:id="#+id/group_request_date_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/group_member_request_date_label"
app:layout_constraintTop_toBottomOf="#id/group_member_label"
app:layout_constraintLeft_toLeftOf="#id/group_member_label"
android:layout_marginTop="8dp"
style="#style/general45"/>
<TextView
android:id="#+id/group_request_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="#style/general45"
android:textAllCaps="true"
app:layout_constraintBaseline_toBaselineOf="#id/group_request_date_label"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="180dp"
tools:text="28/10/2017"/>
<LinearLayout
android:id="#+id/admin_button_container"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginBottom="16dp"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:layout_marginTop="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#id/group_request_date_label"
app:layout_constraintVertical_bias="0.0"
app:layout_goneMarginTop="16dp"
app:layout_goneMarginLeft="24dp"
app:layout_goneMarginRight="24dp"
app:layout_goneMarginBottom="0dp"
android:visibility="gone">
<!--To simplify the question, I hided elements inside this LinearLayout-->
</LinearLayout>
</android.support.constraint.ConstraintLayout>
Figured it out.
goneMargin is used to indicate the margin to a GONE target, instead of itself being GONE.
Therefore in fact I should put the goneMargin attributes in the Request Date instead of the large rectangle.
A common use for LinearLayout is to evenly space (weight) views, for example:
How do you implement evenly spaced views like this using the new ConstraintLayout?
ConstraintLayout links for reference: blog post, I/O session video
There are two ways to accomplish this using ConstraintLayout: Chains and Guidelines. To use Chains, make sure you are using ConstraintLayout Beta 3 or newer and if you want to use the visual layout editor in Android Studio, make sure you are using Android Studio 2.3 Beta 1 or newer.
Method 1 - Using Chains
Open the layout editor and add your widgets as normal, adding parent constraints as needed. In this case, I have added two buttons with constraints to the bottom of the parent and side of the parent (left side for Save button and right side for Share button):
Note that in this state, if I flip to landscape view, the views do not fill the parent but are anchored to the corners:
Highlight both views, either by Ctrl/Cmd clicking or by dragging a box around the views:
Then right-click on the views and choose "Center Horizontally":
This sets up a bi-directional connection between the views (which is how a Chain is defined). By default the chain style is "spread", which is applied even when no XML attribute is included. Sticking with this chain style but setting the width of our views to 0dp lets the views fill the available space, spreading evenly across the parent:
This is more noticeable in landscape view:
If you prefer to skip the layout editor, the resulting XML will look like:
<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">
<Button
android:id="#+id/button_save"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/button_save_text"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="4dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="#+id/button_share"
app:layout_constraintHorizontal_chainStyle="spread" />
<Button
android:id="#+id/button_share"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/button_share_text"
android:layout_marginStart="4dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintLeft_toRightOf="#+id/button_save"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout>
Details:
setting the width of each item to 0dp or MATCH_CONSTRAINT lets the views fill the parent (optional)
the views must be linked together bidirectionally (right of save button links to share button, left of share button links to save button), this will happen automatically via the layout editor when choosing "Center Horizontally"
the first view in the chain can specify the chain style via layout_constraintHorizontal_chainStyle, see the documentation for various chain styles, if the chain style is omitted, the default is "spread"
the weighting of the chain can be adjusted via layout_constraintHorizontal_weight
this example is for a horizontal chain, there are corresponding attributes for vertical chains
Method 2 - Using a Guideline
Open your layout in the editor and click the guideline button:
Then select "Add Vertical Guideline":
A new guideline will appear, that by default, will likely be anchored to the left in relative values (denoted by left-facing arrow):
Click the left-facing arrow to switch it to a percentage value, then drag the guideline to the 50% mark:
The guideline can now be used as an anchor for other views. In my example, I attached the right of the save button and the left of the share button to the guideline:
If you want the views to fill up the available space then the constraint should be set to "Any Size" (the squiggly lines running horizontally):
(This is the same as setting the layout_width to 0dp).
A guideline can also be created in XML quite easily rather than using the layout editor:
<android.support.constraint.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/guideline"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
To create 2 views in same line, equal width, just need to define
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:id="#+id/button1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button 1"
app:layout_constraintEnd_toStartOf="#+id/button2"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="#+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button 2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/button1" />
</android.support.constraint.ConstraintLayout>
Note
width = 0dp (MATCH_CONSTRAINT)
Constraint of button1 and button2 must like above
Result
MORE
If you want View1 bigger than View2 you can use weight or percent.
Example, View1 width = 2 *View2 width use weight
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button
android:id="#+id/button3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button 3"
app:layout_constraintEnd_toStartOf="#+id/button4"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toStartOf="parent"
/>
<Button
android:id="#+id/button4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button 4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="#+id/button3"
/>
</android.support.constraint.ConstraintLayout>
Result
Example, View1 width = 2 *View2 width use percent
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button
android:id="#+id/button5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button 5"
app:layout_constraintEnd_toStartOf="#+id/button6"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_percent="0.667"
/>
<Button
android:id="#+id/button6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button 6"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/button5"
app:layout_constraintWidth_percent="0.333"
/>
</android.support.constraint.ConstraintLayout>
Result
Well if it helps someone
the key is here app:layout_constraintHorizontal_weight="1" and
the best thing about constraint layout is that it supports circular dependency and here this is what I have done using exactly that.
For first child
app:layout_constraintEnd_toStartOf="#+id/textInputSecondChild"
For second child
app:layout_constraintLeft_toRightOf="#+id/textInputFirstChild"
here is the complete demo
<android.support.design.widget.TextInputLayout
android:id="#+id/textInputParent"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<EditText
android:id="#+id/editTextParent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/state" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="#+id/textInputFirstChild"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="#+id/textInputSecondChild"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textInputParent">
<EditText
android:id="#+id/editTextChildOne"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/pin_code" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="#+id/textInputSecondChild"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toRightOf="#+id/textInputFirstChild"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textInputParent">
<EditText
android:id="#+id/editTextChildSecond"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/country" />
</android.support.design.widget.TextInputLayout>
You shoul read about weighted chains. An example of code is here.
<android.support.constraint.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="wrap_content"
>
<TextView
android:id="#+id/figure_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:layout_constraintEnd_toStartOf="#id/figure_2"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
tools:text="1"
/>
<TextView
android:id="#+id/figure_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:layout_constraintEnd_toStartOf="#id/figure_3"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="#id/figure_1"
tools:text="2"
/>
<TextView
android:id="#+id/figure_3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:layout_constraintEnd_toStartOf="#id/figure_4"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="#id/figure_2"
tools:text="3"
/>
<TextView
android:id="#+id/figure_4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="#id/figure_3"
tools:text="4"
/>
</android.support.constraint.ConstraintLayout>
So, set android:layout_width="0dp", app:layout_constraintHorizontal_weight="1" and link every view with neighbours like:
app:layout_constraintStart_toEndOf="#id/figure_2"
app:layout_constraintEnd_toStartOf="#id/figure_4"
Once you have your chained items, you can still use weights on them like relative layout to keep them evenly spaced. The example below shows how to keep them evenly spaced with different size textViews.
<TextView1
app:layout_constraintHorizontal_weight="1" />
<TextView2
app:layout_constraintHorizontal_weight="1" />
<TextView3
app:layout_constraintHorizontal_weight="1" />
<TextView4
app:layout_constraintHorizontal_weight="1" />