• 深入理解Android中View


    文章目录

     

    [隐藏]

     这回我们是深入到View内部,去研究View,去了解View的工作,抛弃其他因素,以便为以后能灵活的使用自定义空间打下一定的基础。希望有志同道合的朋友一起来探讨,深入Android内部,深入理解Android。

    一、View是什么?

           View是什么了,每个人都有自己的理解。在Android的官方文档中是这样描述的:这个类表示了用户界面的基本构建模块。一个View占用了屏幕上的一个矩形区域并且负责界面绘制和事件处理。View是用来构建用户界面组件(Button,Textfields等等)的基类。ViewGroup子类是各种布局的基类,它是个包含其他View(或其他ViewGroups)和定义这些View布局参数的容器。

           其实说白了,View就是一个矩形区域,我们可以在这个区域上定义自己的控件。

           注明:有对系统回调不太了解的回头看看回调,这样有助于对文章的理解。

    二、View创建的一个概述:

           在API中对View的回调流程有以个详细的描述,下面给出了原文翻译:(翻译有点仓促,大家多多包涵,有啥错的地方麻烦告知下我,我好改过来)

           1.Creation           :创建

             ----Constructors(构造器)    

           There is a form of the constructor that arecalled when the view is created from code and a form that is called when theview is inflated from a layout file. The second form should parse and apply anyattributes defined in the layout file.在构造器中有个一个表单当View从代码中创建和从Layout File 文件中创建时。第二个表单应该解析和应用一些在Layout File中定义的属性。

             ---- onFinishInflate()

            Called after a view and all of itschildren has been inflated from XML.当View和他的所有子View从XML中解析完成后调用。

           2. Layout            :布局

             ----onMeasure(int, int)

            Called to determine the size requirementsfor this view and all of its children.   确定View和它所有的子View要求的尺寸时调用

             ---- onLayout(boolean, int, int,int, int)

            Calledwhen this view should assign a size and position to all of its children当这个View为其所有的子View指派一个尺寸和位置时调用

             ---- onSizeChanged(int, int, int,int)

            Calledwhen the size of this view has changed.当这个View的尺寸改变后调用

           3. Drawing         :绘制

             ---- onDraw(Canvas)

            Calledwhen the view should render its content.当View给定其内容时调用

           4.Event processing    :事件流程

             ----onKeyDown(int, KeyEvent)

            Calledwhen a new key event occurs.当一个新的键按下时

             ---- onKeyUp(int, KeyEvent)  

            Calledwhen a key up event occurs.当一个键弹起时

             ----onTrackballEvent(MotionEvent)

            Calledwhen a trackball motion event occurs.当滚迹球事件发生时。

             ----onTouchEvent(MotionEvent)

            Calledwhen a touch screen motion event occurs.当一个触摸屏事件发生时。

           5. Focus              :焦点

             ---- onFocusChanged(boolean, int,Rect)

            onFocusChanged(boolean,int, Rect)当View得到和失去焦点时调用

            ---- onWindowFocusChanged(boolean)

            Called when the windowcontaining the view gains or loses focus.当Window包含的View得到或失去焦点时调用。

           根据View里面方法调用流程的概述,我们来重写其中的几个回调方法来直观的了解下这个调用,具体代码这里就不贴了,代码见测试包:DEMO_View调用流程.rar,调用的log显示:

           这样大家就对View的调用有了个大概的认识,下面将针对View的标志系统、View的的布局参数系统等做一个简单的描述。

    三、View的标志(Flag)系统

           在一个系统中往往使用标志来指示系统中的某些参数,这里对View的标志系统做一些简单的介绍,这样大家可以借鉴下,以后也可以用这种表示方法。

           一般而言标志都是成对出现的也就是表示相反两个属性,对于这种属性的表示方法我们使用一位的0和1就可以表示。如果有多个成对属性,如果每对属性都用一个int值来标志是不方便的。这种情况通常是用一个int的各个位来分别表示每个标志,在处理器中有一个标志位就是采用这种方式设计的。

    我们先来看看位运算。位运算符包括: 与(&)、非(~)、或(|)、异或(^)

          &:   当两边操作数的位同时为1时,结果为1,否则为0。如1100&1010=1000   

           |:   当两边操作数的位有一边为1时,结果为1,否则为0。如1100|1010=1110   

           ~:   0变1,1变0   

           ^:   两边的位不同时,结果为1,否则为0.如1100^1010=0110

           在View系统使用mViewFlags来表征这些属性,其设置的主要方法如下

    1. void setFlag(int mask, int falg)   
    2.     {   
    3.         int old = mViewFlags;①   
    4.         mViewFlags = (mViewFlags & ~mask) | (mask & falg);②   
    5.     int changed = mViewFlags ^ old;// 获取改变的位,方法是对改变的位置1③    
    6.     ... ...   
    7.     }  
    void setFlag(int mask, int falg) 
        { 
            int old = mViewFlags;① 
            mViewFlags = (mViewFlags & ~mask) | (mask & falg);② 
        int changed = mViewFlags ^ old;// 获取改变的位,方法是对改变的位置1③ 
        ... ... 
        }

    其中mask指的是标志位所在的位,falg表示的标志位。下面举个例子:

    1. public static final int VISIBLE = 0x00000000;   
    2.     public static final int INVISIBLE = 0x00000004;   
    3.     public static final int GONE = 0x00000008;   
    4. static final int VISIBILITY_MASK = 0x0000000C;  
    public static final int VISIBLE = 0x00000000; 
        public static final int INVISIBLE = 0x00000004; 
        public static final int GONE = 0x00000008; 
    static final int VISIBILITY_MASK = 0x0000000C;

           其中VISIBLE和INVISIBLE和GONE就是标志位,VISIBILITY_MASK是标志位所在的位,也就有VISIBLE+INVISIBLE+GON=VISIBILITY_MASK。看不懂的把上面四个转换为二进制就看出来了。

           为什么要使用VISIBILITY_MASK?会不会有些多余呢?我们来看View中的计算公式:

    1. mViewFlags = (mViewFlags & ~mask) | (mask & falg);②  
    mViewFlags = (mViewFlags & ~mask) | (mask & falg);②

           其中mViewFlags & ~mask是用来将mViewFlags中表示该标志的位置零。mask & falg是用来获得标志位。举个例子:

           假设mViewFlags的二进制表示为110000;flag为INVISIBLE我们将上面的标志位转换为二进制VISIBLE 0000、INVISIBLE 0100、GONE 1000、VISIBILITY_MASK 1100。

    mViewFlags & ~mask=110000 & 0011 = 110000(上面所用的标志位占用的是最后四位,我们通过这个运算来将这个标志位置零)。

    1. mask & falg = 1100 & 0100 =0100(获得标志)。  
    mask & falg = 1100 & 0100 =0100(获得标志)。

           110000 | 0100(通过或运算来计算出最后的标志)。

           一般而言:在多个同种类型的标志中,通常使用0来作为默认的标志。关于上面的标志系统的其他具体使用我们就不再深入,有兴趣的可以自行深入,有啥好的想法在群里分享下。

    四、MeasureSpec

           在View系统中,指定宽和高,以及指定布局的属性,是由MeasureSpec来封装的。下面是各个模式的标志位表示。

    1. private static final int MODE_SHIFT = 30;   
    2.         private static final int MODE_MASK  = 0x3 << MODE_SHIFT;   
    3.         /**  
    4.          * Measure specification mode: The parent has not imposed any constraint  
    5.          * on the child. It can be whatever size it wants.  
    6.          */  
    7.         public static final int UNSPECIFIED = 0 << MODE_SHIFT;   
    8.         /**  
    9.          * Measure specification mode: The parent has determined an exact size  
    10.          * for the child. The child is going to be given those bounds regardless  
    11.          * of how big it wants to be.  
    12.          */  
    13.         public static final int EXACTLY     = 1 << MODE_SHIFT;   
    14.         /**  
    15.          * Measure specification mode: The child can be as large as it wants up  
    16.          * to the specified size.  
    17.          */  
    18.     public static final int AT_MOST     = 2 << MODE_SHIFT;  
    private static final int MODE_SHIFT = 30; 
            private static final int MODE_MASK  = 0x3 << MODE_SHIFT; 
            /** 
             * Measure specification mode: The parent has not imposed any constraint 
             * on the child. It can be whatever size it wants. 
             */
            public static final int UNSPECIFIED = 0 << MODE_SHIFT; 
            /** 
             * Measure specification mode: The parent has determined an exact size 
             * for the child. The child is going to be given those bounds regardless 
             * of how big it wants to be. 
             */
            public static final int EXACTLY     = 1 << MODE_SHIFT; 
            /** 
             * Measure specification mode: The child can be as large as it wants up 
             * to the specified size. 
             */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

           在这个解析系统中是通过移位来存放更多的数据,现在每个数据标志位都向左移动了30位。这样表示一个View大小是很方便的,我们来看下面的方法:

    1. public static int makeMeasureSpec(int size, int mode) {   
    2.             return size + mode;   
    3.         }  
    public static int makeMeasureSpec(int size, int mode) { 
                return size + mode; 
            }

           通过这个方法就可以制作一个含有两个参数的int值,这个参数包含一个mode标志和一个宽或高的表示。

           我们通过如下方法来获取到mode:

    1. public static int getMode(int measureSpec) {   
    2.             return (measureSpec & MODE_MASK);   
    3.         }  
    public static int getMode(int measureSpec) { 
                return (measureSpec & MODE_MASK); 
            }

           我们也可以用下面方法来获取高或宽的数据表示:

    1. public static int getSize(int measureSpec) {   
    2.             return (measureSpec & ~MODE_MASK);   
    3.         }  
    public static int getSize(int measureSpec) { 
                return (measureSpec & ~MODE_MASK); 
            }

    五、几个重要方法简介

           正如第二节写的那个调用流程一样,这几个重要的方法是系统回调是调用的,同样对于这几个方法也是自定义组件的重要的方法。

           在这节里我们主要是了解这些方法的用途,以期在自定义组件时可以对这些方法得心应手。

    5.1 onFinishInflate()

           这个是当系统解析XML完成,并且将子View全部添加完成之后调用这个方法,我们通常重写这个方法,在这个方法中查找并获得子View引用,当然前提是这个View中有子View所以一般都是继承ViewGroup时用这个方法比较多,比如抽屉效果中:

    1. @Override  
    2. protected void onFinishInflate() {   
    3.     mHandle = findViewById(mHandleId);   
    4.     if (mHandle == null) {   
    5.         throw new IllegalArgumentException("The handle attribute is must refer to an"  
    6.                 + " existing child.");   
    7.     }   
    8.     mHandle.setOnClickListener(new DrawerToggler());   
    9.          
    10.     mContent = findViewById(mContentId);   
    11.     if (mContent == null) {   
    12.         throw new IllegalArgumentException("The content attribute is must refer to an"  
    13.                 + " existing child.");   
    14.     }   
    15.     mContent.setVisibility(View.GONE);   
    16. }  
    @Override
    protected void onFinishInflate() { 
        mHandle = findViewById(mHandleId); 
        if (mHandle == null) { 
            throw new IllegalArgumentException("The handle attribute is must refer to an"
                    + " existing child."); 
        } 
        mHandle.setOnClickListener(new DrawerToggler()); 
           
        mContent = findViewById(mContentId); 
        if (mContent == null) { 
            throw new IllegalArgumentException("The content attribute is must refer to an"
                    + " existing child."); 
        } 
        mContent.setVisibility(View.GONE); 
    }

          通过重写这个方法来获取手柄的View和要显示内容的View。

    5.2 onMeasure(int, int)

           测量这个View的高和宽。通过调用这个方法来设置View的测量后的高和宽,其最终调用的方法是:

    1. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {   
    2.         mMeasuredWidth = measuredWidth;   
    3.         mMeasuredHeight = measuredHeight;   
    4.           
    5.         mPrivateFlags |= MEASURED_DIMENSION_SET;   
    6.     }  
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { 
            mMeasuredWidth = measuredWidth; 
            mMeasuredHeight = measuredHeight; 
            
            mPrivateFlags |= MEASURED_DIMENSION_SET; 
        }

           可见其最终是将高和宽保存在mMeasuredWidth、mMeasuredHeight这两个参数中。

           其实调用onMeasure(int, int)的方法的不是系统,而是

               public final voidmeasure(int widthMeasureSpec, int heightMeasureSpec)

           这个才是系统回调的方法,然后通过这个方法调用onMeasure(int, int)方法,个人感觉这种设计就是把系统方法和用户可以重写的方法分离开,这样避免一些不必要的错误。

           在这个方法中主要是用来初始化各个子View的布局参数,我们来看看抽屉中的实现:

    1. @Override  
    2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   
    3.     int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);   
    4.     int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec);   
    5.          
    6.     int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);   
    7.     int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);   
    8.          
    9.     if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {   
    10.         throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions");   
    11.     }   
    12.          
    13.     final View handle = mHandle;   
    14.     measureChild(handle, widthMeasureSpec, heightMeasureSpec);   
    15.          
    16.     if (mVertical) {   
    17.         int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;   
    18.         mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY),   
    19.                 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));   
    20.     } else {   
    21.         int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;   
    22.         mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),   
    23.                 MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY));   
    24.     }   
    25.          
    26.     setMeasuredDimension(widthSpecSize, heightSpecSize);   
    27. }  
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 
        int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec); 
           
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 
        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec); 
           
        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { 
            throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions"); 
        } 
           
        final View handle = mHandle; 
        measureChild(handle, widthMeasureSpec, heightMeasureSpec); 
           
        if (mVertical) { 
            int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset; 
            mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY), 
                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 
        } else { 
            int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset; 
            mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 
                    MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY)); 
        } 
           
        setMeasuredDimension(widthSpecSize, heightSpecSize); 
    }

           刚才我们已经获取到mHandle和mContent的引用,因为onFinishInflate()方法调用在onMeasure(int, int)方法之前,所以这个不会出现nullPoint。我们可以看到在这个方法中主要就是为mHandle和mContent指定了布局参数。这里用到了MeasureSpec。

    5.3 onLayout(boolean, int, int,int, int)

           onLayout是用来指定各个子View的位置,这个方法和上面方法类似,也不是真正的系统回调函数,真正的回调函数是Layout。这个方法的使用主要在ViewGroup中。这里不再详述。我们在ViewGroup讲解时再去了解这个方法。

    5.4 onSizeChanged(int, int, int,int)

           这个是当View的大小改变时调用,这个也不再详述,基本上用的也比较少。

    5.5 onDraw(android.graphics.Canvas)

           这个方法相信大家都不会陌生了,在我以前的博客里也有这个方法的使用。当然那个比较入门,比较肤浅,呵呵。这里我们深入进去,类似于onMeasure(int, int),其实这个方法是由draw(Canvas)方法调用的。在这个方法中有一个对这个方法的描述:

    1. /*  
    2.         * Draw traversal performs several drawing steps which must be executed  
    3.         * in the appropriate order:  
    4.         *  
    5.         *      1. Draw the background  
    6.         *      2. If necessary, save the canvas' layers to prepare for fading  
    7.         *      3. Draw view's content  
    8.         *      4. Draw children  
    9.         *      5. If necessary, draw the fading edges and restore layers  
    10.         *      6. Draw decorations (scrollbars for instance)  
    11.         */  
    /* 
            * 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) 
            */

           我们可以看到:

                   首先是绘制背景

                   其次如果需要准备层之间的阴影

                   然后绘制内容(这个内容就是调用我们的onDraw方法)

                   再绘制children(dispatchDraw(canvas);)这个方法的调用主要实现在ViewGroup中,和继承ViewGroup的组件中。

                   如果需要绘制层之间的阴影。

                   绘制装饰,也就是scrollbars。

           dispatchDraw(canvas);这也是一个重要的方法,用于绘制子组件用的。下面是抽屉中的实现方法。也比较简单,大家自行阅读下也就了解了。

    1. @Override  
    2.   protected void dispatchDraw(Canvas canvas) {   
    3.       final long drawingTime = getDrawingTime();   
    4.       final View handle = mHandle;   
    5.       final boolean isVertical = mVertical;   
    6.          
    7.       drawChild(canvas, handle, drawingTime);   
    8.          
    9.       if (mTracking || mAnimating) {   
    10.           final Bitmap cache = mContent.getDrawingCache();   
    11.           if (cache != null) {   
    12.               if (isVertical) {   
    13.                   canvas.drawBitmap(cache, 0, handle.getBottom(), null);   
    14.               } else {   
    15.                   canvas.drawBitmap(cache, handle.getRight(), 0, null);                       
    16.               }   
    17.           } else {   
    18.               canvas.save();   
    19.               canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset,   
    20.                       isVertical ? handle.getTop() - mTopOffset : 0);   
    21.               drawChild(canvas, mContent, drawingTime);   
    22.               canvas.restore();   
    23.           }   
    24.       } else if (mExpanded) {   
    25.           drawChild(canvas, mContent, drawingTime);   
    26.       }   
    27.   }  
    @Override
      protected void dispatchDraw(Canvas canvas) { 
          final long drawingTime = getDrawingTime(); 
          final View handle = mHandle; 
          final boolean isVertical = mVertical; 
           
          drawChild(canvas, handle, drawingTime); 
           
          if (mTracking || mAnimating) { 
              final Bitmap cache = mContent.getDrawingCache(); 
              if (cache != null) { 
                  if (isVertical) { 
                      canvas.drawBitmap(cache, 0, handle.getBottom(), null); 
                  } else { 
                      canvas.drawBitmap(cache, handle.getRight(), 0, null);                     
                  } 
              } else { 
                  canvas.save(); 
                  canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset, 
                          isVertical ? handle.getTop() - mTopOffset : 0); 
                  drawChild(canvas, mContent, drawingTime); 
                  canvas.restore(); 
              } 
          } else if (mExpanded) { 
              drawChild(canvas, mContent, drawingTime); 
          } 
      }

           好了,这个就是View里面的内容,关于事件监听我们这里就不再详细描述,自定义组件的话,在写完深入ViewGroup中会有一个专门的专题,而ViewGroup中也会去深化View中一些东西。

           欢迎大家加入:Android研究交流群:194802363

  • 相关阅读:
    【dp】船
    【dp】PKU 1952 buy low,buy lower
    【dp】合唱队形
    【dp】导弹拦截
    【dfs】POJ1321 棋盘问题
    工程师工作与学习的模式
    如何进行大规模在线数据迁移(来自Stripe公司的经验)
    Laravel事件监听器listener与事件订阅者Subscriber的区别
    微信小程序时间处理问题
    [翻译] Facebook HHVM 团队封闭开发三周成果展
  • 原文地址:https://www.cnblogs.com/SZ2015/p/4595390.html
Copyright © 2020-2023  润新知