I stumbled across this problem when working with custom Square Layout
: by extending the Layout and overriding its onMeasure()
method to make the dimensions = smaller of the two (height or width).
Following is the custom Layout code :
public class CustomSquareLayout extends RelativeLayout{
public CustomSquareLayout(Context context) {
super(context);
}
public CustomSquareLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomSquareLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public CustomSquareLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//Width is smaller
if(widthMeasureSpec < heightMeasureSpec)
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
//Height is smaller
else
super.onMeasure(heightMeasureSpec, heightMeasureSpec);
}
}
The custom Square Layout works fine, until in cases where the custom layout goes out of bound of the screen. What should have automatically adjusted to screen dimensions though, doesn't happen. As seen below, the CustomSquareLayout
actually extends below the screen (invisible). What I expect is for the onMeasure
to handle this, and give appropriate measurements. But that is not the case. Note of interest here is that even thought the CustomSquareLayout
behaves weirdly, its child layouts all fall under a Square shaped layout that is always placed on the Left hand side.
<!-- XML for above image -->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="300dp"
android:text="Below is the Square Layout"
android:gravity="center"
android:id="@+id/text"
/>
<com.app.application.CustomSquareLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/text"
android:background="@color/colorAccent" #PINK
android:layout_centerInParent="true"
android:id="@+id/square"
android:padding="16dp"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" #Note this
android:background="@color/colorPrimaryDark" #BLUE
>
</RelativeLayout>
</com.app.application.CustomSquareLayout>
</RelativeLayout>
Normal case : (Textview is in Top)
Following are few links I referenced:
Hope to find a solution to this, using onMeasure
or any other function when extending the layout (so that even if some extends the Custom Layout, the Square property remains)
Edit 1 : For further clarification, the expected result for 1st case is shown
Edit 2 : I gave a preference to onMeasure()
or such functions as the need is for the layout specs (dimensions) to be decided earlier (before rendering). Otherwise changing the dimensions after the component loads is simple, but is not requested.
You can force a square view by checking for "squareness" after layout. Add the following code to onCreate()
.
final View squareView = findViewById(R.id.square);
squareView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
squareView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
if (squareView.getWidth() != squareView.getHeight()) {
int squareSize = Math.min(squareView.getWidth(), squareView.getHeight());
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) squareView.getLayoutParams();
lp.width = squareSize;
lp.height = squareSize;
squareView.requestLayout();
}
}
});
This will force a remeasurement and layout of the square view with a specified size that replaces MATCH_PARENT
. Not incredibly elegant, but it works.
You can also add a PreDraw
listener to your custom view.
onPreDraw
boolean onPreDraw ()
Callback method to be invoked when the view tree is about to be drawn. At this point, all views in the tree have been measured and given a frame. Clients can use this to adjust their scroll bounds or even to request a new layout before drawing occurs.
Return true to proceed with the current drawing pass, or false to cancel.
Add a call to an initialization method in each constructor in the custom view:
private void init() {
this.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (getWidth() != getHeight()) {
int squareSize = Math.min(getWidth(), getHeight());
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
lp.width = squareSize;
lp.height = squareSize;
requestLayout();
return false;
}
return true;
}
});
}
The XML can look like the following:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="300dp"
android:gravity="center"
android:text="Below is the Square Layout" />
<com.example.squareview.CustomSquareLayout
android:id="@+id/square"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/text"
android:background="@color/colorAccent"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:background="@color/colorPrimaryDark" />
</com.example.squareview.CustomSquareLayout>
</RelativeLayout>
There is a difference between the view's measured width and the view's width (same for height). onMeasure
is only setting the view's measured dimensions. There is still a different part of the drawing process that constrains the view's actual dimensions so that they don't go outside the parent.
If I add this code:
final View square = findViewById(R.id.square);
square.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
System.out.println("measured width: " + square.getMeasuredWidth());
System.out.println("measured height: " + square.getMeasuredHeight());
System.out.println("actual width: " + square.getWidth());
System.out.println("actual height: " + square.getHeight());
}
});
I see this in the logs:
09-05 10:19:25.768 4591 4591 I System.out: measured width: 579 09-05 10:19:25.768 4591 4591 I System.out: measured height: 579 09-05 10:19:25.768 4591 4591 I System.out: actual width: 768 09-05 10:19:25.768 4591 4591 I System.out: actual height: 579
How to solve it by creating a custom view? I don't know; I never learned. But I do know how to solve it without having to write any Java code at all: use ConstraintLayout
.
ConstraintLayout supports the idea that children should be able to set their dimensions using an aspect ratio, so you can simply use a ratio of 1
and get a square child. Here's my updated layout (the key piece is the app:layout_constraintDimensionRatio
attr):
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="300dp"
android:gravity="center"
android:text="Below is the Square Layout"
app:layout_constraintTop_toTopOf="parent"/>
<RelativeLayout
android:id="@+id/square"
android:layout_width="match_parent"
android:layout_height="0dp"
android:padding="16dp"
android:background="@color/colorAccent"
app:layout_constraintTop_toBottomOf="@+id/text"
app:layout_constraintDimensionRatio="1">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:background="@color/colorPrimaryDark">
</RelativeLayout>
</RelativeLayout>
</android.support.constraint.ConstraintLayout>
And screenshots:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With