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.
4 Answers
Answers 1
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>
Answers 2
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:
Answers 3
You cannot compare the two measure specs, as they are not simply a size. You can see a very good explanation in this answer. This answer is for a custom view, but measure specs are the same. You need to get the mode and the size to compute final sizes, and compare the end results for both dimensions.
In the second example you shared, the right question is this one (third answer). Is written for Xamarin in C#, but is easy to understand.
The case that is failing for you is because you're finding an AT_MOST
mode (when the view is hitting the bottom of the screen), that's why comparisons are failing in this case.
That should be the final method (can contain typos, I have been unable to test it:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width, height; switch (widthMode) { case MeasureSpec.EXACTLY: width = widthSize; break; case MeasureSpec.AT_MOST: width = Math.min(widthSize, heightSize); break; default: width = 100; break; } switch (heightMode) { case MeasureSpec.EXACTLY: height = heightSize; break; case MeasureSpec.AT_MOST: height = Math.min(widthSize, heightSize); break; default: height = 100; break; } var size = Math.min(width, height); var newMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); super.onMeasure(newMeasureSpec, newMeasureSpec); }
I expect the end result to be roughly like this (maybe centered, but this dimensions):
Notice that this is a made up image done with Gimp.
Answers 4
try this. You can use on measure method to make a custom view. Check the link below for more details.
http://codecops.blogspot.in/2017/06/how-to-make-responsive-imageview-in.html
0 comments:
Post a Comment