2012-09-04 24 views
263

我试图做自定义组件。我扩展了View类,并在onDraw覆写方法中做了一些绘制。为什么我需要覆盖onMeasure?如果我没有,一切看起来都是正确的。有人可以解释吗?我应该如何编写我的onMeasure方法?我见过几个教程,但每个教程都与其他教程略有不同。有时他们最后会拨打super.onMeasure,有时候他们会用setMeasuredDimension,但没有拨打电话。差异在哪里?onMeasure定制视图说明

毕竟我想使用几个完全相同的组件。我将这些组件添加到我的XML文件中,但我不知道它们应该有多大。我想稍后设置它的位置和大小(为什么我需要在onMeasure中设置大小,如果在onDraw中绘制它,工作也是如此)在自定义组件类中。我什么时候需要这样做?

回答

649

onMeasure()是您有机会告诉Android您希望自定义视图的大小取决于父级提供的布局约束;这也是您的自定义视图的机会,可以了解这些布局约束条件(如果您希望在match_parent情况下的行为与wrap_content情况下的行为不同)。这些约束被打包到传入该方法的MeasureSpec值中。下面是模式值的粗略的相关性:

  • EXACTLYlayout_widthlayout_height值设定为特定值。你可能应该让你的观点大小。当使用match_parent时,也可以触发这种情况,以将尺寸精确地设置为父视图(这与框架中的布局相关)。
  • AT_MOST通常表示layout_widthlayout_height值设定为match_parentwrap_content或其中需要的最大尺寸(这是布局依赖于框架),并且父维度的大小的值。你不应该大于这个尺寸。
  • 未知通常表示layout_widthlayout_height值设置为wrap_content,没有任何限制。你可以是任何你想要的大小。某些布局还会使用此回调来确定您希望的尺寸,然后再决定在第二个度量请求中实际再次传递给您的参数。

onMeasure()存在的契约是:setMeasuredDimension()MUST在与大小结束时调用你想的观点是。此方法由所有框架实现调用,包括在View中找到的默认实现,因此,如果符合您的用例,则可以安全地调用super来代替。因为该框架确实应用了默认实现,所以您可能不需要重写此方法,但如果视图空间小于内容(如果不存在),并且如果视图空间小于内容你在两个方向上用wrap_content布局你的自定义视图,你的视图可能根本不显示,因为框架不知道它有多大!

一般来说,如果要覆盖View而不是另一个现有的小工具,它可能是一个好主意,提供一个实现,即使是这样的简单:

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

    int desiredWidth = 100; 
    int desiredHeight = 100; 

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

    int width; 
    int height; 

    //Measure Width 
    if (widthMode == MeasureSpec.EXACTLY) { 
     //Must be this size 
     width = widthSize; 
    } else if (widthMode == MeasureSpec.AT_MOST) { 
     //Can't be bigger than... 
     width = Math.min(desiredWidth, widthSize); 
    } else { 
     //Be whatever you want 
     width = desiredWidth; 
    } 

    //Measure Height 
    if (heightMode == MeasureSpec.EXACTLY) { 
     //Must be this size 
     height = heightSize; 
    } else if (heightMode == MeasureSpec.AT_MOST) { 
     //Can't be bigger than... 
     height = Math.min(desiredHeight, heightSize); 
    } else { 
     //Be whatever you want 
     height = desiredHeight; 
    } 

    //MUST CALL THIS 
    setMeasuredDimension(width, height); 
} 

希望帮助。

+1

嘿@Devunwired很好的解释迄今为止我读的最好的。你的解释回答了我的许多问题,并清除了一些疑问,但仍有一个问题仍然存在:如果我的自定义视图位于ViewGroup中,并且其他视图(无论哪种类型)ViewGroup将其所有子项为每个探测器的LayoutParams约束,并要求每个孩子根据自己的约束来自我测量? – pharaoh

+0

是的,这就是'ViewGroup'的'measureChildren()'方法在度量/布局过程中所做的。 – Devunwired

+34

请注意,如果您重写任何ViewGroup子类的onMeasure,则此代码不会执行此操作。你的子视图不会显示出来,全部大小都为0x0。如果您需要重写自定义ViewGroup的onMeasure,请更改widthMode,widthSize,heightMode和heightSize,然后使用MeasureSpec.makeMeasureSpec将它们编译回measureSpecs,并将生成的整数传递给super.onMeasure。 – Alexey

4

实际上,您的答案并不完整,因为值也取决于包装容器。在相对的或线性的布局的情况下,该值这样的表现:

  • EXACTLY match_parent正是+大小母体的
  • AT_MOST在AT_MOST MeasureSpec
  • UNSPECIFIED从未WRAP_CONTENT结果触发

在水平滚动视图的情况下,您的代码将工作。

+49

如果您觉得这里有些答案不完整,请添加它,而不是给出部分答案。 –

+0

良好的onya连接到布局如何工作,但在我的情况下onMeasure被称为三次我的自定义视图。有问题的视图有一个wrap_content高度和一个加权宽度(width = 0,weight = 1)。第一个电话有Unknown/Without,第二个电话有AT_MOST/EXACTLY,第三个电话是完全正确的。 –