自定义 view - 前置知识点

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 所有生命周期及回调函数

  • onFinishInflate()
    当应用从XML加载该组件并用它构建界面之后调用的方法
  • onMeasure()
    检测View组件及其子组件的大小
  • onLayout()
    当该组件需要分配其子组件的位置、大小时
  • onSizeChange()
    当该组件的大小被改变时
  • onDraw()
    当组件将要绘制它的内容时
  • onKeyDown
    当按下某个键盘时
  • onKeyUp
    当松开某个键盘时
  • onTouchEvent
    当发生触屏事件时
  • onWindowFocusChanged(boolean)
    当该组件得到、失去焦点时
  • onAtrrachedToWindow()
    当把该组件放入到某个窗口时
  • onDetachedFromWindow()
    当把该组件从某个窗口上分离时触发的方法
  • onWindowVisibilityChanged(int)
    当包含该组件的窗口的可见性发生改变时触发的方法

自定义 view 需要注意的生命周期及回调函数

注意这里,面试会问

  1. void onFinishInflate()
    当系统解析XML中声明的View后回调此方法,调用顺序:内层View->外层View,如果是viewgroup,适合在这里获取子View。
    注意点:
    如果View没有在XML中声明而是直接在代码中构造的,则不会回调此方法
    此时无法获取到View的宽高和位置

  2. void onAttachedToWindow()
    当view 被添加到window中回调,调用顺序:外层View->内层View。在XML中声明或在代码中构造,并调用addview(this view)方法都会回调该方法。
    注意点:
    此时View仅仅被添加到View,而没有开始绘制所以同样获取不到宽高和位置

  3. void onDetachedFromWindow()
    看名字就知道是与void onAttachedToWindow();对应的方法,在VIew从Window中移除时回调,如执行removeView()方法。
    注意点:
    如果一个View从window中被移除了,那么其内层View(如果有)也会被一起移除,都会回调该方法,且会先回调内层View的onDetachedFromWindow()方法

  4. void onWindowFocusChanged(boolean hasWindowFocus)
    当View所在的Window获得或失去焦点时被回调此方法。除了常见的设置view的onGlobalLayoutListener,也可以通过这个方法取到VIew的宽高和位置;也适合在判断当失去焦点时停止一些工作,如图片轮播,动画执行等,当获取到焦点后继续执行。
    hasWindowFocus:View所在Window是否获取到焦点,当该Window获得焦点时,hasWindowFocus等于true,否则等于false。

  5. 该方法在当前View或其祖先的可见性改变时被调用

更多详细请看:


自定义 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这个属性也不会调用三个参数的构造函数,所调用的依旧是两个参数的构造函数。

1.public View(Context context)
2.public View(Context context, @Nullable AttributeSet attrs)
3.public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
4.public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)

构造方法严格来说不算回调,但除了方法一外都不需要我们手动调用,而且是自定义View仅有的必须要声明的方法。

  • 构造方法1
    当不在布局文件中声明而在代码中创建View时调用的方法
  • 构造方法2
    当在布局文件中声明,且没有在styles.xml中预设主题级或item级的默认属性时调用。attrs就是一组布局文件中的值(包括默认属性和自定义属性)
  • 构造方法3
    当在布局文件中声明,在attrs.xml中有声明一个属性,并在styles.xml中的主题item声明这个属性的值(即View的一组默认属性)调用
  • 构造方法4
    当在布局文件中声明,在styles.xml中的主题里没有声明但单独声明了View的一组默认属性时调用。

属性赋值优先级:Xml定义 (方法二)> Xml的style定义(方法二) > defStyleAttr (方法三)> defStyleRes> theme直接定义(方法四)

构造方法四要求api21以上,所以我们一般采用构造方法二(没有默认属性)或构造方法三(有默认属性)


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"

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容