android View的绘制过程和获取组件高宽值的三种方法
view绘制过程中的几个方法的调用顺序
onViewAdded
绘制View的流程是在onCreate中使用setContentView来设置要显示Layout文件或直接创建一个View,当调用setContentView后系统会对这个View进行解析,我们看到系统调用了onViewAdded()方法。
在View.java类中
protectedvoid onFinishInflate() {
}
View对象和它的所有子对象都用XML填充完之后,调用这个方法。
在系统将子View都添加完之后,就会回调当前视图View中的onFinishInflate方法。
只有解析了这个View我 们才能在这个View容器中获取到对子View的引用,同样因为系统解析完View之后才会调用onFinishInflate方法,所以我们自定义组件时可以onFinishInflate方法中获取指定子View的引用。
一般我们在该函数中获取View的引用还有高度。获取高度的方式有三种,每一种都有一些区别。
1 .
int w = View.MeasureSpec.makeMeasureSpec(0,
View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0,
View.MeasureSpec.UNSPECIFIED);
imageView.measure(w, h);
int height = imageView.getMeasuredHeight();
int width = imageView.getMeasuredWidth();
通过这种方式获取的高度不是很准确。
试一:<Linearlayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="305dp"
android:background="@drawable/lauch_page_background">
<LinearLayout/>
如果你的Layout中背景图片的高度小于305dp,我们获取的高度就是305dp。
如果你的Layout中背景图片的高度大于305dp,我们获取的高度就是背景图片的高度。不是实际上屏幕的高度。
(原因还需继续研究)
使用第二种方式就是ok的。
2.
ViewTreeObserver vto = imageView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
imageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
//我们在每次监听前remove前一次的监听,避免重复监听。
imageView.getHeight();
imageView.getWidth();
}
});
该方法获取的高度就是实际在屏幕上显示的高度。
publicinterface OnGlobalLayoutListener {
/**
* Callback method to be invoked when the global layout state or the visibility of views
-
within the view tree changes
-
全局布局状态或者Views的可见性发生变化时回调该方法。
*/
publicvoid onGlobalLayout();
}
我们可以看一下这个接口时在什么时候调用的:
publicfinalvoid dispatchOnGlobalLayout() {
final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
if (listeners != null && listeners.size() > 0) {
CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
try {
int count = access.size();
for (int i = 0; i < count; i++) {
access.get(i).onGlobalLayout();
}
} finally {
listeners.end();
}
}
}
这个dispatchOnGlobalLayout()是final类型的,它是在
privatevoid performTraversals() {
…
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
…
if (triggerGlobalLayoutListener) {
attachInfo.mRecomputeGlobalAttributes = false;
attachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
}
3.
ViewTreeObserver vto = imageView.getViewTreeObserver();
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
vto.removeOnPreDrawListener(this);
int height = imageView.getMeasuredHeight();
int width = imageView.getMeasuredWidth();
return true;
}
});
这个方法会被调用多次,初始化的时候会被调用多次,离开界面的时候也会被调用多次。只要界面有变化就会被调用。
publicinterface OnPreDrawListener {
/**
* 将要画view的时候调用。这个时候所有的View树都已经被测量了并且给了frame
*/
publicboolean onPreDraw();
}
publicfinalboolean dispatchOnPreDraw() {
boolean cancelDraw = false;
final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
if (listeners != null && listeners.size() > 0) {
CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
try {
int count = access.size();
for (int i = 0; i < count; i++) {
cancelDraw |= !(access.get(i).onPreDraw());
}
} finally {
listeners.end();
}
}
return cancelDraw;
}
它的调用顺序在方法2监听器的后面。
privatevoid performTraversals() {
…
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
…
if (triggerGlobalLayoutListener) {
attachInfo.mRecomputeGlobalAttributes = false;
attachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
…
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
...
}
通过后两种方法得到组件的高度和宽度比较方便,第一种完全时自己测量组件高和宽,比较麻烦。
onMeasure(int, int)
调用这个方法确定View对象及其所有子对象的尺寸要求。
ViewRootImpl.java
privatevoid performTraversals() {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
performDraw();
}
整个View树的绘图流程是从performTraversals()函数展开的,该函数做的执行过程可概况为:根据之前设置的状态,判断是否需要重新计算视图大小(measure())、是否重新需要安置视图的位置(layout())、以及是否需要重绘(draw())。
privatevoid performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
long startMeasure = System.nanoTime(); //Flyme_Added
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
//Flyme_Added
float totalMeasure = (float)(System.nanoTime() - startMeasure) * 0.000001f;
mAttachInfo.mMeasureTime += totalMeasure;
}
}
mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调
View/ViewGroup对象的onMeasure()方法。(ViewGroup对象 需要重载onMeasure()方法)
publicfinalvoid measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
回调的过程分为两部分:
1.回调View视图里的onMeasure过程
protectedvoid onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//设置该view的实际宽(mMeasuredWidth)高(mMeasuredHeight)
// setMeasuredDimension(h , l) ; 该方法必须在onMeasure中调用,否者报异常。
2、如果该View是ViewGroup类型,重写onMeasure()方法。则对它的每个子View进行measure()过程
int childCount = getChildCount() ;
for(int i=0 ;i<childCount ;i++){
//2.1、获得每个子View对象引用
View child = getChildAt(i) ;
//整个measure()过程就是个递归过程
//该方法只是一个过滤器,最后会调用measure()
measureChild(child , h, i) ;
}
}
protectedvoid measureChildWithMargins(View child,
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
//又会调用View中的measure(int , int);
Measure()的过程传递的布局参数,都封装在MeasureSpec类中,MeasureSpec由大小和模式组成,一共有三种模式:UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;AT_MOST(至多),子元素至多达到指定大小的值。
onLayout(boolean,int,int,int,int)
当View对象给它的所有子对象分配尺寸和位置时调用。
这个函数是一个抽象函数,要求实现ViewGroup的函数必须实现这个函数,这也就是ViewGroup是一个抽象函数的原因。因为各种组件实现的布局方式不一样,而onLayout是必须被重载的函数
ViewRootImpl.java
privatevoid performTraversals() {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
performDraw();
}
接着host.layout()开始View树的布局,继而回调给View/ViewGroup类中的layout()方法。具体流程如下
1、 layout方法会设置该View视图位于父视图的坐标轴,int left, int top, int right, int bottom) (调用setFrame()函数去实现)。
publicvoid layout(int l, int t, int r, int b) {
...
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
…
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
onLayout(changed, l, t, r, b);
...
}
接下来回调onLayout()方法;
protectedvoid onLayout(boolean changed, int left, int top, int right, int bottom) {
}
2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。(以RelativeLayout为例)
protectedvoid onLayout(boolean changed, int l, int t, int r, int b) {
finalint count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
RelativeLayout.LayoutParams st =
(RelativeLayout.LayoutParams) child.getLayoutParams();
child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
}
}
}
onSizeChanged(int,int,int,int)
当这个View对象的尺寸发生改变时,调用这个方法。
onDraw(Canvas)
从performTraversals()方法开始。
privatevoid performTraversals() {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
performDraw();
}
接着调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。
mView.draw()开始绘制,draw()方法实现的功能如下:
1 、绘制该View的背景
2 、为显示渐变框做一些准备操作
3、调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
4.1 dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个地方“需要重绘”的视图才会调用draw()方法)。值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
5、绘制滚动条
浙公网安备 33010602011771号