How to set the height equal to its width? - android

I would like to make a board game like chess (8x8 cell board).
As the goal, I hope my application runs on several smartphones like screen dots (= 720px X 1280px, 1024px X 1920px, 1024 X 2340px).
The attached photo is my game board, and make the height equal to its width by input px value. In this result, 1024px X 1920px and 1024 X 2340px are OK.
Of cource, not for 720px X 1280px smartphones.
Could you give me your technics to control TableLayout's height equal to its width ?
<TableLayout
android:id="#+id/TableLayout"
android:layout_width="1080px"
android:layout_height="1080px"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
enter image description here

For fixed number of boxes/grids, I would recommend using GridLayout or RecyclerView with GridLayoutManager. Following is an example of GridLayout for a 2 X 2 case:
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="2"> //specify number of column each row
<TextView
android:layout_columnWeight="1" //【important!! Not "layout_rowWeight"!!】
android:gravity="center" //center content
android:text="Sam"
android:textColor="#color/black"
android:textSize="30sp" />
<TextView
android:layout_columnWeight="1"
android:gravity="center"
android:text="Sam"
android:textColor="#color/black"
android:textSize="30sp" />
<TextView
android:layout_columnWeight="1"
android:gravity="center"
android:text="Sam"
android:textColor="#color/black"
android:textSize="30sp" />
<TextView
android:layout_columnWeight="1"
android:gravity="center"
android:text="Sam"
android:textColor="#color/black"
android:textSize="30sp" />
</GridLayout>
However, you will notice that its height is not equal to the width, that's because we use match_parent for the GridLayout to take the full width of the screen, but we use wrap_content for its height. So, set match_parent to the height? No good. Use hard code dp? No good. Well, turns out that there is an amazing attribute called layout_constraintDimensionRatio for the ConstraintLayout:
<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">
<GridLayout
android:layout_width="match_parent"
android:layout_height="0dp" //must be 0dp for either width or height
app:layout_constraintDimensionRatio="1:1" //make the ratio 1:1
android:columnCount="2"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
...
</GridLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
There you go.

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.

Android - Constraint Layout - margins vs layout_constraintWidth_percent

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.

How to set maxHeight over DimensionRatio with ConstraintLayout?

I'm trying to display an image centered in parent with a dimension ratio of 1220:1000 AND a maximum height of 300dp (to keep the image small even with large screen)
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.facebook.drawee.view.SimpleDraweeView
android:id="#+id/image"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="10dp"
android:adjustViewBounds="true"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1220:1000"
app:layout_constraintHeight_max="200dp" ==> This line break the ratio (the image is not displayed)
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
The app:layout_constraintHeight_max property break the ratio.
Is there is a way to do it?
If you want to enforce max_height and keep the dimension ratio at the same time, you need to constrain the width based on height. You can achieve that by adding W to your ratio:
app:layout_constraintDimensionRatio="W,1220:1000"
This will constrain the height first and then set the width accordingly to satisfy the ratio.
More info on how this dimension ratio works can be found in the documentation.
If you want to maximize the area with a dimension ratio and a max height, but you don't know if width or height is going to be adjusted, better don't to use layout_constraintDimensionRatio + layout_constraintHeight_max. They don't work well together.
Here I put my solution in the case you have an image 16:9 which will use full width while max height is not yet reached, otherwise it will respect max height, and width will be adjusted:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- This space view makes ImageView expands to its corresponding height -->
<Space
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="#+id/imageview" />
<com.a3.sgt.ui.widget.AspectRatioImageView
android:id="#+id/imageview"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:adjustViewBounds="true"
app:dominantMeasurement="height"
app:aspectRatioEnabled="true"
app:aspectRatio="1.778"
android:scaleType="centerCrop"
app:layout_constraintHeight_max="400dp"
tools:src="#drawable/placeholder_glacier"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
I used a custom ImageView I had in my gist which adjusts to aspect ratio, in order to avoid using layout_constraintDimensionRatio which causes the problem together with layout_constraintHeight_max
Have you tried to add the tag maxHeight?
android:maxHeight="200dp"

ConstraintLayout aspect ratio

