Android基础——View 知识点

https://rainmonth.github.io/posts/A210219.html

View 大小相关

getWidth()getMeasuredWidth()的区别

View 的widthheightmeasuredWidthmeasuredHeight的意义,分别的使用场景和赋值时机?

getWidth()代码:

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

getMeasuredWidth()代码:

1
2
3
4
public final int getMeasuredWidth() {
// mMeasuredWidth在onMeasure调用过程中赋值
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
  • getWidth()的值在layout(int l, int t, int r, int b)执行完成后真正确定;
  • getMeasureWidth()的值是在onMeasure(int widthMeasureSpec, int heightMeasureSpec)中的setMeasuredDimension(int measureWidth, int measrueHeight)执行完成后确定的,mMeasureWidth本身是包含测量模式和测量的具体值的,但在调用getMeasureWidth()后,进行了掩码操作,将高两位移除了;

结论:子View最终的位置是在onLayout 中确定的,但子View的宽高是在onMeasure中确定的

MeasureSpec相关

到底是个啥?各种值的含义?mode和size分别代表什么意思?

MeasureSpec详解

MeasureSpec表示的是View的测量结果,用一个int值表示(int值四个字节)其中高两位用来表示mode(测量模式),低30位用来表示size(测量的值),测量模式有三种:

  • UNSPECIFIED:不对View大小做限制,如:ListViewScrollView
  • EXACTLY:确切的大小,如:100dp或者march_parent
  • AT_MOST:大小不可超过某数值,如:wrap_content

着重看一下ViewGroup的getChildMeasureSpec(int spec, int padding, int childDimension)方法

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* @param spec The requirements for this view
* @param padding padding
* @param childDimension How big the child wants to be in the current
* dimension
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);

int size = Math.max(0, specSize - padding);

int resultSize = 0;
int resultMode = 0;

switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

针对以上ViewGroup的getChildMeasureSpec代码做个小结:

  1. 不管父View是何模式,若子View有确切数值,则子View大小就是其本身大小,且mode是EXACTLY
  2. 若子View是match_parent,则模式与父View相同,且大小同父View(若父View是UNSPECIFIED,则子View大小为0)
  3. 若子View是wrap_content,则模式是AT_MOST,大小同父View,表示不可超过父View大小(若父View是UNSPECIFIED,则子View大小为0)
  1. View的一般绘制流程是什么?

    measure->layout->draw

  2. 源码分析自定义ViewGroup onDraw方法无效

事件分发

事件分发机制(参考一文读懂Android View事件分发机制

1
2
3
4
5
6
7
8
9
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;//事件是否被消费
if (onInterceptTouchEvent(ev)){//调用onInterceptTouchEvent判断是否拦截事件
consume = onTouchEvent(ev);//如果拦截则调用自身的onTouchEvent方法
} else{
consume = child.dispatchTouchEvent(ev);//不拦截调用子View的dispatchTouchEvent方法
}
return consume;//返回值表示事件是否被消费,true事件终止,false调用父View的onTouchEvent方法
}
  1. RecyclerView中嵌套横向的RecyclerView,外层RecyclerView竖直滑动时,内层的不能保持原来的位置
  2. RecyclerView的PagerSnapHelper的用法
  3. ViewPager直接调用setCurrentItem,不触发ViewPager.OnPageChangeListeneronPageScrollStateChanged,但触发onPageSelected
  4. 子View最终的位置是在onLayout 中确定的,但子View的宽高是在onMeasure中确定的
  5. ViewPager的PageTransform效果在调用Adapter的notifyDataChanged方法后会失效,需要重新setAdapter
  6. ViewPager中显示单独的View时,调用notifyDataChange时View里面的内容不会更,原因是ViewPager的Adapter被设计成在其显示的Item增加删除时起作用,即instantiateItemdestroyItem两个方法

这个时候可以重写Adapter的getItemPostion方法,让其返回POSITION_NONE(该方法默认放回UNCHANGED,但这个时候需要自己解决显示View列表的缓存问题

如果不解决,那么ViewPager中的View就会越来越多