22

我的问题的先前版本太罗嗦了。人们无法理解它,所以下面是一个完整的重写。如果您对旧版本感兴趣,请查看编辑历史记录。

RelativeLayout父母将sMeasureSpec发送到其子视图的onMeasure方法,以查看孩子想要多大。这发生在几个通道中。

我的自定义视图

我有一个自定义视图。随着视图内容的增加,视图的高度也会增加。当视图达到父级允许的最大高度时,任何其他内容的视图宽度都会增加(只要wrap_content选择了宽度)。因此,自定义视图的宽度直接取决于父级所说的最大高度必须是什么。

在此处输入图像描述

(不和谐的)亲子对话

onMeasure通过 1

父母告诉我的RelativeLayout观点,“你可以是任意宽度 900任意高度 600你说什么?”

我的观点是,“嗯,在那个高度,我可以容纳宽度为 的所有东西100。所以我将采用宽度100和高度600。”

onMeasure通过 2

家长告诉我的RelativeLayout观点,“你上次告诉我你想要一个宽度100,所以让我们将它设置为一个精确的宽度。现在,基于这个宽度,你想要什么样的高度?任何高度都可以 500。”

“嘿!” 我的看法答复。“如果你只给我的最大高度500,那就100太窄了。我需要一个宽度来200达到那个高度。但是没关系,随你的便吧。我不会违反规则(还......)。我将采用宽度100和高度500。"

最后结果

RelativeLayout父级为视图分配100宽度和500高度的最终尺寸。这对于视图来说当然太窄了,并且部分内容被剪裁了。

“叹息,”我的看法是。“为什么我的父母不让我变宽?空间很大。也许 Stack Overflow 上的人可以给我一些建议。”

4

2 回答 2

13

更新:修改代码以修复一些问题。

首先,让我说您提出了一个很好的问题并且很好地提出了问题(两次!)这是我的解决方案:

onMeasure从表面上看,似乎有很多事情发生,并没有多大意义。既然是这种情况,我们将让它onMeasure按原样运行,最后通过设置我们将接受的新最小宽度来判断View' 的边界。在中,使用 a ,我们将强制使用另一个布局 ( )。从文档(强调添加):onLayoutmStickyWidthonPreDrawViewTreeObserver.OnPreDrawListenerrequestLayout

boolean onPreDraw ()

即将绘制视图树时调用的回调方法。此时,树中的所有视图都已被测量并给出了一个框架。客户可以使用它来调整他们的滚动范围,甚至在绘制之前请求一个新的布局

onLayout现在将强制执行新的最小宽度设置,现在可以onMeasure更智能地了解可能的情况。

我已经用你的示例代码对此进行了测试,它似乎工作正常。它将需要更多的测试。可能还有其他方法可以做到这一点,但这是该方法的要点。

自定义视图.java

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;

public class CustomView extends View
        implements ViewTreeObserver.OnPreDrawListener {
    private int mStickyWidth = STICKY_WIDTH_UNDEFINED;

    public CustomView(Context context) {
        super(context);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        logMeasureSpecs(widthMeasureSpec, heightMeasureSpec);
        int desiredHeight = 10000; // some value that is too high for the screen
        int desiredWidth;

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;

        // Height
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            height = Math.min(desiredHeight, heightSize);
        } else {
            height = desiredHeight;
        }

        // Width
        if (mStickyWidth != STICKY_WIDTH_UNDEFINED) {
            // This is the second time through layout and we are trying renogitiate a greater
            // width (mStickyWidth) without breaking the contract with the View.
            desiredWidth = mStickyWidth;
        } else if (height > BREAK_HEIGHT) { // a number between onMeasure's two final height requirements
            desiredWidth = ARBITRARY_WIDTH_LESSER; // arbitrary number
        } else {
            desiredWidth = ARBITRARY_WIDTH_GREATER; // arbitrary number
        }

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            width = Math.min(desiredWidth, widthSize);
        } else {
            width = desiredWidth;
        }

        Log.d(TAG, "setMeasuredDimension(" + width + ", " + height + ")");
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        int w = right - left;
        int h = bottom - top;

        super.onLayout(changed, left, top, right, bottom);
        // Here we need to determine if the width has been unnecessarily constrained.
        // We will try for a re-fit only once. If the sticky width is defined, we have
        // already tried to re-fit once, so we are not going to have another go at it since it
        // will (probably) have the same result.
        if (h <= BREAK_HEIGHT && (w < ARBITRARY_WIDTH_GREATER)
                && (mStickyWidth == STICKY_WIDTH_UNDEFINED)) {
            mStickyWidth = ARBITRARY_WIDTH_GREATER;
            getViewTreeObserver().addOnPreDrawListener(this);
        } else {
            mStickyWidth = STICKY_WIDTH_UNDEFINED;
        }
        Log.d(TAG, ">>>>onLayout: w=" + w + " h=" + h + " mStickyWidth=" + mStickyWidth);
    }

    @Override
    public boolean onPreDraw() {
        getViewTreeObserver().removeOnPreDrawListener(this);
        if (mStickyWidth == STICKY_WIDTH_UNDEFINED) { // Happy with the selected width.
            return true;
        }

        Log.d(TAG, ">>>>onPreDraw() requesting new layout");
        requestLayout();
        return false;
    }

    protected void logMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        String measureSpecHeight;
        String measureSpecWidth;

        if (heightMode == MeasureSpec.EXACTLY) {
            measureSpecHeight = "EXACTLY";
        } else if (heightMode == MeasureSpec.AT_MOST) {
            measureSpecHeight = "AT_MOST";
        } else {
            measureSpecHeight = "UNSPECIFIED";
        }

        if (widthMode == MeasureSpec.EXACTLY) {
            measureSpecWidth = "EXACTLY";
        } else if (widthMode == MeasureSpec.AT_MOST) {
            measureSpecWidth = "AT_MOST";
        } else {
            measureSpecWidth = "UNSPECIFIED";
        }

        Log.d(TAG, "Width: " + measureSpecWidth + ", " + widthSize + " Height: "
                + measureSpecHeight + ", " + heightSize);
    }

    private static final String TAG = "CustomView";
    private static final int STICKY_WIDTH_UNDEFINED = -1;
    private static final int BREAK_HEIGHT = 1950;
    private static final int ARBITRARY_WIDTH_LESSER = 200;
    private static final int ARBITRARY_WIDTH_GREATER = 800;
}
于 2017-03-03T16:37:26.920 回答
1

要制作自定义布局,您需要阅读并理解这篇文章https://developer.android.com/guide/topics/ui/how-android-draws.html

实现你想要的行为并不难。您只需要在自定义视图中覆盖 onMeasure 和 onLayout 即可。

在 onMeasure 中,您将获得自定义视图的最大可能高度,并为循环中的孩子调用 measure()。在孩子测量后,从每个孩子那里获得所需的高度并计算孩子是否适合当前列,如果不增加新列的自定义视图宽度。

在 onLayout 中,您必须为所有子视图调用 layout() 以设置它们在父视图中的位置。您之前在 onMeasure 中计算过的这个位置。

于 2017-03-03T10:40:18.163 回答