I tried to refer[https://medium.com/#nikhil4092/how-to-have-a-height-wrapping-viewpager-when-images-have-variable-heights-on-android-60b18e55e72e] this link for making the viewpager's height wrap_content but it didn't work.I've tried several questions of stackoverflow but none of them could address my problem.When I'm giving the height as wrap_content nothing is shown
Code:
public class HeightWrappingViewPager extends ViewPager {
public HeightWrappingViewPager(Context context) {
super(context);
}
public HeightWrappingViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
// Unspecified means that the ViewPager is in a ScrollView WRAP_CONTENT.
// At Most means that the ViewPager is not in a ScrollView WRAP_CONTENT.
if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
// super has to be called in the beginning so the child views can be initialized.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if (h > height) height = h;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
// super has to be called again so the new specs are treated as exact measurements
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
XML:
<com.project.test.HeightWrappingViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
I'm not sure is it helps you.
I used the code below to images with different height.
Almost like your code, but i save height like a field.
public class MeasuredViewPager extends ViewPager {
private int mMaxHeight = 0;
public MeasuredViewPager(Context context) {
super(context);
}
public MeasuredViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if (h > mMaxHeight) mMaxHeight = h;
}
if (mMaxHeight != 0) heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Related
I have a custom ViewPager inside of an RecyclerView. I made wrap_content height of ViewPager and put onMeasure() function inside of CustomViewPager. But when I scroll down and up the RecyclerView, sometimes ViewPager is disappear. I look many SO answers but onMeasure() function not working every time for me. Method:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); //Deneme
int height = 0;
for(int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if(h > height) height = h;
}
if (height != 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
ViewPager XML:
<com.monster.vpagerapp.Utils.ViewPagerFixedPost
android:id="#+id/postsViewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Also I use this in constructor of RecylerView:
setHasStableIds(true);
And use this in MainFragment
recyclerView.getRecycledViewPool().setMaxRecycledViews(1, 0); //for unnecessary refresh
I've read many other SO answers but nothing seems to be what I want. What I want is a ViewPager inside a ScrollView with the heights of each page being appropriate for the content. Some of the more accepted answers on SO seem to have to take the max height of the children in the ViewPager but that leads for empty space.
WrapContentViewPager
public class WrapContentViewPager extends ViewPager {
public WrapContentViewPager(Context context) {
super(context);
}
public WrapContentViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = 0;
View view = null;
for(int i = 0; i < getChildCount(); i++) {
view = getChildAt(i);
view.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = view.getMeasuredHeight();
if(h > height) height = h;
}
if (height != 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), measureHeight(heightMeasureSpec, view));
}
/**
* Determines the height of this view
*
* #param measureSpec A measureSpec packed into an int
* #param view the base view with already measured height
*
* #return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec, View view) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
// set the height from the base view if available
if (view != null) {
result = view.getMeasuredHeight();
}
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
}
The reason why this doesn't work is because if I go to the third page, which has a large height with 32 items in the list, then go back to the second page with only 3 items, there is a lot of empty space in the second page since it took the height of the third page as max.
I've tried WCViewPager library on GitHub at https://github.com/rnevet/WCViewPager and it doesn't work for me as well.
Could someone guide me to a correct solution? I am using a ViewPager with just PagerAdapter, not FragmentPagerAdapter by the way.
UPDATE
I figured out a better way to solve it.
`public class WrapContentViewPager extends ViewPager {
public WrapContentViewPager(Context context) {
super(context);
}
public WrapContentViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
// Unspecified means that the ViewPager is in a ScrollView WRAP_CONTENT.
// At Most means that the ViewPager is not in a ScrollView WRAP_CONTENT.
if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
// super has to be called in the beginning so the child views can be initialized.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int position = getCurrentItem();
View child = this.findViewWithTag("view"+position);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int height = child.getMeasuredHeight();
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
// super has to be called again so the new specs are treated as exact measurements
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}`
and in my PagerAdapter I changed the method instantiateItem
#Override
public #NonNull Object instantiateItem(#NonNull ViewGroup view, int position) {
View layout = inflater.inflate(R.layout.layout, view, false);
layout.setTag("view" + position);
}
This solution remeasures the height at the current page every time it is moved. getChildAt(getCurrentItem()) gives different results so it's not reliable.
I would like to create a custom RelativeLayout that has two views in one row: one on the left side of the screen (android:layout_alignParentStart="true") and one on the right (android:layout_alignParentEnd="true"). The view on the right will grow toward the left view until it takes up all the space between the two views. Then it will move to a new line under the view on the left.
I have implemented a slightly modified version of Romain Guy's FlowLayout that extends RelativeLayout. However, this class seems to ignore the RelativeLayout's align properties and just sticks the views right next to each other. Is there a way to implement a such a layout that will anchor the views to the left and right?
FlowLayout class:
public class FlowLayout extends RelativeLayout {
private int mHorizontalSpacing;
private int mVerticalSpacing;
public FlowLayout(Context context) {
super(context);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
mHorizontalSpacing = attributes.getDimensionPixelSize(R.styleable
.FlowLayout_horizontalSpacing, 0);
mVerticalSpacing = attributes.getDimensionPixelSize(R.styleable
.FlowLayout_verticalSpacing, 0);
attributes.recycle();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int width = 0;
int height = getPaddingTop();
int currentWidth = getPaddingStart();
int currentHeight = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
measureChild(child, widthMeasureSpec, heightMeasureSpec);
if (currentWidth + child.getMeasuredWidth() > widthSize) {
height += currentHeight + mVerticalSpacing;
currentHeight = 0;
width = Math.max(width, currentWidth);
currentWidth = getPaddingEnd();
}
int spacing = mHorizontalSpacing;
if (lp.spacing > -1) {
spacing = lp.spacing;
}
lp.x = currentWidth + spacing;
lp.y = currentHeight;
currentWidth += child.getMeasuredWidth();
currentHeight = Math.max(currentHeight, child.getMeasuredHeight());
}
width += getPaddingEnd();
height += getPaddingBottom();
setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height,
heightMeasureSpec));
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child
.getMeasuredHeight());
}
}
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
#Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout
.LayoutParams.WRAP_CONTENT);
}
#Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p.width, p.height);
}
#Override
public RelativeLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends RelativeLayout.LayoutParams {
public int spacing;
public int x;
public int y;
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable
.FlowLayout_LayoutParams);
spacing = attributes.getDimensionPixelSize(R.styleable
.FlowLayout_LayoutParams_layoutSpacing, -1);
attributes.recycle();
}
public LayoutParams(int width, int height) {
super(width, height);
}
}
}
It turns out that rather than calculating the right view's new position yourself, you can change its LayoutParams and have the OS handle positioning for you. I created a custom layout that extends RelativeLayout and overrides the onMeasure() method. This will adjust the LayoutParams accordingly.
More specifically:
Call the super method then find the widths of the two views and their parent in onMeasure(). Use these to figure out if the right view will overlap the left view. If so, change the right view's layout_alignParentEnd="true" property to be layout_alignParentStart="true" and give it the layout_below="#id/left_view" property. Do the opposite when there will be no overlap. Call the super method again to have the OS remeasure the views for you.
The layout class:
public class WrappingLayout extends RelativeLayout {
private TextView leftView;
private EditText rightView;
//Use this to prevent unnecessarily adjusting the LayoutParams
//when the right view is already in the correct position
private boolean isMultiline = false;
public WrappingLayout(Context context) {
super(context);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.wrapping_layout, this);
leftView = (TextView) findViewById(R.id.left_view);
rightView = (EditText) findViewById(R.id.right_view);
}
public WrappingLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.wrapping_layout, this);
leftView = (TextView) findViewById(R.id.left_view);
rightView = (EditText) findViewById(R.id.right_view);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//Call first to make sure the views' initial widths have been set
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int screenWidth = getMeasuredWidth();
int leftViewWidth = getPaddingStart() + leftView.getMeasuredWidth() + leftView.getPaddingEnd();
int rightViewWidth = getPaddingEnd() + rightView.getMeasuredWidth() + rightView.getPaddingStart();
LayoutParams rightViewParams = (LayoutParams) rightView.getLayoutParams();
if (!isMultiline && rightViewWidth + leftViewWidth > screenWidth) {
isMultiline = true;
rightViewParams.addRule(BELOW, R.id.left_view);
rightViewParams.removeRule(ALIGN_PARENT_END);
rightViewParams.addRule(ALIGN_PARENT_START);
//Call again here to adjust dimensions for new params
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else if (isMultiline && rightViewWidth + leftViewWidth < screenWidth) {
isMultiline = false;
rightViewParams.removeRule(BELOW);
rightViewParams.addRule(ALIGN_PARENT_END);
rightViewParams.removeRule(ALIGN_PARENT_START);
//Call again here to adjust dimensions for new params
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
The layout XML:
<?xml version="1.0" encoding="utf-8"?>
<merge
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/left_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"/>
<EditText
android:id="#id/right_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:gravity="center"
android:text="#string/hello"/>
</merge>
I wrote the following ViewGroup
public class myViewGroup extends ViewGroup{
List<View> qResult;
List<Point> qLoc;
ImageView qImage;
public QueryViewLayout(Context context){
super(context);
}
public QueryViewLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public QueryViewLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
qResult = new LinkedList<View>();
qLoc = new LinkedList<Point>();
qImage = null;
}
public void addMainView(ImageBorderView view){
qImage = view;
super.removeAllViews();
super.addView(view);
}
public void addResultView(View result, Point loc){
super.addView(result);
qResult.add(result);
qLoc.add(loc);
}
/**
* Any layout manager that doesn't scroll will want this.
*/
#Override
public boolean shouldDelayChildPressedState() {
return false;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// Measurement will ultimately be computing these values.
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// Only main view affects the layouts measure
if (qImage != null) {
if (qImage.getVisibility() != GONE) {
// Measure the child.
qImage.measure(widthMeasureSpec, heightMeasureSpec);
maxWidth = qImage.getMeasuredWidth();
maxHeight = qImage.getMeasuredHeight();
childState = qImage.getMeasuredState();
}
}
for (View child:qResult){
if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED)
child.measure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
}
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Report our final dimensions.
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
int parentLeft = left + getPaddingLeft();
int parentRight = right - getPaddingRight();
final int parentTop = top + getPaddingTop();
final int parentBottom = bottom - getPaddingBottom();
if (qImage == null) return;
qImage.layout(parentLeft, parentTop, parentRight, parentBottom);
Iterator<Point> loc = qLoc.iterator();
for (View child:qResult) {
Point p = loc.next();
if (child.getVisibility() != GONE) {
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
Point locOnView = qImage.projectOnView(p);
width = (width < (int) Math.max(parentRight - (int) locOnView.x, locOnView.x - parentLeft)) ?
width : (parentLeft + parentRight)/2;
height = (height < (int) Math.max(parentBottom - (int) locOnView.y, locOnView.y - parentTop)) ?
height : (parentBottom + parentTop)/2;
int x = (width < (parentRight - (int) locOnView.x)) ? (int) locOnView.x : (parentRight - width);
int y = (height < parentBottom - (int) locOnView.y) ? (int) locOnView.y : (parentBottom - height);
// Place the child.
child.layout(x, y, x + width, y + height);
}
}
}
}
It is supposed to show some arbitrary view on top of an image, given a location for that view, when I use a GridView as the arbitrary view, even though I have defined a certain width for the GridView it is forced to have a width as large as the frame. In the measure phase I changed the mode to
MeasureSpec.AT_MOST
for both width and height of the overlay view, but this does not seem to work, can someone please help.
here is the xml where I, inflate the GridView from
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/result_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:columnWidth="#dimen/result_view_column_width"
android:numColumns="2"
android:verticalSpacing="2dp"
android:horizontalSpacing="2dp"
android:stretchMode="none"
android:gravity="center"
android:layout_margin = "2dp"
android:background="#drawable/solid_with_shadow" />
After a lot of trial and error, replacing
child.measure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
with
measureChild(child, MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
worked for me, I am not sure why, but a wild guess would be calling measure on a child does not read all the xml props, but measureChild(child, ...) does.
I'm using a GridView to display a bunch of views which are essentially LinearLayouts. I want the LinearLayouts to all be square, but I also want them to be dynamically sized--that is, there are two columns and I want the LinearLayouts to stretch depending on the size of the screen but remain square. Is there a way to do this through the xml layout or do I have to set the heights and widths programmatically?
A neat solution for square GridView items is to extend RelativeLayout or LinearLayout and override onMeasure like so:
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
With the new ConstraintLayout introduced in Android Studio 2.3, it is now quite easy to build responsive layouts.
In a parent ConstraintLayout, to make any of its children view/layout dynamically square, add this attribute
app:layout_constraintDimensionRatio="w,1:1"
w is to specify width-wise constraints and 1:1 ratio ensures square layout.
I've done this way:
#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 size;
if(widthMode == MeasureSpec.EXACTLY && widthSize > 0){
size = widthSize;
}
else if(heightMode == MeasureSpec.EXACTLY && heightSize > 0){
size = heightSize;
}
else{
size = widthSize < heightSize ? widthSize : heightSize;
}
int finalMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(finalMeasureSpec, finalMeasureSpec);
}
With this implementation, your layout will be square, assuming the lower size between width and height. And it can even be set with dynamic values, like using weight inside a LinearLayout.
There's nothing in the xml that will let you link the width and height properties. Probably the easiest thing to do is to subclass LinearLayout and override onMeasure
#Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int size = width > height ? height : width;
setMeasuredDimension(size, size);
}
I've used this to create views that are always square before. It should still work for a LinearLayout.
More info that will help doing this:
http://developer.android.com/guide/topics/ui/custom-components.html
http://developer.android.com/reference/android/view/View.MeasureSpec.html
We can do it with a very simple way - just call super.onMeasure() twice.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int squareLen = Math.min(width, height);
super.onMeasure(
MeasureSpec.makeMeasureSpec(squareLen, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(squareLen, MeasureSpec.EXACTLY));
}
By calling super.onMeasure() twice, this is less efficient in terms of the drawing process, but it is a simple way to fix layout issues that the other answers can cause.
It is as simple as:
public class SquareRelativeLayout extends RelativeLayout {
public SquareRelativeLayout(Context context) {
super(context);
}
public SquareRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
if (widthMeasureSpec < heightMeasureSpec)
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
else
super.onMeasure(heightMeasureSpec, heightMeasureSpec);
}
}
Add the following line in XML:
app:layout_constraintDimensionRatio="1:1"
Here's a solution that works for all layout parameters that can be set to view or viewgroup:
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int widthDesc = MeasureSpec.getMode(widthMeasureSpec);
int heightDesc = MeasureSpec.getMode(heightMeasureSpec);
int size = 0;
if (widthDesc == MeasureSpec.UNSPECIFIED
&& heightDesc == MeasureSpec.UNSPECIFIED) {
size = DP(defaultSize); // Use your own default size, in our case
// it's 125dp
} else if ((widthDesc == MeasureSpec.UNSPECIFIED || heightDesc == MeasureSpec.UNSPECIFIED)
&& !(widthDesc == MeasureSpec.UNSPECIFIED && heightDesc == MeasureSpec.UNSPECIFIED)) {
//Only one of the dimensions has been specified so we choose the dimension that has a value (in the case of unspecified, the value assigned is 0)
size = width > height ? width : height;
} else {
//In all other cases both dimensions have been specified so we choose the smaller of the two
size = width > height ? height : width;
}
setMeasuredDimension(size, size);
Cheers
My suggestion is to create a custom layout class that inherits from FrameLayout. Override the OnMeasure() method and put whatever control you want to be square inside that SquareFrameLayout.
This is how it's done in Xamarin.Android:
public class SquareFrameLayout : FrameLayout
{
private const string _tag = "SquareFrameLayout";
public SquareFrameLayout(Android.Content.Context context):base(context) {}
public SquareFrameLayout(IntPtr javaReference, Android.Runtime.JniHandleOwnership transfer):base(javaReference, transfer) {}
public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs):base(context, attrs) {}
public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs, int defStyleAttr):base(context, attrs, defStyleAttr) {}
public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes):base(context, attrs, defStyleAttr, defStyleRes) {}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
var widthMode = MeasureSpec.GetMode(widthMeasureSpec);
int widthSize = MeasureSpec.GetSize(widthMeasureSpec);
var heightMode = MeasureSpec.GetMode(heightMeasureSpec);
int heightSize = MeasureSpec.GetSize(heightMeasureSpec);
int width, height;
switch (widthMode)
{
case MeasureSpecMode.Exactly:
width = widthSize;
break;
case MeasureSpecMode.AtMost:
width = Math.Min(widthSize, heightSize);
break;
default:
width = 100;
break;
}
switch (heightMode)
{
case MeasureSpecMode.Exactly:
height = heightSize;
break;
case MeasureSpecMode.AtMost:
height = Math.Min(widthSize, heightSize);
break;
default:
height = 100;
break;
}
Log.Debug(_tag, $"OnMeasure({widthMeasureSpec}, {heightMeasureSpec}) => Width mode: {widthMode}, Width: {widthSize}/{width}, Height mode: {heightMode}, Height: {heightSize}/{height}");
var size = Math.Min(width, height);
var newMeasureSpec = MeasureSpec.MakeMeasureSpec(size, MeasureSpecMode.Exactly);
base.OnMeasure(newMeasureSpec, newMeasureSpec);
}
}
If you want a View (or any other control) to be square (and centered) just add it to your layout the following way:
<your.namespace.SquareFrameLayout
android:id="#+id/squareContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center">
<View
android:id="#+id/squareContent"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</your.namespace.SquareFrameLayout>
Check out SquareLayout, an Android Library which provides a wrapper class for different Layouts, rendering them Squared dimensioned without losing any core functionalities.
The dimensions are calculated just before the Layout is rendered, hence there is no re-rendering or anything as such to adjust once the View is obtained.
To use the Library, add this to your build.gradle:
repositories {
maven {
url "https://maven.google.com"
}
}
dependencies {
compile 'com.github.kaushikthedeveloper:squarelayout:0.0.3'
}
The one you require is SquareLinearLayout.
For anyone wants solution With Kotlin, here's what I did with FrameLayout.
package your.package.name
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
class SquareLayout: FrameLayout {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (widthMeasureSpec < heightMeasureSpec)
super.onMeasure(widthMeasureSpec, widthMeasureSpec)
else
super.onMeasure(heightMeasureSpec, heightMeasureSpec)
}
}
Try this code:
public class SquareRelativeLayout extends RelativeLayout {
public SquareRelativeLayout(Context context) {
super(context);
}
public SquareRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#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 size;
if (widthMode == MeasureSpec.EXACTLY && widthSize > 0) {
size = widthSize;
} else if (heightMode == MeasureSpec.EXACTLY && heightSize > 0) {
size = heightSize;
} else {
size = widthSize < heightSize ? widthSize : heightSize;
}
int finalMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(finalMeasureSpec, finalMeasureSpec);
}
}
<androidx.constraintlayout.widget.ConstraintLayout
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" >
<ImageView
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center_horizontal"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="#tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>