Android 系统源码分析——Launcher3自定义控件之Workspace

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

Android Launcher3分析——自定义控件之Workspace

绘制方面

可以参考文章Android View的绘制流程分析

onMeasure

Workspace 并没有进行什么测量工作,测量工作都交给其父类PagedView处理,看看PagedView的onMeasure如何处理的。

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 没有子View直接调用View的onMeasure方法,退出
if (getChildCount() == 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}

// We measure the dimensions of the PagedView to be larger than the pages so that when we
// zoom out (and scale down), the view is still contained in the parent
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// NOTE: We multiply by 2f to account for the fact that depending on the offset of the
// viewport, we can be at most one and a half screens offset once we scale down
DisplayMetrics dm = getResources().getDisplayMetrics();
int maxSize = Math.max(dm.widthPixels + mInsets.left + mInsets.right,
dm.heightPixels + mInsets.top + mInsets.bottom);

int parentWidthSize = (int) (2f * maxSize);
int parentHeightSize = (int) (2f * maxSize);
int scaledWidthSize, scaledHeightSize;
if (mUseMinScale) {
scaledWidthSize = (int) (parentWidthSize / mMinScale);
scaledHeightSize = (int) (parentHeightSize / mMinScale);
} else {
scaledWidthSize = widthSize;
scaledHeightSize = heightSize;
}
mViewport.set(0, 0, widthSize, heightSize);

if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}

// Return early if we aren't given a proper dimension
if (widthSize <= 0 || heightSize <= 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}

/* Allow the height to be set as WRAP_CONTENT. This allows the particular case
* of the All apps view on XLarge displays to not take up more space then it needs. Width
* is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
* each page to have the same width.
*/
final int verticalPadding = getPaddingTop() + getPaddingBottom();
final int horizontalPadding = getPaddingLeft() + getPaddingRight();

int referenceChildWidth = 0;
// The children are given the same width and height as the workspace
// unless they were set to WRAP_CONTENT
if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize);
if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize);
if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding);
final int childCount = getChildCount();
// 遍历子View(只处理可见的子View)
for (int i = 0; i < childCount; i++) {
// disallowing padding in paged view (just pass 0)
final View child = getPageAt(i);
// 处理可见子View
if (child.getVisibility() != GONE) {
// 获取子布局的布局参数
final LayoutParams lp = (LayoutParams) child.getLayoutParams();

int childWidthMode;// 子View的宽度测量规范
int childHeightMode;// 子View的高度测量规范
int childWidth;// 子View的宽
int childHeight;// 子View的高

// 根据布局参数来设置子View的宽、高以及宽高规范
if (!lp.isFullScreenPage) {
if (lp.width == LayoutParams.WRAP_CONTENT) {
childWidthMode = MeasureSpec.AT_MOST;
} else {
childWidthMode = MeasureSpec.EXACTLY;
}

if (lp.height == LayoutParams.WRAP_CONTENT) {
childHeightMode = MeasureSpec.AT_MOST;
} else {
childHeightMode = MeasureSpec.EXACTLY;
}

childWidth = getViewportWidth() - horizontalPadding
- mInsets.left - mInsets.right;
childHeight = getViewportHeight() - verticalPadding
- mInsets.top - mInsets.bottom;
mNormalChildHeight = childHeight;
} else {
childWidthMode = MeasureSpec.EXACTLY;
childHeightMode = MeasureSpec.EXACTLY;

childWidth = getViewportWidth() - mInsets.left - mInsets.right;
childHeight = getViewportHeight();
}
if (referenceChildWidth == 0) {
referenceChildWidth = childWidth;
}

// 获取子View的宽高测量规范
final int childWidthMeasureSpec =
MeasureSpec.makeMeasureSpec(childWidth, childWidthMode);
final int childHeightMeasureSpec =
MeasureSpec.makeMeasureSpec(childHeight, childHeightMode);
// 根据获取到的测量规范测量每个子View
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
setMeasuredDimension(scaledWidthSize, scaledHeightSize);
}

onLayout

1
2
3
4
5
6
7
8
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
mWallpaperOffset.syncWithScroll();
mWallpaperOffset.jumpToFinal();
}
super.onLayout(changed, left, top, right, bottom);
}

主要还是调用PagedView的onLayout方法,只是在调用之前根据是否第一次Layout及当前页是否在有效范围来对Launcher壁纸的偏移量做处理。

onDraw

1
2
3
4
5
6
7
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

// Call back to LauncherModel to finish binding after the first draw
post(mBindPages);
}

调用PagedView的onDraw方法,然后告知通知LauncherModel绑定完成。

注意:ViewGroup作为一个View的容器,为了性能上的考虑,通常是不需要调用自己的onDraw函数的(不需要重写),只有在我们设置了ViewGroup的背景或者调用setWillNotDraw(false)时才会调用。

事件处理方面

可以参考文章Android View的事件处理流程分析

onInterceptTouchEvent(MotionEvent ev)

onTouchEvent(MotionEvent ev)