Consider the following layout file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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.support.constraint.ConstraintLayout
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF0000"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin">
<ImageView
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#0000FF"
android:padding="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintDimensionRatio="H,3:1"
tools:layout_editor_absoluteX="16dp" />
</android.support.constraint.ConstraintLayout>
</RelativeLayout>
I am not sure how the app:layout_constraintDimensionRatio works. My understanding is the ratio will always be width:height. So 3:1 will always make the ImageView appear 3 times wider than height. The prefix H or W tells ConstraintLayout which dimension should respect the ratio. If it is H then it means width will be first computed from other constraints and then height will be adjusted according to the aspect ratio. However this is the result of the layout:
The height is 3 times larger than width which is unexpected. Can anyone explain to me how the dimensions are computed with respect to app:layout_constraintDimensionRatio setting?
Your understanding for the way app:layout_constraintDimensionRatio works is correct. If you set app:layout_constraintDimensionRatio="H,3:1" then it means width will be first computed from other constraints and then height will be adjusted according to the aspect ratio. The only problem with your implementation is that you added app:layout_constraintBottom_toBottomOf="parent" to the ImageView, so that it caused app:layout_constraintDimensionRatio to be ignored.
Here's the layout to size your ImageView in 3:1 aspect ratio:
<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"
android:background="#FF0000">
<ImageView
android:id="#+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="#0000FF"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintDimensionRatio="H,3:1" />
</android.support.constraint.ConstraintLayout>
and here's the result view:
Basically, we have
layout_constraintDimensionRatio(width:height)
EXAMPLE
<!-- button which have width = it's content and height = 1/2 width -->
<Button
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent" <!-- I still think that we don't need this attribute but I when I don't add this, constraint not working -->
android:text="Button TEST RATIO 1"
app:layout_constraintDimensionRatio="2:1" />
Output
<!-- button which have width = it's content and height = 1/2 width -->
<Button
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
android:text="Button TEST RATIO 2"
app:layout_constraintDimensionRatio="2" /> <!-- 2 here <=> 2:1 <=> 2/1 (1:1 <=> 1, 1/2 <=> 0.5, ....) ->
Output
<!-- button which have width = match_parent and height = 1/2 width -->
<Button
android:layout_width="match_parent"
android:layout_height="0dp"
android:text="Button TEST RATIO 3"
app:layout_constraintDimensionRatio="2" />
Output
<!-- button which have width = match constraint and height = 1/2 width -->
<Button
android:layout_width="0dp"
android:layout_height="0dp"
android:text="Button TEST RATIO 4"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintDimensionRatio="2" />
Output
DEMO: https://github.com/PhanVanLinh/AndroidConstraintLayoutRatio
Take a look at these ImageView properties:
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
These properties override the layout_constraintDimensionRatio due to which the ImageView is constrained to the bottom, top and left of the main parent resulting in the View occupying the left, top and bottom portions of the main screen.
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
This would be one solution. You can omit layout_constraintBottom_toBottomOf if you want the View to appear on the top or vice-versa. It would probably be best to remove all the above constraints altogether except the layout_constraintDimensionRatio, which would be the most recommended solution.

LinearLayout weights

I have two linear layouts that have the same width - one with two children and one with three. I'm trying to make the last two children the same width but I can't wrap my head around why Android behaves this way. I finally got it to look the way I want it to with the following weights:
Can anyone explain to me why this is working out this way?
I tried to do some simple math to figure out why. I'm guessing it's the sum of weights, minus the weight of the child, divided by the sum of weights, times the width of the parent. So:
sum = 1 + 6
((sum - 6) / sum) * W = 14.3% * W
I tried the same algorithm for the second row but it was totally off:
sum = 1 + 1 + 1.5
((sum - 1.5) / sum) * W = 57.1% * W
UPDATE
The above is true only when the child widths are set to match_parent. Setting width to 0, or 0dp, actually behaves as expected - greater weights lead to more space allocated for the child. The algorithm makes a little more sense now and works as expected for both rows.
sum = 1 + 6
(1 / sum) * W = 14.3% * W
Is the former behavior for weights with child widths set to match_parent by design? If so, what is the approximate algorithm for calculating the width of the children?
You can specify weightSum attribute for your LinearLayout in XML to let's say 1 for your first layout. Then your first 2 TextViews would get approx 0.9 and 0.1 weights (like 90% and 10%)
The same thing needs to be applied to the second LinearLayout. Something like:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="1"
android:background="#FF0000FF" >
<TextView
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight=".9"
android:layout_margin="3dp"
android:background="#FFFFFFFF"
android:text="TextView" />
<TextView
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight=".1"
android:layout_margin="3dp"
android:background="#FFFFFFFF"
android:text="TextView" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:weightSum="1"
android:background="#FF0000FF" >
<TextView
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:layout_weight=".45"
android:background="#FFFFFFFF"
android:text="TextView" />
<TextView
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight=".45"
android:layout_margin="3dp"
android:background="#FFFFFFFF"
android:text="TextView" />
<TextView
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight=".1"
android:layout_margin="3dp"
android:background="#FFFFFFFF"
android:text="TextView" />
</LinearLayout>
BTW, don't forget to set your width attributes to 0dip
First:
Based on the look of your layout, it looks like you'd want to use a GridLayout instead of two LinearLayouts.
Back to your question:
Don't set our layout_width to wrap_content (and especially not match_parent!) in your case.
Set the layout_width to 0dp and let the layout_weight attribute distribute the available horzontal space.
The layout_weight attribute distributes/adds additional space to the specified layout_width (or layout_height for vertical oriented LinearLayouts). If you want only the layout_weight to contribute to the spacing, set layout_width to 0dp (layout_height to 0dp in case of vertically oriented LinearLayout).
Try this first and see if this behaves more to what you'd like and expect.
Try this example (where the grids are ratio 3:3:1):
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FF0000FF" >
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="6"
android:background="#FFFFFFFF"
android:text="TextView" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#FFFFFFFF"
android:text="TextView" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:background="#FF0000FF" >
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:background="#FFFFFFFF"
android:text="TextView" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:background="#FFFFFFFF"
android:text="TextView" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#FFFFFFFF"
android:text="TextView" />
</LinearLayout>
According to your picture - weights are incorrect. E.g first "row": you have set first TextView with weight '1' and second weight is '6' - this means that second TextView will have more space - 6/7 of all layout width and first TextView have only 1/7. To simplify calculations imagine total width as 100(%) and divide them between views according to proportions which you want to give them.

Categories

Resources