android - 自定义 View 的 onMeasure : how to get width based on height

标签 android android-custom-view android-relativelayout onmeasure

我的问题之前的版本太罗嗦了。人们无法理解它,所以下面是一个完整的重写。如果您对旧版本感兴趣,请参阅 edit history

RelativeLayout 的父级将 MeasureSpec s 发送到其 subview 的 onMeasure 方法,以查看 child 想要多大。这发生在几个 channel 中。

我的自定义 View

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

enter image description here

(不和谐的)父子对话

onMeasure 传递 1

RelativeLayout 父级告诉我的观点,“你可以是任何宽度 up to 900 和任何高度 up to 600 。你说什么?”

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

onMeasure 第 2 遍

RelativeLayout 父级告诉我的观点,“你上次告诉我你想要一个 100 的宽度,所以让我们将其设置为 exact 宽度。现在,基于这个宽度,你想要什么样的高度?任何 up to 500 没问题。”

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

最终结果

RelativeLayout 父级为 View 分配最终大小,宽度为 100,高度为 500。这对于 View 来说当然太窄了,并且部分内容被剪裁了。

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

最佳答案

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

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

onMeasure 似乎发生了很多事情,从表面上看,这并没有多大意义。既然如此,我们就让 onMeasure 照常运行,最后通过 onLayout 判断 View 的边界将 mStickyWidth 设置为我们将接受的新最小宽度。在 onPreDraw 中,使用 ViewTreeObserver.OnPreDrawListener,我们将强制另一个布局 (requestLayout)。来自 documentation (强调):

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.

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

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

CustomView.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;
}

关于android - 自定义 View 的 onMeasure : how to get width based on height,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42390378/

相关文章:

android - ScrollView 内的 TextView 未填充屏幕高度

Android 相对布局无法使图像居中

java - 通过音调生成创建听力测试 Android 应用程序

java - 如何在java中从蓝牙套接字读取字符串

android - Canvas : Path with negative and large positive numbers

android - Canvas.drawArc() Artifact

android - findViewById() 为自定义 View 返回 null - Android

c# - 在 InputField 中检测复制和粘贴

android - 如何通过 Workmanager 定期发送随机通知

android - RelativeLayout 导致动画不起作用?