您的位置:首页 > 移动开发 > Android开发

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

2015-01-26 17:57 399 查看
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、如果该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、绘制滚动条
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