重写onMeasure()的权威方法?


88

重写onMeasure()的正确方法是什么?我见过各种方法。例如,专业Android开发人员使用MeasureSpec来计算尺寸,然后以对setMeasuredDimension()的调用结束。例如:

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

另一方面,按照本文所述,“正确”的方法是使用MeasureSpec,调用setMeasuredDimensions(),然后调用setLayoutParams(),最后调用super.onMeasure()。例如:

@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);
}

那么哪种方法正确呢?两种方法都对我没有100%的作用。

我想我真正要问的是有人知道一个解释onMeasure(),子视图的布局,尺寸等的教程吗?


15
您觉得答案有用/正确吗?为什么不把它标记为答案吗?
superjos 2011年

Answers:


68

其他解决方案并不全面。在某些情况下,它们可能会起作用,并且是一个不错的起点,但不能保证它们会起作用。

调用onMeasure时,您可能会或可能不会有权更改大小。传递给你的onMeasure值(widthMeasureSpecheightMeasureSpec),包含有关你的孩子视图允许做的信息。当前有三个值:

  1. MeasureSpec.UNSPECIFIED -您可以随心所欲
  2. MeasureSpec.AT_MOST-parentWidth在您的示例中,可以根据需要选择最大尺寸(不超过规格尺寸)。
  3. MeasureSpec.EXACTLY- 没有选择。父母选择了。

这样做是为了使Android可以多次通过以找到适合每个项目的尺寸,请参见此处以了解更多详细信息。

如果不遵循这些规则,则不能保证您的方法有效。

例如,如果要检查是否允许更改大小,可以执行以下操作:

final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;

使用此信息,您将知道是否可以像在代码中一样修改值。或者,如果您需要执行其他操作。解决所需大小的快速简便方法是使用以下方法之一:

int resolveSizeAndState(int大小,int measureSpec,int childMeasuredState)

int resolveSize(int大小,int measureSpec)

虽然第一个仅在Honeycomb上可用,但是第二个在所有版本上都可用。

注意:您可能会发现resizeWidthresizeHeight始终为假。如果我要求的话,我发现情况就是这样MATCH_PARENT。我可以通过请求WRAP_CONTENT父级布局,然后在未修改阶段请求的大小来解决此问题Integer.MAX_VALUE。这样可以为您的父母在下一次通过onMeasure时所允许的最大尺寸。


39

该文档是此问题的权威:http : //developer.android.com/guide/topics/ui/how-android-draws.htmlhttp://developer.android.com/guide/topics/ui/custom -components.html

总结一下:在覆盖onMeasure方法的末尾,您应该调用setMeasuredDimension

您不应在致电super.onMeasure后致电setMeasuredDimension,这只会删除您设置的任何内容。在某些情况下,您可能需要先调用super.onMeasure,然后通过调用修改结果setMeasuredDimension

不要叫setLayoutParamsonMeasure。测量后,布局会在第二遍通过。


我的经验是,如果我在不调用super.onMeasure的情况下覆盖onMeasure,则不会在子视图上调用OnLayout。
William Jockusch 2014年

2

我认为这取决于您要覆盖的父级。

例如,如果要扩展ViewGroup(如FrameLayout),则在测量尺寸后,应按以下方式调用

super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));

因为您可能希望ViewGroup进行休息工作(在子视图上做一些事情)

如果要扩展View(例如ImageView),则可以调用this.setMeasuredDimension(width, height);,因为父类将执行通常执行的操作。

简而言之,如果您希望父类免费提供某些功能,则应调用super.onMeasure()(通常通过MeasureSpec.EXACTLY模式下的Measure spec),否则调用this.setMeasuredDimension(width, height);就足够了。


1

这是我解决问题的方法:

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

        ....

        setMeasuredDimension( measuredWidth, measuredHeight );

        widthMeasureSpec = MeasureSpec.makeMeasureSpec( measuredWidth, MeasureSpec.EXACTLY );
        heightMeasureSpec = MeasureSpec.makeMeasureSpec( measuredHeight, MeasureSpec.EXACTLY);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

此外,这对于ViewPager组件是必要的


3
这不是正确的解决方案。super.onMeasure将清除使用设置的尺寸setMeasuredDimension
tomrozb 2015年

@tomrozb,它不是humptydevelopers.com/2013/05/…,您可以检查android来源
Yuli Reiri 2015年

@YuliReiri:完美的解决方案!
Shayan Tabatabaee,

0

如果要更改视图大小,则onMeasure需要setMeasuredDimension调用。如果要更改外面的尺寸,则onMeasure需要致电setLayoutParams。例如,在更改文本时更改文本视图的大小。


您仍然可以调用setMinWidth()和setMaxWidth()并让标准的onMeasure处理它。我发现最好不要从自定义View类内部调用setLayoutParams。实际上,它用于ViewGroups或Activity来覆盖子视图行为。
Dorrin


-1

我猜想,setLayoutParams和重新计算度量值是一种正确调整子视图大小的解决方法,因为这通常是在派生类的onMeasure中完成的。

但是,这很少能正常工作(无论出于何种原因...),最好调用measureChildren(在派生ViewGroup时),或者在必要时尝试类似的操作。


-5

您可以将这段代码作为onMeasure()的示例:

public class MyLayerLayout extends RelativeLayout {

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);

        int currentChildCount = getChildCount();
        for (int i = 0; i < currentChildCount; i++) {
            View currentChild = getChildAt(i);

            //code to find information

            int widthPercent = currentChildInfo.getWidth();
            int heightPercent = currentChildInfo.getHeight();

//considering we will pass height & width as percentage

            int myWidth = (int) Math.round(parentWidth * (widthPercent / 100.0));
            int myHeight = (int) Math.round(parentHeight * (heightPercent / 100.0));

//Considering we need to set horizontal & vertical position of the view in parent

            AlignmentTraitValue vAlign = currentChildInfo.getVerticalLocation() != null ? currentChildlayerInfo.getVerticalLocation() : currentChildAlignmentTraitValue.TOP;
            AlignmentTraitValue hAlign = currentChildInfo.getHorizontalLocation() != null ? currentChildlayerInfo.getHorizontalLocation() : currentChildAlignmentTraitValue.LEFT;
            int topPadding = 0;
            int leftPadding = 0;

            if (vAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                topPadding = (parentHeight - myHeight) / 2;
            } else if (vAlign.equals(currentChildAlignmentTraitValue.BOTTOM)) {
                topPadding = parentHeight - myHeight;
            }

            if (hAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                leftPadding = (parentWidth - myWidth) / 2;
            } else if (hAlign.equals(currentChildAlignmentTraitValue.RIGHT)) {
                leftPadding = parentWidth - myWidth;
            }
            LayoutParams myLayoutParams = new LayoutParams(myWidth, myHeight);
            currentChildLayoutParams.setMargins(leftPadding, topPadding, 0, 0);
            currentChild.setLayoutParams(myLayoutParams);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

6
这段代码根本上是错误的。首先,它没有考虑布局模式(请参阅Grimmace的答案)。不好,但是您看不到它的效果,因为您最后还要调用super.onMeasure(),它会覆盖您设置的值(请参见satur9nine的答案),并且根本无法覆盖onMeasure()的目的。
spaaarky21
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.