美文网首页
Android自定义View

Android自定义View

作者: 我要离开浪浪山 | 来源:发表于2021-10-18 14:05 被阅读0次

    1、自定义View的基本方法

    自定义View的最基本的三个方法分别是: onMeasure()、onLayout()、onDraw(); View在Activity中显示出来,要经历测量、布局和绘制三个步骤,分别对应三个动作:measure、layout和draw。

    • 测量:onMeasure()决定View的大小;
    • 布局:onLayout()决定View在ViewGroup中的位置;
    • 绘制:onDraw()决定绘制这个View。

    2、自定义控件分类

    • 自定义View: 只需要重写onMeasure()和onDraw()
    • 自定义ViewGroup: 则只需要重写onMeasure()和onLayout()

    3、View的分类

    类别 解释 特点
    单一视图 即一个View,如TextView 不包含子View
    视图组 即多个View组成的ViewGroup,如LinearLayout 包含子View

    4、View类简介

    • View类是Android中各种组件的基类,如View是ViewGroup基类
    • View表现为显示在屏幕上的各种视图

    Android中的UI组件都由View、ViewGroup组成。

    View的构造函数:共有4个

    // 如果View是在Java代码里面new的,则调用第一个构造函数
     public CarsonView(Context context) {        
        super(context);  
     }
    
    // 如果View是在.xml里声明的,则调用第二个构造函数
    // 自定义属性是从AttributeSet参数传进来的   
     public  CarsonView(Context context, AttributeSet attrs) {       
          super(context, attrs);  
     }
    
    // 不会自动调用
    // 一般是在第二个构造函数里主动调用// 如View有style属性时    
    public  CarsonView(Context context, AttributeSet attrs, int defStyleAttr) {        
        super(context, attrs, defStyleAttr);   
    }
    
    
    // 不会自动调用
    // 一般是在第二个构造函数里主动调用// 如View有style属性时    
    public  CarsonView(Context context, AttributeSet attrs, int defStyleAttr) {       
        super(context, attrs, defStyleAttr);  
     }    
    
    //API21之后才使用    
    // 不会自动调用    
    // 一般是在第二个构造函数里主动调用    
    // 如View有style属性时    
    public  CarsonView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {       
        super(context, attrs, defStyleAttr, defStyleRes);
    }
    

    5、AttributeSet与自定义属性

    系统自带的View可以在xml中配置属性,对于写的好的自定义View同样可以在xml中配置属性,为了使自定义的View的属性可以在xml中配置,需要以下4个步骤:

      1. 通过<declare-styleable>为自定义View添加属性
      1. 在xml中为相应的属性声明属性值
      1. 在运行时(一般为构造函数)获取属性值
      1. 将获取到的属性值应用到View

    6、View视图结构

      1. PhoneWindow是Android系统中最基本的窗口系统,继承自Windows类,负责管理界面显示以及事件响应。它是Activity与View系统交互的接口
      1. DecorView是PhoneWindow中的起始节点View,继承于View类,作为整个视图容器来使用。用于设置窗口属性。它本质上是一个FrameLayout
      1. ViewRoot在Activtiy启动时创建,负责管理、布局、渲染窗口UI等等
    image.png
    • 4.对于多View的视图,结构是树形结构:最顶层是ViewGroup,ViewGroup下可能有多个ViewGroup或View,如下图:
    image.png

    一定要记住:无论是measure过程、layout过程还是draw过程,永远都是从View树的根节点开始测量或计算(即从树的顶端开始),一层一层、一个分支一个分支地进行(即树形递归),最终计算整个View树中各个View,最终确定整个View树的相关属性。

    7、Android坐标系

    Android的坐标系定义为:

    • 屏幕的左上角为坐标原点
    • 向右为x轴增大方向
    image.png

    8、View位置(坐标)描述

    View的位置由4个顶点决定的 4个顶点的位置描述分别由4个值决定:

    请记住:View的位置是相对于父控件而言的)

    • Top:子View上边界到父view上边界的距离
    • Left:子View左边界到父view左边界的距离
    • Bottom:子View下边距到父View上边界的距离
    • Right:子View右边界到父view左边界的距离

    9、位置获取方式

    View的位置是通过view.getxxx()函数进行获取:(以Top为例)

    // 获取Top位置
    public final int getTop() {     
       return mTop;  
    }  
    // 其余如下:  
    getLeft();      //获取子View左上角距父View左侧的距离  
    getRight();     //获取子View右下角距父View左侧的距离
    getTop():    //获取子View右上角距父View顶部的距离
    getBottom();    //获取子View右下角距父View顶部的距离  
    
    

    与MotionEvent中 get()和getRaw()的区别

    //get() :触摸点相对于其所在组件坐标系的坐标 
    event.getX();        
    event.getY();
    //getRaw() :触摸点相对于屏幕默认坐标系的坐标 
    event.getRawX();     
    event.getRawY();
    
    image.png

    10、View树的绘制流程是谁负责的?

    • view树的绘制流程是通过ViewRoot去负责绘制的;
    • ViewRoot这个类的命名有点坑,最初看到这个名字,翻译过来是view的根节点,但是事实完全不是这样,ViewRoot其实不是View的根节点,它连view节点都算不上;
    • 它的主要作用是View树的管理者,负责将DecorView和PhoneWindow“组合”起来,而View树的根节点严格意义上来说只有DecorView;
    • 每个DecorView都有一个ViewRoot与之关联,这种关联关系是由WindowManager去进行管理的;

    11、view的添加

    image.png

    12、view的绘制流程

    image.png

    13、measure

      1. 系统为什么要有measure过程?
      1. measure过程都干了点什么事?
      1. 对于自适应的尺寸机制,如何合理的测量一颗View树?
      1. 那么ViewGroup是如何向子View传递限制信息的?
      1. ScrollView嵌套ListView问题?


        image.png

    14、layout

      1. 系统为什么要有layout过程?
      1. layout过程都干了点什么事?
    image.png

    15、draw

      1. 系统为什么要有draw过程?
      1. draw过程都干了点什么事?
    image.png

    16、LayoutParams

    • LayoutParams翻译过来就是布局参数,子View通过LayoutParams告诉父容器(ViewGroup)应该如何放置自己。
    • 从这个定义中也可以看出来LayoutParams与ViewGroup是息息相关的,因此脱离ViewGroup谈LayoutParams是没有意义的。
    • 事实上,每个ViewGroup的子类都有自己对应的LayoutParams类,典型的如LinearLayout.LayoutParams和FrameLayout.LayoutParams等,可以看出来LayoutParams都是对应ViewGroup子类的内部类。

    17、MarginLayoutParams

    MarginLayoutParams是和外间距有关的。事实也确实如此,和LayoutParams相比,MarginLayoutParams只是增加了对上下左右外间距的支持。实际上大部分LayoutParams的实现类都是继承自MarginLayoutParams,因为基本所有的父容器都是支持子View设置外间距的。

    • 属性优先级问题 MarginLayoutParams主要就是增加了上下左右4种外间距。在构造方法中,先是获取了margin属性;如果该值不合法,就获取horizontalMargin;如果该值不合法,再去获取leftMargin和rightMargin属性(verticalMargin、topMargin和bottomMargin同理)。我们可以据此总结出这几种属性的优先级
    margin > horizontalMargin和verticalMargin > leftMargin和RightMargin、topMargin和bottomMargin
    
    • 属性覆盖问题 优先级更高的属性会覆盖掉优先级较低的属性。此外,还要注意一下这几种属性上的注释
    Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
    

    18、LayoutParams与View如何建立联系

    • 在XML中定义View
    • 在Java代码中直接生成View对应的实例对象

    19、addView

    /** * 重载方法1:添加一个子View * 如果这个子View还没有LayoutParams,就为子View设置当前ViewGroup默认的LayoutParams
     */
    public void addView(View child) {    
        addView(child, -1);
    }
    /** * 重载方法2:在指定位置添加一个子View 
    * 如果这个子View还没有LayoutParams,就为子View设置当前ViewGroup默认的LayoutParams
     * @param index View将在ViewGroup中被添加的位置(-1代表添加到末尾) 
    */
    public void addView(View child, int index) {   
         if (child == null) {        
             throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");  
         }   
           LayoutParams params = child.getLayoutParams();   
           if (params == null) {       
               params = generateDefaultLayoutParams();// 生成当前ViewGroup默认的LayoutParams        
              if (params == null) {            
                        throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");      
           }  
     }    
      addView(child, index, params);
    }
    
    /** * 重载方法3:添加一个子View 
    * 使用当前ViewGroup默认的LayoutParams,并以传入参数作为LayoutParams的width和height 
    */
    public void addView(View child, int width, int height) {   
       final LayoutParams params = generateDefaultLayoutParams();  // 生成当前ViewGroup默认的LayoutParams    
         params.width = width;   
         params.height = height;    
         addView(child, -1, params);
    }
    
    /** * 重载方法4:添加一个子View,并使用传入的LayoutParams */
    
    @Override
    public void addView(View child, LayoutParams params) {   
          addView(child, -1, params);
    }
    
    /** * 重载方法4:在指定位置添加一个子View,并使用传入的LayoutParams 
    */
    public void addView(View child, int index, LayoutParams params) {   
             if (child == null) {        
                throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");   
    }
         requestLayout();    
         invalidate(true);    
         addViewInner(child, index, params, false);
    }
    

    20、自定义LayoutParams

    1. 创建自定义属性

    <resources>    
             <declare-styleable name="xxxViewGroup_Layout">       
                      <!-- 自定义的属性 -->        
                      <attr name="layout_simple_attr" format="integer"/>       
                      <!-- 使用系统预置的属性 -->       
                     <attr name="android:layout_gravity"/>    
            </declare-styleable>
    </resources>
    

    2. 继承MarginLayout

    public static class LayoutParams extends ViewGroup.MarginLayoutParams {   
    
          public int simpleAttr;    
          public int gravity;    
          public LayoutParams(Context c, AttributeSet attrs) {        
              super(c, attrs);        
              // 解析布局属性       
              TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.SimpleViewGroup_Layout);       
           simpleAttr = typedArray.getInteger(R.styleable.SimpleViewGroup_Layout_layout_simple_attr, 0);         
     gravity=typedArray.getInteger(R.styleable.SimpleViewGroup_Layout_android_layout_gravity, -1);        
    typedArray.recycle();//释放资源   
    }    
    
    public LayoutParams(int width, int height) {        s
           uper(width, height);  
     }   
    
     public LayoutParams(MarginLayoutParams source) {
           super(source);
    }
    
     public LayoutParams(ViewGroup.LayoutParams source) {         
          super(source);
      }
    }
    

    3. 重写ViewGroup中几个与LayoutParams相关的方法

    // 检查LayoutParams是否合法
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {      return p instanceof SimpleViewGroup.LayoutParams;
    }
    
    // 生成默认的LayoutParams
    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {     return new SimpleViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    }
    
    // 对传入的LayoutParams进行转化
    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {     
        return new SimpleViewGroup.LayoutParams(p);
    }
    
    // 对传入的LayoutParams进行转化
    @Overridepublic ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {     
        return new SimpleViewGroup.LayoutParams(getContext(), attrs);
    }
    
    

    21、LayoutParams常见的子类

    在为View设置LayoutParams的时候需要根据它的父容器选择对应的LayoutParams,否则结果可能与预期不一致,这里简单罗列一些常见的LayoutParams子类:

    • ViewGroup.MarginLayoutParams
    • FrameLayout.LayoutParams
    • LinearLayout.LayoutParams
    • RelativeLayout.LayoutParams
    • RecyclerView.LayoutParams
    • GridLayoutManager.LayoutParams
    • StaggeredGridLayoutManager.LayoutParams
    • ViewPager.LayoutParams
    • WindowManager.LayoutParams

    22、MeasureSpec

    image.png

    测量规格,封装了父容器对 view 的布局上的限制,内部提供了宽高的信息( SpecMode 、 SpecSize ),SpecSize是指在某种SpecMode下的参考尺寸,其中SpecMode 有如下三种:

    • UNSPECIFIED 父控件不对你有任何限制,你想要多大给你多大,想上天就上天。这种情况一般用于系统内部,表示一种测量状态。(这个模式主要用于系统内部多次Measure的情形,并不是真的说你想要多大最后就真有多大)
    • EXACTLY 父控件已经知道你所需的精确大小,你的最终大小应该就是这么大。
    • AT_MOST 你的大小不能大于父控件给你指定的size,但具体是多少,得看你自己的实现。
    image.png

    针对上表,这里再做一下具体的说明

    • 对于应用层 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定
    • 对于不同的父容器和view本身不同的LayoutParams,view就可以有多种MeasureSpec。
      1. 当view采用固定宽高的时候,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精确模式并且其大小遵循Layoutparams中的大小;
      1. 当view的宽高是match_parent时,这个时候如果父容器的模式是精准模式,那么view也是精准模式并且其大小是父容器的剩余空间,如果父容器是最大模式,那么view也是最大模式并且其大小不会超过父容器的剩余空间;
      1. 当view的宽高是wrap_content时,不管父容器的模式是精准还是最大化,view的模式总是最大化并且大小不能超过父容器的剩余空间。
      1. Unspecified模式,这个模式主要用于系统内部多次measure的情况下,一般来说,我们不需要关注此模式(这里注意自定义View放到ScrollView的情况 需要处理)。

    23、Drawable是什么?

    一种可以在Canvas上进行绘制的抽象的概念 颜色、图片等都可以是一个
    Drawable Drawable可以通过XML定义,或者通过代码创建 Android中 Drawable是一个抽象类,每个具体的Drawable都是其子类

    24、Drawable的优点

    使用简单,比自定义View成本低 非图片类的Drawable所占空间小,能减 小apk大小 Drawable的内部宽/高

    一般getIntrinsicWidth/Height能获得内部宽/高 图片Drawable其内部宽高就是图
    片的宽高 颜色Drawable没有内部宽高的概念 内部宽高不等同于它的大小,一般
    Drawable没有大小概念(作为View背景时,会被拉伸至View的大小)

    25、自定义Drawable

    一般作为ImageView的图像来显示 另一个是作为View的背景 自定义 Drawable主要就是实现draw方法 setAlpha、setColorFilter、getOpacity也 需要重写,但是模板固定 当自定义Drawable有固定大小时(比如绘制一 张图片),需要重写getIntrinsicWidth()/getIntrinsicHeight()方法(默认返 回-1),会影响到View的wrap_content布局 内部固定大小不等于Drawable 的实际区域大小,getBounds能获得实际区域大小

    相关文章

      网友评论

          本文标题:Android自定义View

          本文链接:https://www.haomeiwen.com/subject/caagoltx.html