Android制霸控件View总结
创始人
2025-01-27 21:41:08
0次

[[163808]]

关于Android View控件

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

view 继承树

Android UI界面架构

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

UI 架构

Android是如何绘制View的?

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

  • Measure
  • Layout
  • Draw

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

  1. private void performTraversals() { 
  2.     ...... 
  3.     //最外层的根视图的widthMeasureSpec和heightMeasureSpec由来 
  4.     //lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT 
  5.     int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); 
  6.     int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); 
  7.     ...... 
  8.     mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
  9.     ...... 
  10.     mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); 
  11.     ...... 
  12.     mView.draw(canvas); 
  13.     ...... 
  14. } 

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

绘制流程

Measure过程

  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {   
  2.     //....   
  3.  
  4.     //回调onMeasure()方法     
  5.     onMeasure(widthMeasureSpec, heightMeasureSpec);   
  6.  
  7.     //more   
  8. } 

计算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方法。

  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  2.         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 
  3.                 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); 
  4.     } 

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

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

Layout过程

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

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

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

  1. public void layout(int l, int t, int r, int b) { 
  2.     int oldL = mLeft; 
  3.     int oldT = mTop; 
  4.     int oldB = mBottom; 
  5.     int oldR = mRight; 
  6.     boolean changed = setFrame(l, t, r, b); 
  7.     if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { 
  8.         ..... 
  9.         onLayout(changed, l, t, r, b); 
  10.         ..... 
  11. } 

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

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

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

  • getWidth():View在设定好布局后View的宽度。
  • getMeasuredWidth():对View上的內容进行测量后得到的View內容占据的宽度。

getwidth

Draw过程

  1. public void draw(Canvas canvas) { 
  2.         ...... 
  3.         /* 
  4.          * Draw traversal performs several drawing steps which must be executed 
  5.          * in the appropriate order: 
  6.          * 
  7.          *      1. Draw the background 
  8.          *      2. If necessary, save the canvas' layers to prepare for fading 
  9.          *      3. Draw view's content 
  10.          *      4. Draw children 
  11.          *      5. If necessary, draw the fading edges and restore layers 
  12.          *      6. Draw decorations (scrollbars for instance) 
  13.          */ 
  14.  
  15.         // Step 1, draw the background, if needed 
  16.         ...... 
  17.         if (!dirtyOpaque) { 
  18.             drawBackground(canvas); 
  19.         } 
  20.  
  21.         // skip step 2 & 5 if possible (common case) 
  22.         ...... 
  23.  
  24.         // Step 2, save the canvas' layers 
  25.         ...... 
  26.             if (drawTop) { 
  27.                 canvas.saveLayer(left, top, right, top + length, null, flags); 
  28.             } 
  29.         ...... 
  30.  
  31.         // Step 3, draw the content 
  32.         if (!dirtyOpaque) onDraw(canvas); 
  33.  
  34.         // Step 4, draw the children 
  35.         dispatchDraw(canvas); 
  36.  
  37.         // Step 5, draw the fade effect and restore layers 
  38.         ...... 
  39.         if (drawTop) { 
  40.             matrix.setScale(1, fadeHeight * topFadeStrength); 
  41.             matrix.postTranslate(left, top); 
  42.             fade.setLocalMatrix(matrix); 
  43.             p.setShader(fade); 
  44.             canvas.drawRect(left, top, right, top + length, p); 
  45.         } 
  46.         ...... 
  47.  
  48.         // Step 6, draw decorations (scrollbars) 
  49.         onDrawScrollBars(canvas); 
  50.         ...... 
  51.     } 

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

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

参考资料

  1. Android视图绘制流程完全解析,带你一步步深入了解View(二)
  2. Android应用层View绘制流程与源码分析
  3. How Android Draws Views
  4. 《Android群英传》
  5. What is the difference between getWidth/Height() and getMeasuredWidth/Height() in Android SDK?

相关内容

热门资讯

PHP新手之PHP入门 PHP是一种易于学习和使用的服务器端脚本语言。只需要很少的编程知识你就能使用PHP建立一个真正交互的...
网络中立的未来 网络中立性是什... 《牛津词典》中对“网络中立”的解释是“电信运营商应秉持的一种原则,即不考虑来源地提供所有内容和应用的...
各种千兆交换机的数据接口类型详... 千兆交换机有很多值得学习的地方,这里我们主要介绍各种千兆交换机的数据接口类型,作为局域网的主要连接设...
粉嫩如何诠释霸道 东芝M805... “霸道粉”是个什么玩意东芝M805拿过来的时候,笔者扑哧笑了,不是笑这款笔记本,而是笑这款产品的颜色...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
什么是大数据安全 什么是大数据... 在《为什么需要大数据安全分析》一文中,我们已经阐述了一个重要观点,即:安全要素信息呈现出大数据的特征...
全面诠释网络负载均衡 负载均衡的出现大大缓解了服务器的压力,更是有效的利用了资源,提高了效率。那么我们现在来说一下网络负载...
只需三步,让你掌握阿尔法路由器... 请参照以下阿尔法路由器设置方法,通过浏览器进入及设置宽带路由器。经过这三个步骤的设置,基本上能够熟悉...
如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
30分钟搞定iOS自定义相机 最近公司的项目中用到了相机,由于不用系统的相机,UI给的相机切图,必须自定义才可以。就花时间简单研究...