对于一个Android开发来说,不管你有多不喜欢自定义控件,你都得和她谈一场恋爱。所以我主动谈了这场很费脑子的恋爱,因为阅读源码的能力不好,在寻根抛底的找线索的过程中,搞得反胃,不过最后还是把来龙去脉都捋了一遍。当然今天的角不是常用且高效的组合型自定义控件,而是纯粹绘制出来的View或者重新定义规则的一个ViewGroup。
简述1: 启动Activity的时候会创建一个PhoneWindow和DectorView,并将DectorView放进PhoneWindow中。这个DectorView(继承FrameLayout的控件View)的资源布局是根据Theme来确定的,这个布局一般是LinearLayout的上下布局,上面是一个title标题栏,下面是一个content的内容栏。我们一般在onCreate中的setContentView(resID),就是往这个内容栏里面放布局。
简述2:DectorView和PhoneWindow都有了,布局也setContentView了。 那么是谁启动的测量,布局,绘制这些绘制流程的呢? 答案是ViewRootImp这个类。 还是简单的捋一捋吧:
1.ActivityThread收到RESUME_ACTIVITY消息,执行handleResumeActivity()方法:将DectorView添加进Window中。
代码: wm.addView(decor, l);
2.然后会在WindowManagerGlobal类中调用addView方法:创建ViewrootImp,并将DectorView传进去。
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);
3.然后在ViewRootImp的setView中触发requestLayout()方法,并执行scheduleTraversals();
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
4.然后scheduleTraversals()这个方法就是发送一个消息去执行一个线程,这个线程中便是真正开启执行测量,最终调用的是performTraversals方法:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
5.然后在这个方法里面调用测量 布局 绘制图 这些方法。performMeasure() performLayout() performDraw();
上面说了这么多就是为了更好的理解这个流程,而不至于一头雾水的留下为啥会调用这些方法,谁最先开启的这些方法等等有头无尾的疑问。
简述3:MeasureSpec是Mode和Size共同产生的一个值,32位。前两位是Mode模式,后面30位才是View实实在在的尺寸值。 一般情况下 一个View或者说ViewGroup的MeasureSpec是由父类ViewGroup和自己共同决定的。如下图:
1. EXACTLY :这个模式,精确模式,就是我能确定了它的具体值了。
a.给View 设置了具体的值,100dp 200dp 等
b.match_parenter:填充父窗体意思。如果父窗体是EXACTLY模式,即是一个确定的,那这个View就和他爹一样大。
2. AT_MOST :这个模式代表最大不超过某个值的模式。wrap_content.。自适应大小,但是最大不超过他爹的最大值。
3. UNSPECIFIED: 父容器不对View有任何限制,给它想要的任何尺寸。一般用于系统内部,表示一种测量状态。
一:测量
1. ViewRootImp进行测量的初始 performTraversals()此方法,这里面我们测量了根布局DectorView的宽和高childWidthMeasureSpec 和 childHeightMeasureSpec ,需要注意的是Dedctorview就是老祖宗,没有爹的限制,自己是啥就是啥。而实际上他的 宽高都是match_parent。所以就是EXACTLY+屏幕的尺寸。
对应的源码是:
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
2. 然后
-----> performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
-----> mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
-----> onMeasure(widthMeasureSpec, heightMeasureSpec);
3. onMeasure(widthMeasureSpec, heightMeasureSpec):首先判断这个View是否重写了onMeasure方法,如果重写了则调用重写的,否则就调用View.java 的。 这个方法View.java有,但是ViewGroup.java是没有的,因为继承他的自定义控件是一个容器,所以他的大小与子View的排列有关。因此继承ViewGroup 的自定义控件需要自己重写onMeasure来重新定义子View在其内部的排列规则。虽然View.java是有onMeasure方法,但是自己绘制的自定义View,他默认不处理padding设置的值,另外AT_MOST和MATCH_PARENT模式得到的尺寸是一样的,所以一般也是需要重写onmeasure()方法。下面我们就先看看View.java中的onMeasure()的源码