View的几个高度/宽度

自定义view的过程

onMeasure()–>onLayout()–>onDraw()

  • onMeasure() 计算当前各个子view的高宽度

  • onLayout() 确定各个子view在父容器的位置

    需要4个参数确定子view的位置。都是相对父容器的
    left 左边距离
    top 上边距离
    right 右边距离
    bottom 下边距离
    如果我想让某个子view占据父布局的上一半。就是 child.layout(0,0,0,1/2*父容器的高度)

  • onDraw() 开始绘制 真正开始具体绘制


1 view.getLayoutParams().height/width

获取当前view在父容器中的布局宽高度, 父容器根据LayoutParams()可以确定这个view的怎么布局。这里的height 和 width 可以是具体的数,也可以是如下的参数。每个不一样的ViewGroup拥有不一样的LayoutParams。LayoutParams()不仅仅是确定高度和宽度,还可以确定Margin,gravity。

获取来源:1.动态布局,可以任何时候手动setLayoutParams,setLayoutParams方法里面会触发requestLayout();requestLayout触发父容器重新依次计算子view在父布局的位置。
2.android.view.ViewGroup 的绘制过程中的方法会自动调用 view.setLayoutParams(params);注意可能获取为null,还没没有添加到布局中,自然不存在 LayoutParams。在方法 public void addView(**)会创建params = generateDefaultLayoutParams();每一种不同的布局类型,不同的generateDefaultLayoutParams初始化策略。

获取时机:addView之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public static class LayoutParams {
/**
* Special value for the height or width requested by a View.
* FILL_PARENT means that the view wants to be as big as its parent,
* minus the parent's padding, if any. This value is deprecated
* starting in API Level 8 and replaced by {@link #MATCH_PARENT}.
*/
@SuppressWarnings({"UnusedDeclaration"})
@Deprecated
public static final int FILL_PARENT = -1;

/**
* Special value for the height or width requested by a View.
* MATCH_PARENT means that the view wants to be as big as its parent,
* minus the parent's padding, if any. Introduced in API Level 8.
*/
public static final int MATCH_PARENT = -1;

/**
* Special value for the height or width requested by a View.
* WRAP_CONTENT means that the view wants to be just large enough to fit
* its own internal content, taking its own padding into account.
*/
public static final int WRAP_CONTENT = -2;

/**
* Information about how wide the view wants to be. Can be one of the
* constants FILL_PARENT (replaced by MATCH_PARENT
* in API Level 8) or WRAP_CONTENT, or an exact size.
*/
@ViewDebug.ExportedProperty(category = "layout", mapping = {
@ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
@ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
})
public int width;

/**
* Information about how tall the view wants to be. Can be one of the
* constants FILL_PARENT (replaced by MATCH_PARENT
* in API Level 8) or WRAP_CONTENT, or an exact size.
*/
@ViewDebug.ExportedProperty(category = "layout", mapping = {
@ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
@ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
})
public int height;

...
}

2 view().getHeight()/getWidth()

获取当前view的高度和宽度,需在onLayout之后获取,就是用laout中的 宽度就是=左边-去右边 高度 = 上边-下边

获取时机:必须在onLayout 之后

1
2
3
4
5
6
7
public final int getHeight() {
return mBottom - mTop;
}

public final int getWidth() {
return mRight - mLeft;
}

3 view().getMeasuredHeight()/getMeasuredWidth()

获取view自己的原始高度,

1
2
3
4
5
6
7
8
9
  //The raw measured width of this view.
public final int getMeasuredWidth() {
//取mod MEASURED_SIZE_MASK = 0x00ffffff;
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
//The raw measured height of this view.
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}

值的来源 onMeasure中调用的setMeasuredDimensionRaw

1
2
3
4
5
6
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;

mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

setMeasuredDimensionRaw来源于view#measure(int widthMeasureSpec, int heightMeasureSpec)
widthMeasureSpec 和 heightMeasureSpec 和 view 和 原始layoutpram 又相关。

获取时机:必须在view的onMeasure之后

关于MeasuredHeight() 请参考 MeasureSpec


4. 出发点的BUG 为什么view重叠的时候,有时可以点击?而有时不可以点击?

如图所示,存在以为视图布局如下,当以你以为的布局进行点击的时候,发现点击小view时而可以,时而不行?why?本质是点击事件的分发有关,点击事件由ViewGroup发往子View的时候,是从后面向前遍历的。也就是说,当2个view重叠的情况,谁在后面,谁响应本次事件。

可见android.view.ViewGroup#dispatchTouchEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public boolean dispatchTouchEvent(MotionEvent ev){
---
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
// 从后向前遍历 一个可以接受点击事件的 view
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}

//判断子 view 能不能收到 点击事件,和点击事件是不是在 子view的范围中
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))
---
}

最后才在 dispatchTransformedTouchEvent 调用 child.dispatchTouchEvent(event)

在onMeasure 调用 super.onMeasure 在super.onMeasure中计算子view的高度的时候会用到子view原来的LayoutParams,然后再计算 set子view的LayoutParams的,最后在onLayout的时候 通过 getMeasuredWidth()获取高度。问题在于getMeasuredWidth()获取的高度是在super.onMeasure的时候就已经确定了的,并不是在在onMeasure计算的高度,实际在于刚才set的LayoutParams。后面进入的时候 LayoutParams已经被更新了,再次super.onMeasure 更新的MeasuredWidth()就是和新的LayoutParams相关了,展示就会正确。

问题就是第一次进来的时候就会获取错误的高度,onlayout并不是我们手动计算的高度。