转自:http://www.androidchina.net/4458.html
1.view的绘制流程分几步,从哪开始?哪个过程结束以后能看到view?
答:从ViewRoot的performTraversals开始,经过measure,layout,draw 三个流程。draw流程结束以后就可以在屏幕上看到view了。
2.view的测量宽高和实际宽高有区别吗?
答:基本上百分之99的情况下都是可以认为没有区别的。有两种情况,有区别。第一种 就是有的时候会因为某些原因 view会多次测量,那第一次测量的宽高 肯定和最后实际的宽高 是不一定相等的,但是在这种情况下
最后一次测量的宽高和实际宽高是一致的。此外,实际宽高是在layout流程里确定的,我们可以在layout流程里 将实际宽高写死 写成硬编码,这样测量的宽高和实际宽高就肯定不一样了,虽然这么做没有意义 而且也不好。
3.view的measureSpec 由谁决定?顶级view呢?
答:由view自己的layoutparams和父容器 一起决定自己的measureSpec。一旦确定了spec,onMeasure中就可以确定view的宽高了。
顶级view就稍微特殊一点,对于decorView的测量在ViewRootImpl的源码里。
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
|
//desire的这2个参数就代表屏幕的宽高,
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//decorView的measureSpec就是在这里确定的,其实比普通view的measurespec要简单的多
//代码就不分析了 一目了然的东西
private
static
int
getRootMeasureSpec(
int
windowSize,
int
rootDimension) {
int
measureSpec;
switch
(rootDimension) {
case
ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break
;
case
ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break
;
default
:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break
;
}
return
measureSpec;
}
|
4.对于普通view来说,他的measure过程中,与父view有关吗?如果有关,这个父view也就是viewgroup扮演了什么角色?
答:看源码:
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
|
//对于普通view的measure来说 是由这个view的 父view ,也就是viewgroup来触发的。
//也就是下面这个measureChildWithMargins方法
protected
void
measureChildWithMargins(View child,
int
parentWidthMeasureSpec,
int
widthUsed,
int
parentHeightMeasureSpec,
int
heightUsed) {
//第一步 先取得子view的 layoutParams 参数值
final
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//然后开始计算子view的spec的值,注意这里看到 计算的时候除了要用子view的 layoutparams参数以外
//还用到了父view 也就是viewgroup自己的spec的值
final
int
childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final
int
childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
//这个算view的spec的方法 看上去一大串 但是真的逻辑非常简单 就是根据父亲viewgroup
//的meaurespec 同时还有view自己的params来确定 view自己的measureSpec。
//注意这里的参数是padding,这个值的含义是 父容器已占用的控件的大小 所以view的Specsize
//的值 你们可以看到 是要减去这个padding的值的。总大小-已经用的 =可用的。 很好理解。
//然后就是下面的switch逻辑 要自己梳理清楚。其实也不难,主要是下面几条原则
//如果view采用固定宽高,也就是写死的数值那种。那就不管父亲的spec的值了,view的spec 就肯定是exactly 并且大小遵循layout参数里设置的大小。
//如果view的宽高是match_parent ,那么就要看父容器viewgroup的 spec的值了,如果父view的spec是exactly模式,
//那view也肯定是exactly,并且大小就是父容器剩下的空间。如果父容器是at_most模式,那view也是at_most 并且不会超过剩余空间大小
//如果view的宽高是wrap_content, 那就不管父容器的spec了,view的spec一定是at_most 并且不会超过父view 剩余空间的大小。
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
;
}
return
MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
|
5.view的meaure和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
|
//view的measure是final 方法 我们子类无法修改的。
public
final
void
measure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
boolean
optical = isLayoutModeOptical(
this
);
if
(optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int
oWidth = insets.left + insets.right;
int
oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
long
key = (
long
) widthMeasureSpec <<
32
| (
long
) heightMeasureSpec & 0xffffffffL;
if
(mMeasureCache ==
null
) mMeasureCache =
new
LongSparseLongArray(
2
);
if
((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int
cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -
1
:
mMeasureCache.indexOfKey(key);
if
(cacheIndex <
0
|| sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
else
{
long
value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((
int
) (value >>
32
), (
int
) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if
((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw
new
IllegalStateException(
"View with id "
+ getId() +
": "
+ getClass().getName() +
"#onMeasure() did not set the"
+
" measured dimension by calling"
+
" setMeasuredDimension()"
);
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((
long
) mMeasuredWidth) <<
32
|
(
long
) mMeasuredHeight & 0xffffffffL);
// suppress sign extension
}
//不过可以看到的是在measure方法里调用了onMeasure方法
//所以就能知道 我们在自定义view的时候一定是重写这个方法!
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
|
6.简要分析view的measure流程?
答:先回顾问题4,viewgroup 算出子view的spec以后 会调用子view的measure方法,而子view的measure方法 我们问题5也看过了实际上是调用的onMeasure方法
所以我们只要分析好onMeasure方法即可,注意onMeasure方法的参数 正是他的父view算出来的那2个spec的值(这里view的measure方法会把这个spec里的specSize值做略微的修改 这个部分 不做分析 因为measure方法修改specSize的部分很简单)。
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
|
//可以看出来这个就是setMeasuredDimension方法的调用 这个方法看名字就知道就是确定view的测量宽高的
//所以我们分析的重点就是看这个getDefaultSize 方法 是怎么确定view的测量宽高的
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//这个方法特别简单 基本可以认为就是近似的返回spec中的specSize,除非你的specMode是UNSPECIFIED
//UNSPECIFIED 这个一般都是系统内部测量才用的到,这种时候返回size 也就是getSuggestedMinimumWidth的返回值
public
static
int
getDefaultSize(
int
size,
int
measureSpec) {
int
result = size;
int
specMode = MeasureSpec.getMode(measureSpec);
int
specSize = MeasureSpec.getSize(measureSpec);
switch
(specMode) {
case
MeasureSpec.UNSPECIFIED:
result = size;
break
;
case
MeasureSpec.AT_MOST:
case
MeasureSpec.EXACTLY:
result = specSize;
break
;
}
return
result;
}
//跟view的背景相关 这里不多做分析了
protected
int
getSuggestedMinimumWidth() {
return
(mBackground ==
null
) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
|
7.自定义view中 如果onMeasure方法 没有对wrap_content 做处理 会发生什么?为什么?怎么解决?
答:如果没有对wrap_content做处理 ,那即使你在xml里设置为wrap_content.其效果也和match_parent相同。看问题4的分析。我们可以知道view自己的layout为wrap,那mode就是at_most(不管父亲view是什么specmode).
这种模式下宽高就是等于specSize(getDefaultSize函数分析可知),而这里的specSize显然就是parentSize的大小。也就是父容器剩余的大小。那不就和我们直接设置成match_parent是一样的效果了么?
解决方式就是在onMeasure里 针对wrap 来做特殊处理 比如指定一个默认的宽高,当发现是wrap_content 就设置这个默认宽高即可。
8.ViewGroup有onMeasure方法吗?为什么?
答:没有,这个方法是交给子类自己实现的。不同的viewgroup子类 肯定布局都不一样,那onMeasure索性就全部交给他们自己实现好了。
9.为什么在activity的生命周期里无法获得测量宽高?有什么方法可以解决这个问题吗?
答:因为measure的过程和activity的生命周期 没有任何关系。你无法确定在哪个生命周期执行完毕以后 view的measure过程一定走完。可以尝试如下几种方法 获取view的测量宽高。
1
2
3
4
5
6
7
8
9
10
11
|
//重写activity的这个方法
public
void
onWindowFocusChanged(
boolean
hasFocus) {
super
.onWindowFocusChanged(hasFocus);
if
(hasFocus) {
int
width = tv.getMeasuredWidth();
int
height = tv.getMeasuredHeight();
Log.v(
"burning"
,
"width=="
+ width);
Log.v(
"burning"
,
"height=="
+ height);
}
}
|
或者重写这个方法
1
2
3
4
5
6
7
8
9
10
11
|
@Override
protected
void
onStart() {
super
.onStart();
tv.post(
new
Runnable() {
@Override
public
void
run() {
int
width = tv.getMeasuredWidth();
int
height = tv.getMeasuredHeight();
}
});
}
|
再或者:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Override
protected
void
onStart() {
super
.onStart();
ViewTreeObserver observer = tv.getViewTreeObserver();
observer.addOnGlobalLayoutListener(
new
ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public
void
onGlobalLayout() {
int
width = tv.getMeasuredWidth();
int
height = tv.getMeasuredHeight();
tv.getViewTreeObserver().removeOnGlobalLayoutListener(
this
);
}
});
}
|
10.layout和onLayout方法有什么区别?
答:layout是确定本身view的位置 而onLayout是确定所有子元素的位置。layout里面 就是通过serFrame方法设设定本身view的 四个顶点的位置。这4个位置以确定 自己view的位置就固定了
然后就调用onLayout来确定子元素的位置。view和viewgroup的onlayout方法都没有写。都留给我们自己给子元素布局
11.draw方法 大概有几个步骤?
答: 一共是4个步骤, 绘制背景———绘制自己——–绘制chrildren—-绘制装饰。
12.setWillNotDraw方法有什么用?
答:这个方法在view里。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public
void
setWillNotDraw(
boolean
willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW :
0
, DRAW_MASK);
}
|
用于设置标志位的 也就是说 如果你的自定义view 不需要draw的话,就可以设置这个方法为true。这样系统知道你这个view 不需要draw 可以优化执行速度。viewgroup 一般都默认设置这个为true,因为viewgroup多数都是只负责布局
不负责draw的。而view 这个标志位 默认一般都是关闭的。
13.自定义view 有哪些需要注意的点?
答:主要是要处理wrap_content 和padding。否则xml 那边设置这2个属性就根本没用了。还有不要在view中使用handler 因为人家已经提供了post方法。如果是继承自viewGroup,那在onMeasure和onLayout里面 也要考虑
padding和layout的影响。也就是说specSize 要算一下 。最后就是如果view的动画或者线程需要停止,可以考虑在onDetachedFromWindow里面来做。
针对上述的几点,给出几个简单的自定义view 供大家理解。
给出一个圆形的view 范例:
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
|
package
com.example.administrator.motioneventtest;
import
android.content.Context;
import
android.graphics.Canvas;
import
android.graphics.Color;
import
android.graphics.Paint;
import
android.util.AttributeSet;
import
android.view.View;
/**
* Created by Administrator on 2016/2/4.
*/
public
class
CircleView
extends
View {
private
int
mColor = Color.RED;
private
Paint mPaint =
new
Paint(Paint.ANTI_ALIAS_FLAG);
private
void
init() {
mPaint.setColor(mColor);
}
@Override
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
super
.onMeasure(widthMeasureSpec, heightMeasureSpec);
int
widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int
widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int
heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int
heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
//处理为wrap_content时的情况
if
(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(
200
,
200
);
}
else
if
(widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(
200
, heightSpecSize);
}
else
if
(heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize,
200
);
}
}
@Override
protected
void
onDraw(Canvas canvas) {
super
.onDraw(canvas);
//处理padding的情况
final
int
paddingLeft = getPaddingLeft();
final
int
paddingRight = getPaddingRight();
final
int
paddingTop = getPaddingTop();
final
int
paddingBottom = getPaddingBottom();
int
width = getWidth() - paddingLeft - paddingRight;
int
height = getHeight() - paddingTop - paddingBottom;
int
radius = Math.min(width, height) /
2
;
canvas.drawCircle(paddingLeft + width /
2
, paddingTop + height /
2
, radius, mPaint);
}
public
CircleView(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
init();
}
public
CircleView(Context context) {
super
(context);
init();
}
public
CircleView(Context context, AttributeSet attrs) {
super
(context, attrs);
init();
}
}
|
然后下面再给出一个范例,稍微复杂一点是自定义viewgroup了(主要是加强对onMeasure和onLayout的理解), 需求如下:
一个水平的viewgroup,内部的子元素 为了简单 我们假定他们的宽高都是一样的。来写一个这样的简单的viewgroup。
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
|
package
com.example.administrator.motioneventtest;
import
android.content.Context;
import
android.util.AttributeSet;
import
android.util.Log;
import
android.view.View;
import
android.view.ViewGroup;
/**
* Created by Administrator on 2016/2/4.
*/
//这里我们只处理了padding的状态 没有处理margin的状态,子view的margin 对measure和layout的影响
//就留给读者自己完成了
public
class
CustomHorizontalLayout
extends
ViewGroup {
//设置默认的控件最小是多少 这里不提供自定义属性了 写死在代码里 你们可以自行拓展
final
int
minHeight =
0
;
final
int
minWidth =
0
;
public
CustomHorizontalLayout(Context context) {
super
(context);
}
public
CustomHorizontalLayout(Context context, AttributeSet attrs) {
super
(context, attrs);
}
public
CustomHorizontalLayout(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
}
@Override
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
super
.onMeasure(widthMeasureSpec, heightMeasureSpec);
int
measureWidth =
0
;
int
measureHeight =
0
;
final
int
childCount = getChildCount();
measureChildren(widthMeasureSpec, heightMeasureSpec);
int
widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int
widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int
heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int
heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
final
View childView = getChildAt(
0
);
final
int
paddingLeft = getPaddingLeft();
final
int
paddingRight = getPaddingRight();
final
int
paddingTop = getPaddingTop();
final
int
paddingBottom = getPaddingBottom();
//没有子控件 时 我们的宽高要作特殊处理
if
(childCount ==
0
) {
//当没有子控件时,如果长宽有一个为wrap 那么就让这个控件以最小的形式展现
//这里我们最小设置为0
if
(widthSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(minWidth, minHeight);
}
else
{
//否则根据我们的layout属性来
setMeasuredDimension(getLayoutParams().width, getLayoutParams().height);
}
}
else
if
(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
measureWidth = childView.getMeasuredWidth() * childCount;
measureHeight = childView.getMeasuredHeight();
setMeasuredDimension(paddingLeft + measureWidth + paddingRight, paddingTop + measureHeight + paddingBottom);
}
else
if
(heightSpecMode == MeasureSpec.AT_MOST) {
measureHeight = childView.getMeasuredHeight();
setMeasuredDimension(paddingLeft + paddingRight + widthSpecSize, paddingTop + paddingBottom + measureHeight);
}
else
if
(widthSpecMode == MeasureSpec.AT_MOST) {
measureWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(paddingLeft + paddingRight + measureWidth, paddingTop + paddingBottom + heightSpecSize);
}
}
@Override
protected
void
onLayout(
boolean
changed,
int
l,
int
t,
int
r,
int
b) {
final
int
paddingLeft = getPaddingLeft();
final
int
paddingRight = getPaddingRight();
final
int
paddingTop = getPaddingTop();
final
int
paddingBottom = getPaddingBottom();
//左边初始位置为0
int
childLeft =
0
+ paddingLeft;
final
int
childCount = getChildCount();
for
(
int
i =
0
; i < childCount; i++) {
final
View childView = getChildAt(i);
if
(childView.getVisibility() != View.GONE) {
final
int
childWidth = childView.getMeasuredWidth();
childView.layout(childLeft,
0
+ paddingTop, childLeft + childWidth, paddingTop + childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
}
|