美文网首页Android自定义View
自定义 view - 前置知识点

自定义 view - 前置知识点

作者: 前行的乌龟 | 来源:发表于2018-07-14 06:10 被阅读39次

    ps :在系统的学习自定义 view 之前,搞懂本本篇的内容会让你学习的过程顺序,简单很多

    view 的分类

    view 其实就2种:

    • 单一视图 view
      我们常用的各种具体控件都是 view ,比如 TextView
    • 视图容器 ViewGroup
      视图容器简单说就是各种 layout 布局,里面用来存放 view ,给 view 定位的。想我们常用的 LinearLayout 就是视图容器

    自定义 view 的3个核心方法

    • onMeasure
      根据 view 的测量模式计算确定 view 的宽高
    • onLayout
      ViewGroup 中对所有的子 view 排版,决定子 view 的位置
    • onDraw
      具体绘制 view

    这3个方法就是自定义的核心了,自定义 view 不管我们怎么写,基本都是围绕这3个方法玩。view 没有 onLayout 方法,因为 view 不是容器里面放不了 view ,只有 ViewGroup 才有 onLayout 方法


    自定义 view 的种类

    • 继承现成 view 控件
      比如我们写一个自定义 view 继承 textview ,这样难度小,view 的3个核心方法我么你都不用关心,不过一般这样写都是为了给某个控件添加额外功能,难度小,但是不具有普遍适应性。

    • 直接继承 view
      直接继承 view ,view 的 onMeasure 测量 ,onDraw 绘制都需要我们自己来做,很考验功底的,里面又会涉及到大量的动画操作,是非常难得,学好了能大大提升我们的代码水平

    • 继承现成 ViewGroup 容器
      难度小,一般我们都是做组合类型的 view 时用,多用于封装 app 中的公共基础 UI 组件,虽然难度低,但是具有普遍性

    • 直接继承 ViewGroup 容器
      难度最大,ViewGroup 容器的工作在于给子 view 确定位置,给子view 排版,需要大量的计算操作,还要精确考虑 magin,padding 的问题,很难,一般很少这样做,都是对自己有信心的人才回去尝试,技术不熟练的先不要来了


    view 的多个构造方法

    view 的构造方法有4个,分别面对不同的使用情况,我们在自定义 view 时要知道在哪个构造的方法里做初始化,其实一般我们都是在这4个方法里面都写初始化方法的

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

    注意:即使你在View中使用了Style这个属性也不会调用三个参数的构造函数,所调用的依旧是两个参数的构造函数。


    view 的视图层级

    我们连带着把 Actvity 的 视图层级一起写一下吧,下面这张就是 Actvity 视图层级


    2086682-5fda69bb0c776c22.png

    我们在 xml 布局文件中声明的布局根节点并不是 Activity 的视图根节点,是上图中的 contentView 的位置,contentView 上面的都是 Activity 内部添加的,我们控制不了,但是我们需要了解,一些页面效果我们需要操作 DecorView

    这张图是常见的 view 视图层级


    944365-afb2be431e523baf.png

    ViewGroup 里面还可以再放 ViewGroup,但是 view 里面就不能放任何view 了

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

    上面图中有一层就表示视图有一个层级,视图层级越多就会加重 cpu 计算负荷,这个不是线性的关系,是几何层级的关系。ViewGroup 宽高采用 warp_content 时,会跑2次这个 ViewGroup 所属子 view 的 onMeasur 方法,会大大增加任务量。所以我们在写布局时,层级过多或是 warp_content 应用过多,都会造成页面加载计算大,页面卡顿,这是我们需要优化的一个点。


    Android 坐标系

    • 屏幕的左上角为坐标原点
    • 向右为x轴增大方向
    • 向下为y轴增大方向

    详细看下图:


    944365-ee0cd39fd788e293.png

    和数学坐标系的 Y 轴方向是不同的


    view 的坐标

    view 有3套描述坐标位置的方式:

    • Left,Top,Right,Bottom
    • x,y,translationX、translationY
    • rawX ,rawY
    1. Left,Top,Right,Bottom

    这4个描述的是 view 的左右上下到 view 所在父控件左上角的位置

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

    相关的 API :

    getTop();       //获取子View左上角距父View顶部的距离
    getLeft();      //获取子View左上角距父View左侧的距离
    getBottom();    //获取子View右下角距父View顶部的距离
    getRight();     //获取子View右下角距父View左侧的距离
    

    详细看下图:


    005Xtdi2gw1f1qzqwvkkbj308c0dwgm9.jpg

    需要注意的是 right = left + view 的 width , bottom = top + view 的 height

    2. rawX ,rawY

    描述的是 view 左上角到屏幕左上角的距离

    这个可以用 MotionEvent 中 get 和 getRaw 的区别来学习

    相关 API :

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

    详细看下图:


    005Xtdi2jw1f1r2bdlqhbj308c0dwwew.jpg
    3. x,y,translationX、translationY

    从android3.0开始,View增加了额外几个参数:x,y,translationX、translationY。其中x和y是View左上角的坐标,translationX和translationY是View左上角相对于父容器的偏移量,它们默认值是0。这些参数也是相对于View父容器。具体关系见下图:


    5494434-0fbe681e48aaa4ea.png

    x = left + translationX,y = top + translationY

    x和left不同体现在:left是View的初始坐标,在绘制完毕后就不会再改变;而x是View偏移后的实时坐标,是实际坐标。y和top的区别同理。


    如何获取 view 的宽高

    获取 view 的宽高有2套 API:

    • getWidth() / getHeight():获得View最终的宽 / 高
    • getMeasuredWidth() / getMeasuredHeight():获得 View测量的宽 / 高

    他们的区别:


    944365-6b27b9835d927e04.png

    getMeasuredWidth() 方法可以在 view 的 onLayout 方法里使用,onLayout 在 onMeasure 之后跑,这时候 measuredWidth view 的宽是计算出来的,但是我们要考虑 view 申请的大小超过父控件最大值的问题。

    我们可以考虑在 onSizeChange 方法内记录 view 的大小,这也是一种办法。这2套获取宽高的 API 最终的结果值都一样,区别在于产生数据的时机不同。

    getWidth() / getHeight() 只有在 view 计算完并显示之后才能返回具体的值,其他时候返回的都是 0,所以 getWidth() / getHeight() 一般我们都是做延迟使用,等待 view 计算显示完毕

    获取 view 宽高的时机不同,所依赖的方法也是不同的,具体的我就不写了,大家看这个:

    另外还有一点要清楚:getMeasuredXXX() 有时并不 = getXXX() ,下面这段话足以解释

    getMeasuredXXX() 与 getXXX() 的区别和联系所在。说得直白一点,measuredWidth 与 width 分别对应于视图绘制 的 measure 与 layout 阶段。很重要的一点是,我们要明白,View 的宽高是由 View 本身和 parent 容器共同决定的,要知道有这个 MeasureSpec 类的存在。

    比如,View 通过自身 measure() 方法向 parent 请求 100x100 的宽高,那么这个宽高就是 measuredWidth 和 measuredHeight 值。但是,在 parent 的 onLayout() 阶段,通过 childview.layout() 方法只分配给 childview 50x50 的宽高。那么,这个 50x50 宽高就是 childview 实际绘制并显示到屏幕的宽高,也就是 width 和 height 值。

    如果你对自定义 View 过程很熟练的话,理解这部分内容就比较轻松一些。事实上,开发过程中,getWidth() 和 getHeight() 方法用的更多一些。


    Android 的角度 (angle) 与弧度 (radian)

    android 的角度和弧度有其需要说上一说, android 里的角度和我们平时的习惯是嫌烦的,这点很坑爹

    另外这块涉及到画布的相关操作(旋转)、正余弦函数计算等,即会涉及到角度(angle)与弧度(radian)的相关知识。

    另外记住缩写:

    • deg --> 角度
    • rad --> 弧度

    角度,弧度的详细描述:


    944365-7a81d3e1715eda0b.png

    android 角度方向是顺时针的:


    1785445-fbfc94447e590f0e.png

    Android 中颜色部分

    Android 支持一下几种颜色模式:


    944365-43d2051c332e0f95.png

    ARGB 表示4位颜色通道,RGB 表示3位颜色通道,RGB 相比 ARGB 少了透明的颜色通道,需要注意的是 ARGB8888 4位通道的图片若是转成 RGB565 3位通道的图片格式,是会造成图片色差的,A 透明颜色通道用的越多色差越严重

    4位颜色通道含义:


    944365-f63d3055739f08b2.png
    • java中定义颜色
    //java中使用Color类定义颜色
     int color = Color.GRAY;     //灰色
    
      //Color类是使用ARGB值进行表示
      int color = Color.argb(127, 255, 0, 0);   //半透明红色
      int color = 0xaaff0000;                   //带有透明度的红色
    
    • xml文件中定义颜色
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        //定义了红色(没有alpha(透明)通道)
        <color name="red">#ff0000</color>
        //定义了蓝色(没有alpha(透明)通道)
        <color name="green">#00ff00</color>
    </resources>
    
    • java文件中引用xml中定义的颜色
    //方法1
    int color = getResources().getColor(R.color.mycolor);
    
    //方法2(API 23及以上)
    int color = getColor(R.color.myColor);    
    
    • xml文件(layout或style)中引用或者创建颜色
    <!--在style文件中引用-->
        <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
            <item name="colorPrimary">@color/red</item>
        </style>
    
     <!--在layout文件中引用在/res/values/color.xml中定义的颜色-->
      android:background="@color/red"     
    
     <!--在layout文件中创建并使用颜色-->
      android:background="#ff0000"
    

    相关文章

      网友评论

      • 一盘好书:问一下哈,你这些图片是用什么软件做的呢?

      本文标题:自定义 view - 前置知识点

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