Setting a maximum width on a ViewGroup
Asked Answered
O

6

61

How do I set the maximum width of a ViewGroup? I am using a Theme.Dialog activity, however, this does not look so good when resized to bigger screens, it's also sort of lightweight and I don't want it taking up the whole screen.

I tried this suggestion to no avail. Also, there is no android:maxWidth property like some views.

Is there a way to restrict the root LinearLayout so that it is only (for example) 640 dip? I am willing to change to another ViewGroup for this.

Any suggestions?

Obryan answered 3/5, 2011 at 21:23 Comment(0)
L
90

One option which is what I did is to extend LinearLayout and override the onMeasure function. For example:

public class BoundedLinearLayout extends LinearLayout {

    private final int mBoundedWidth;

    private final int mBoundedHeight;

    public BoundedLinearLayout(Context context) {
        super(context);
        mBoundedWidth = 0;
        mBoundedHeight = 0;
    }

    public BoundedLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BoundedView);
        mBoundedWidth = a.getDimensionPixelSize(R.styleable.BoundedView_bounded_width, 0);
        mBoundedHeight = a.getDimensionPixelSize(R.styleable.BoundedView_bounded_height, 0);
        a.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Adjust width as necessary
        int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
        if(mBoundedWidth > 0 && mBoundedWidth < measuredWidth) {
            int measureMode = MeasureSpec.getMode(widthMeasureSpec);
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(mBoundedWidth, measureMode);
        }
        // Adjust height as necessary
        int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
        if(mBoundedHeight > 0 && mBoundedHeight < measuredHeight) {
            int measureMode = MeasureSpec.getMode(heightMeasureSpec);
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(mBoundedHeight, measureMode);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

Then you XML would use the custom class:

<com.yourpackage.BoundedLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:bounded_width="900dp">

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
    />
</com.youpackage.BoundedLinearLayout>

And the attr.xml file entry

<declare-styleable name="BoundedView">
    <attr name="bounded_width" format="dimension" />
    <attr name="bounded_height" format="dimension" />
</declare-styleable>

EDIT: This is the actual code I am using now. This is still not complete but works in most cases.

Lympho answered 2/6, 2011 at 8:24 Comment(4)
It's incredible that Google didn't include a maxWidth out of the box, instead we have to subclass :-(Scholastic
You are absolutely correct. They are effectively crowdsourcing the platform :]Lympho
i got an error when i add app_name:bounded_width="900dp"Diaconate
@AHmédNet add xmlns:app_name="http://schemas.android.com/apk/res-auto" to the top element in your layout to fix thatEustacia
A
34

Here is better code for the Dori's answer.

In method onMeasure, If you call super.onMeasure(widthMeasureSpec, heightMeasureSpec); first in the method then all objects' width in the layout will not be changed. Because they initialized before you set the layout(parent) width.

public class MaxWidthLinearLayout extends LinearLayout {
    private final int mMaxWidth;

    public MaxWidthLinearLayout(Context context) {
        super(context);
        mMaxWidth = 0;
    }

    public MaxWidthLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MaxWidthLinearLayout);
        mMaxWidth = a.getDimensionPixelSize(R.styleable.MaxWidthLinearLayout_maxWidth, Integer.MAX_VALUE);
        a.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
        if (mMaxWidth > 0 && mMaxWidth < measuredWidth) {
            int measureMode = MeasureSpec.getMode(widthMeasureSpec);
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, measureMode);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

And here is a link for usage of xml attr:
 http://kevindion.com/2011/01/custom-xml-attributes-for-android-widgets/

Thanks for this question and answers. Your answer has helped me a lot, and I hope it helps somebody else in the future as well.

Angulation answered 25/7, 2012 at 16:29 Comment(2)
this answer is better. (also why Dori missed a trivial bug?)Enucleate
The contents are still using the original width if in the layout params of MaxWidthLinearLayout is specified an exact size... you'll have to specify MATH_PARENT and put the exact size management inside the onMeasure(..). silly but working.Cushion
T
30

Building on top of the original answer by Chase (+1) I would make a couple of changes (outlined below).

  1. I would have the max width set via a custom attribute (xml below the code)

  2. I would call super.measure() first and then do the Math.min(*) comparison. Using the original answers code we may encounter problems when the incoming size set in the MeasureSpec is either LayoutParams.WRAP_CONTENT or LayoutParams.FILL_PARENT. As these valid constants have values of -2 and -1 respectivly, the original Math.min(*) becomes useless as it will preserve these vales over the max size, and say the measured WRAP_CONTENT is bigger than our max size this check would not catch it. I imagine the OP was thinking of exact dims only (for which it works great)

    public class MaxWidthLinearLayout extends LinearLayout {
    
    private int mMaxWidth = Integer.MAX_VALUE;
    
    public MaxWidthLinearLayout(Context context) {
    
        super(context);
    }
    
    public MaxWidthLinearLayout(Context context, AttributeSet attrs) {
    
        super(context, attrs);
    
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MaxWidthLinearLayout);
        mMaxWidth = a.getDimensionPixelSize(R.styleable.MaxWidthLinearLayout_maxWidth, Integer.MAX_VALUE);
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //get measured height
        if(getMeasuredWidth() > mMaxWidth){
            setMeasuredDimension(mMaxWidth, getMeasuredHeight());
        }
    }
    }
    

