android 控件总结,Android制霸控件View总结

5cf20544175effe486bd40cf0c335ecd.png

关于Android View控件

Android中控件大致被分为两类ViewGroup,View。ViewGroup作为容器管理View。Android视图,是类似于Dom树的架构。父视图负责测量定位绘制等操作。我们经常在用的findViewById方法代价昂贵的原因,就是因为他负责至上而下遍历整棵控件树,来寻找View实例,在重复操作中尽量少用。现在在用的很多控件都是直接或者间接继承自View的,如下图。

83490a21ba96fc31562fea37f6f12998.png

view 继承树

Android UI界面架构

每个Activity包含一个PhoneWindow对象,PhoneWindow设置DecorView为应用窗口的根视图。在里面就是熟悉的TitleView和ContentView,没错,平时使用的setContentView()就是设置的ContentView。

e6a2b470e1079514ed98ab79cc1882af.png

UI 架构

Android是如何绘制View的?

当一个Activity启动时,会被要求绘制出它的布局。Android框架会处理这个请求,当然前提是Activity提供了合理的布局。绘制从根视图开始,从上至下遍历整棵视图树,每一个ViewGroup负责让自己的子View被绘制,每一个View负责绘制自己,通过draw()方法,绘制过程分三步走。

Measure

Layout

Draw

整个绘制流程是在ViewRoot中的performTraversals()方法展开的。部分源代码如下。

privatevoidperformTraversals() {

......

//最外层的根视图的widthMeasureSpec和heightMeasureSpec由来

//lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT

intchildWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);

intchildHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

......

mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

......

mView.layout(0,0, mView.getMeasuredWidth(), mView.getMeasuredHeight());

......

mView.draw(canvas);

......

}

在绘制之前当然要知道view的尺寸和绘制。所以先进行measu和layout(测量和定位),如下图。

a9737ee1052a6342a5b5d116b1db1a97.png

绘制流程

Measure过程

publicfinalvoidmeasure(intwidthMeasureSpec,intheightMeasureSpec) {

//....

//回调onMeasure()方法

onMeasure(widthMeasureSpec, heightMeasureSpec);

//more

}

计算view的实际大小,获得高宽存入mMeasuredHeight和mMeasureWidth,measure(int, int)传入的两个参数。MeasureSpec是一个32位int值,高2位为测量的模式,低30位为测量的大小。测量的模式可以分为以下三种。

EXACTLY

精确值模式,当layout_width或layout_height指定为具体数值,或者为match_parent时,系统使用EXACTLY。

AT_MOST

***值模式,指定为wrap_content时,控件的尺寸不能超过父控件允许的***尺寸。

UNSPECIFIED

不指定测量模式,View想多大就多大,一般不太使用。

根据上面的源码可知,measure方法不可被重写,自定义时需要重写的是onMeasure方法。

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec) {

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),

getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

}

查看源码可知,最终的高宽是调用setMeasuredDimension()设定的,如果不重写,默认是直接调用getDefaultSize获取尺寸的。

使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。

Layout过程

Layout方法就是用来确定view布局的位置,就好像你知道了一件东西的大小以后,总要知道位置才能画上去。

mView.layout(0,0, mView.getMeasuredWidth(), mView.getMeasuredHeight());

layout获取四个参数,左,上,右,下坐标,相对于父视图而言。这里可以看到,使用了刚刚测量的宽和高。

publicvoidlayout(intl,intt,intr,intb) {

intoldL = mLeft;

intoldT = mTop;

intoldB = mBottom;

intoldR = mRight;

booleanchanged = setFrame(l, t, r, b);

if(changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {

.....

onLayout(changed, l, t, r, b);

.....

}

通过setFrame设置坐标。如果坐标改变过了,则重新进行定位。如果是View对象,那么onLayout是个空方法。因为定位是由ViewGroup确定的。

当layout结束以后getWidth()与getHeight()才会返回正确的值。

这里出现一个问题,getWidth/Height() 和 getMeasuredWidth/Height()有什么区别?

getWidth():View在设定好布局后View的宽度。

getMeasuredWidth():对View上的內容进行测量后得到的View內容占据的宽度。

539b29c86f29e70795b5a9f7950d34f3.png

getwidth

Draw过程

publicvoiddraw(Canvas canvas) {

......

/*

* Draw traversal performs several drawing steps which must be executed

* in the appropriate order:

*

*      1. Draw the background

*      2. If necessary, save the canvas' layers to prepare for fading

*      3. Draw view's content

*      4. Draw children

*      5. If necessary, draw the fading edges and restore layers

*      6. Draw decorations (scrollbars for instance)

*/

// Step 1, draw the background, if needed

......

if(!dirtyOpaque) {

drawBackground(canvas);

}

// skip step 2 & 5 if possible (common case)

......

// Step 2, save the canvas' layers

......

if(drawTop) {

canvas.saveLayer(left, top, right, top + length, null, flags);

}

......

// Step 3, draw the content

if(!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children

dispatchDraw(canvas);

// Step 5, draw the fade effect and restore layers

......

if(drawTop) {

matrix.setScale(1, fadeHeight * topFadeStrength);

matrix.postTranslate(left, top);

fade.setLocalMatrix(matrix);

p.setShader(fade);

canvas.drawRect(left, top, right, top + length, p);

}

......

// Step 6, draw decorations (scrollbars)

onDrawScrollBars(canvas);

......

}

重点是第三步调用onDraw方法。其它几步都是绘制一些边边角角的东西比如背景、scrollBar之类的。其中dispatchDraw,是用来递归调用子View,如果没有则不需要。

onDraw方法是需要自己实现的,因为每个控件绘制的内容不同。主要用canvas对象进行绘制,这里就不说了。

参考资料

【编辑推荐】

【责任编辑:倪明 TEL:(010)68476606】

点赞 0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值