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 视图层级
![](https://img.haomeiwen.com/i1785445/4d168421ca15c51e.png)
我们在 xml 布局文件中声明的布局根节点并不是 Activity 的视图根节点,是上图中的 contentView 的位置,contentView 上面的都是 Activity 内部添加的,我们控制不了,但是我们需要了解,一些页面效果我们需要操作 DecorView
这张图是常见的 view 视图层级
![](https://img.haomeiwen.com/i1785445/c45d05d15f22407d.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轴增大方向
详细看下图:
![](https://img.haomeiwen.com/i1785445/6fa08977b1c67462.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左侧的距离
详细看下图:
![](https://img.haomeiwen.com/i1785445/db5a48be0daf3f6d.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();
详细看下图:
![](https://img.haomeiwen.com/i1785445/393b57519fe38959.jpg)
3. x,y,translationX、translationY
从android3.0开始,View增加了额外几个参数:x,y,translationX、translationY。其中x和y是View左上角的坐标,translationX和translationY是View左上角相对于父容器的偏移量,它们默认值是0。这些参数也是相对于View父容器。具体关系见下图:
![](https://img.haomeiwen.com/i1785445/645f3dc7e34b31c2.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测量的宽 / 高
他们的区别:
![](https://img.haomeiwen.com/i1785445/ed5f20971e56532e.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 --> 弧度
角度,弧度的详细描述:
![](https://img.haomeiwen.com/i1785445/459c532b8dfbf39b.png)
android 角度方向是顺时针的:
![](https://img.haomeiwen.com/i1785445/e7ae80cbc886632f.png)
Android 中颜色部分
Android 支持一下几种颜色模式:
![](https://img.haomeiwen.com/i1785445/0b82df90d89fa771.png)
ARGB 表示4位颜色通道,RGB 表示3位颜色通道,RGB 相比 ARGB 少了透明的颜色通道,需要注意的是 ARGB8888 4位通道的图片若是转成 RGB565 3位通道的图片格式,是会造成图片色差的,A 透明颜色通道用的越多色差越严重
4位颜色通道含义:
![](https://img.haomeiwen.com/i1785445/05d2b05a7d2ecfe8.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"
网友评论