如何根据父容器的尺寸调整Android视图的大小

109

如何根据其父布局的大小设置视图大小。例如,我有一个填满整个屏幕的RelativeLayout,我希望子视图(例如ImageView)占据整个高度和1/2的宽度?

我已经尝试了覆盖所有onMeasureonLayoutonSizeChanged等方法,但都无法使其正常工作……


可以使用ConstraintLayout来实现,参考这里:https://dev59.com/0VoU5IYBdhLWcg3wk3l2 - Ali Nem
11个回答

124

我不知道是否还有人在关注这个帖子,但Jeff的解决方案只能帮你完成一半(字面意思)。他的onMeasure将会在父视图的一半中展示出图片的一半。问题是,在调用setMeasuredDimension之前调用super.onMeasure将会根据原始大小测量视图中的所有子元素,然后在setMeasuredDimension重新调整大小时,只是将视图切成两半。

相反,您需要调用setMeasuredDimension(对于onMeasure重写是必需的)并为您的视图提供一个新的LayoutParams,然后调用super.onMeasure。请记住,您的LayoutParams源自您视图的父类型,而不是您视图的类型。

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
   int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
   int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
   this.setMeasuredDimension(parentWidth/2, parentHeight);
   this.setLayoutParams(new *ParentLayoutType*.LayoutParams(parentWidth/2,parentHeight));
   super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

我相信只有在父布局是AbsoluteLayout(已经被弃用但有时仍然有用)的情况下,您才会遇到与父LayoutParams相关的问题。


12
根据我的经验,这种做法很少奏效,特别是在使用fill/match_parent或权重时。更好的方法是在setMeasuredDimension后调用measureChildren(例如使用MeasureSpec.makeMeasureSpec(newWidth,MeasureSpec.AT_MOST))。 - M. Schenk
1
MeasureSpec.getSize(widthMeasureSpec) 真的可以得到正确的父宽度吗?对我来说,它只返回接近实际宽度但不精确的随机宽度。 - Konsumierer
1
@M.Schenk已经解决了这个问题。我有一个VideoView在一个权重布局中。我需要宽度是屏幕大小的百分比,并且需要保持纵横比。这会拉伸视频,所以wrap_content不起作用。Vic的答案对于这种情况不起作用,但是@M.Schenk的评论可以解决。它还可以节省额外的布局传递。您还应该将其发布为可见性的答案。 - dokkaebi
7
调用 super.onMeasure() 不会简单地覆盖和使早期调用的 this.setMeasuredDimension() 失效吗? - Spinner
1
我也很好奇,就像@Spinner提到的那样,为什么调用super.onMeasure()没有覆盖先前的计算。 - jwir3
显示剩余3条评论

43

你可以通过创建自定义视图并覆盖onMeasure()方法来解决此问题。如果你在xml中总是使用"fill_parent"作为layout_width,那么传递到onMeasusre()方法中的widthMeasureSpec参数应该包含父容器的宽度。

public class MyCustomView extends TextView {

    public MyCustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
        this.setMeasuredDimension(parentWidth / 2, parentHeight);
    }
}   

您的 XML 可以长这样:

<LinearLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <view
        class="com.company.MyCustomView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

2
@jeff => 为什么是 parentWidth / 2 ?? - Saeed-rz
6
问题要求:整个高度和1/2的宽度。 - EdgarT

29
我发现最好不要自己设置测量尺寸。实际上,在父视图和子视图之间存在一些协商,您不希望重新编写所有code
但是,您可以修改measureSpecs,然后使用它们调用super。您的视图将永远不会知道它正在从其父级接收到修改后的消息,并且会为您处理所有内容:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
    int myWidth = (int) (parentHeight * 0.5);
    super.onMeasure(MeasureSpec.makeMeasureSpec(myWidth, MeasureSpec.EXACTLY), heightMeasureSpec);
}

17

在XML上定义布局和视图时,可以将视图的布局宽度和高度指定为wrap_contentfill_parent。占据一半区域有点困难,但如果您想在另一半上放置某些内容,可以尝试以下方法。

