好,问题的背景大家应该很熟悉,Android 的ViewGroup中可以通过addview()的方式加入View或者viewGroup,然后通常一个view要正常显示在屏幕上它的onMeasure(), onLayout()和onDraw至少要被调用一次。
现在我遇到的一个需求是SystemUI,显示状态栏的列表,当列表比较小时显示全部,当列表比较大时显示一定的高度,其他的通过滚动的方式显示。效果如下图:
这个显然想到的是ScrollView。但是单单ScrollView是实现不了的,不能实现动态的高度调整。于是我采用如下的布局:
public class RestrictedContainerLayout extends ViewGroup {
public RestrictedContainerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
setTag("RestrictedContainerLayout");
// TODO Auto-generated constructor stub
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int maxHeight = 200;
switch (heightMode){
case MeasureSpec.EXACTLY:
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
heightSize = Math.min(maxHeight,heightSize);
break;
}
int spec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
int n = getChildCount();
for(int i = 0 ; i < n ; i++){
View view = getChildAt(i);
measureChild(view, widthMeasureSpec, spec);
int height = view.getMeasuredHeight();
int min = Math.max(height, heightSize);
spec = MeasureSpec.makeMeasureSpec(min, heightMode);
}
super.onMeasure(widthMeasureSpec,spec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
int n = getChildCount();
for(int i = 0 ; i < n ; i++){
View view = getChildAt(i);
view.layout(l, t, r, b);
}
}
@Override
public void draw(Canvas canvas) {
// TODO Auto-generated method stub
super.draw(canvas);
}
}
只能出示看家本领debug和logcat,既然没有显示,或者显示出问题了,极有可能是onMeasure(), onLayout()或者onDraw其中出问题了,通过调试
很快定位到时RestricetedContainerLayout的onMeasure(), onLayout()和onDraw都被调用了,但是他的子View,LinearLayout的onDraw没有被调用但是onMeasure(), onLayout()被调用了。好,问题的表面原因找到了,就是onDraw()没有被调用。但是立刻陷入了一个更大的漩涡:为什么onDraw()会没有被调用?
当然也许你眼睛犀利可以一下子发现我代码中的问题,当然如果这样子的话就没必要写这篇博客。为了找到onDraw()没有被调用的原因,我决定进入framework进行调试,其实思路很简单,既然RestricetedContainerLayout的onDraw被调用了而LinearLayout是被RestricetedContainerLayout的onDraw调用的,原因肯定是RestricetedContainerLayout的onDraw在调用子类的draw()过程中有个判断没有过去。
如下图是ViewGroup及其子View的调用过程:
ViewGroup也就是我们这里的RestricetedContainerLayout的draw函数被调用,进而调用父类View的draw函数,进而调用view的onDawe()函数,这里draw()和onDraw()都可能被ViewGroup重载,如果这样自然调用的就是ViewGroup重载后的函数了。然后调用ViewGroupd的dispatchDraw()函数,进而调用drawChild()。最后调用draw(Canvas canvas, ViewGroup parent, long drawingTime)和draw(Canvas canvas)现在我们的问题就转换为:第一步:1.draw()我们知道是成功的,而第七步:7.draw()没有被调用。那肯定是这个调用链出了问题。
我的办法是:
1.在创建View的时候设置tag:
Log.e("luoziorng11","ViewGroupe drawChild "+" tag:"+this.getTag());
2.在这个链路的各个函数的关键节点加上log,就是在ViewGroup和View的draw,onDraw()等函数加上log,例如:
Log.e("luoziorng11","ViewGroupe drawChild "+" tag:"+this.getTag());
3.编译framework得到framework.jar.ext.jar等文件push到system/framework目录下。
4.启动并执行程序,观察log:
adb logcat | grep -E '(luoziorng11.*RestrictLinearLayout|RestrictLinearLayout.*luoziorng11)'
注意:我这里其实利用了一个很简单的技巧给我们的View设置tag,这样子可以过滤出只针对特定Veiw的在View.java和ViewGroup.java的log。
追踪不久,发现问题出现在boolean draw(Canvas canvas, ViewGroup parent, long drawingTime)函数中的如下代码:
if (!concatMatrix &&
(flags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS |
ViewGroup.FLAG_CLIP_CHILDREN)) == ViewGroup.FLAG_CLIP_CHILDREN &&
canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) &&
(mPrivateFlags & PFLAG_DRAW_ANIMATION) == 0) {
mPrivateFlags2 |= PFLAG2_VIEW_QUICK_REJECTED;
if (DBG_DRAW) {
Xlog.d(VIEW_LOG_TAG, "view draw1 quickReject, this =" + this
+ ", mLeft = " + mLeft + ", mTop = " + mTop
+ ", mBottom = " + mBottom + ", mRight = " + mRight);
}
Log.e("luoziorng11","View draw 3 parameter"+" tag"+this.getTag()+"802");
return more;
}
也就是说他算出来的子View的位置,在给他的画布的显示区域之外。那为什么呢?其实原因就在onLayout()里:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
int n = getChildCount();
for(int i = 0 ; i < n ; i++){
View view = getChildAt(i);
view.layout(l, t, r, b);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
Log.e("luoziorng11", " l:"+l+" t:"+t+" r:"+r+" b:"+b);
int n = getChildCount();
for(int i = 0 ; i < n ; i++){
View view = getChildAt(i);
view.layout(0, 0, r-l, b-t);
}
}
总结:这个问题出现的原因是我对layout的参数的理解出现问题,实属初级错误,但是这个解决问题的过程我觉得可以解决各种类似的复杂问题。这样子,感觉就不是作为一个简单搬用工,靠记概念解决,而且容易问题变了就不知道怎么解了,同时这样子在framework走一遭对framework的理解也深刻不少^_^