and the xml attr

    <!-- MaxWidthLinearLayout -->
    <declare-styleable name="MaxWidthLinearLayout">
        <attr name="maxWidth" format="dimension" />
    </declare-styleable>
Tribesman answered 23/12, 2011 at 18:30 Comment(3)
Thanks for your response. I don't need this anymore, but I'm sure other people might need it as well. If i feel I need to revise my code, I will be sure to check this out!Obryan
if was for others / me in a years time when i forget what i have done :)Tribesman
This code does not work as expected. While the width is adjusted, the containing elements still work with the original width. Thus the content is just truncated. Jonypoins answer works.Plasmosome
C
11

Now android.support.constraint.ConstraintLayout makes it easier. Just wrap your view (of any type) with ConstraintLayout, and set the following attributes to the view:

android:layout_width="0dp"
app:layout_constraintWidth_default="spread"
app:layout_constraintWidth_max="640dp"

http://tools.android.com/recent/constraintlayoutbeta5isnowavailable

Crystallography answered 2/6, 2017 at 17:12 Comment(0)
T
0

Here's a pure Kotlin solution, with the added benefit of being able to use android:maxWidth.

BoundedFrameLayout.kt

package example.com

import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.core.content.withStyledAttributes
import com.sas.android.common.R

class BoundedFrameLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
    private var maxWidth = -1

    init {
        if (attrs != null) {
            context.withStyledAttributes(attrs, R.styleable.BoundedView) {
                maxWidth = getDimensionPixelSize(
                    R.styleable.BoundedView_android_maxWidth, maxWidth
                )
            }
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        var widthSpec = widthMeasureSpec
        if (maxWidth >= 0) {
            val widthSize = MeasureSpec.getSize(widthMeasureSpec)
            if (widthSize > maxWidth) {
                val widthMode = MeasureSpec.getMode(widthMeasureSpec)
                widthSpec = MeasureSpec.makeMeasureSpec(maxWidth, widthMode)
            }
        }
        super.onMeasure(widthSpec, heightMeasureSpec)
    }
}

attrs.xml

<resources>
    <declare-styleable name="BoundedView">
        <attr name="android:maxWidth" />
    </declare-styleable>
</resources>

Then in your layout XML:

…
<com.example.BoundedFrameLayout
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:maxWidth="350dp"
/>
…

Max height can be handled in the same way as width; it is omitted here for brevity.

This pattern can be adopted for any ViewGroup subclass (e.g. LinearLayout).

Thorfinn answered 14/9, 2021 at 3:23 Comment(0)
L
-3

Here is a simple answer,

Width/Height seem to always have to be set together. This is working in my view.

    <Button
        android:text="Center"
        android:layout_width="100dp"
        android:layout_height="fill_parent"
        android:id="@+id/selectionCenterButton"
        android:minWidth="50dp"
        android:minHeight="50dp"
        android:maxWidth="100dp"
        android:maxHeight="50dp"
        android:layout_weight="1" />

The button's parent is set to wrap content, so scales down, but up to a max of 400 wide (4 buttons).

Lenee answered 6/10, 2012 at 3:26 Comment(1)
A Button is not a ViewGroup. ViewGroups do not have max* attributes. Button is a subclass of TextView which does have these attributes. This entire question is about how to handle views that do not have max* attributes.Stonyhearted

© 2022 - 2024 — McMap. All rights reserved.