android View的绘制过程和获取组件高宽值的三种方法


view绘制过程中的几个方法的调用顺序

 

onViewAdded

绘制View的流程是在onCreate中使用setContentView来设置要显示Layout文件或直接创建一个View,当调用setContentView后系统会对这个View进行解析,我们看到系统调用了onViewAdded()方法。



OnFinishInflate

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、如果该ViewViewGroup类型,重写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中的measureint , 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()方法。具体流程如下

1layout方法会设置该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、绘制滚动条



posted on 2015-01-26 17:57  Jason_ward  阅读(1253)  评论(0)    收藏  举报