<LinearLayout android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <ImageView android:layout_height="fill_parent"
        android:layout_width="0dp"
        android:layout_weight="1" />
    <ImageView android:layout_height="fill_parent"
        android:layout_width="0dp"
        android:layout_weight="1" />
</LinearLayout>
给两个物体相同的权重意味着它们将拉伸以占据屏幕的相同比例。有关布局的更多信息,请参见开发文档

我不建议这样做,但是……作为一种hack方法,如果您确实使用上述方法,您可以将其中一个视图设置为“虚拟”的,并将android:visibility =“invisible”设置为填充一半的区域。 - RickNotFred
只使用一半的区域有什么目的?你计划在另一半显示任何内容吗?剩余区域的处理方式将决定最佳方法。 - RickNotFred
这绝对应该是第一位的 - 如果你可以在xml中完成它,为什么要在java中做呢? - fandang

9

我相信Mayras的 XML 方法可以使事情更加简单。但是,通过设置 weightSum 来仅使用一个视图可以使其更加准确。我不再称其为“hack”,而是在我看来最直接的方法:

<LinearLayout android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:weightSum="1">
    <ImageView android:layout_height="fill_parent"
               android:layout_width="0dp"
               android:layout_weight="0.5"/>
</LinearLayout>

像这样,您可以使用任何权重,例如0.6(并居中)是我喜欢用于按钮的权重。


3

试试这个

int parentWidth = ((parentViewType)childView.getParent()).getWidth();
int parentHeight = ((parentViewType)childView.getParent()).getHeight();

然后您可以使用LinearLayout.LayoutParams来设置子视图的参数。
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(childWidth,childLength);
childView.setLayoutParams(params);

2
你现在可以使用PercentRelativeLayout。嘭!问题解决了。

2
PercentRelativeLayout在API级别26.0.0-beta1中已被弃用。 - dzikovskyy
你说的“在 API 级别”是什么意思?你是指库版本号吗?PRL 在支持库中,没有 API 级别。 - androidguy
API级别是指Android的版本。有关此处的信息,请参阅https://developer.android.com/reference/android/support/percent/PercentRelativeLayout.html。有关API级别的更多信息,请参阅https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels。 - dzikovskyy
除非您需要使用版本26.0.0或更高版本,否则仍然可以使用此类。 - androidguy

1

大概是这样的:

<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"
    tools:context="com.company.myapp.ActivityOrFragment"
    android:id="@+id/activity_or_fragment">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/linearLayoutFirst"
    android:weightSum="100">   <!-- Overall weights sum of children elements -->

    <Spinner
        android:layout_width="0dp"   <!-- It's 0dp because is determined by android:layout_weight -->
        android:layout_weight="50"
        android:layout_height="wrap_content"
        android:id="@+id/spinner" />

</LinearLayout>


<LinearLayout
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:layout_below="@+id/linearLayoutFirst"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:id="@+id/linearLayoutSecond">

    <EditText
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:layout_weight="75"
        android:inputType="numberDecimal"
        android:id="@+id/input" />

    <TextView
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:layout_weight="25"
        android:id="@+id/result"/>

</LinearLayout>
</RelativeLayout>

1
Roman,如果你想在Java代码(ViewGroup的子类)中完成布局,是可以实现的。诀窍在于你必须实现onMeasure和onLayout方法。 首先调用onMeasure方法,你需要在其中“测量”子视图(有效地将其大小调整为所需值)。然后在onLayout调用中再次调整其大小。如果你未能按照这个顺序执行或未能在onMeasure代码的末尾调用setMeasuredDimension(),则无法获得结果。为什么要以如此复杂和脆弱的方式设计这个功能,我不明白。

1
如果另一侧为空,最简单的方法可能是添加一个空视图(例如线性布局),然后将两个视图的宽度设置为fill_parent,并将它们的权重都设置为1。
这在LinearLayout中有效...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接