自定义View从入门到懵逼系列(下)
传送门
自定义View从入门到懵逼系列(上)
自定义View从入门到懵逼系列(下)
这篇是笔记中的下篇,包含的主要内容有:Paint类、绘制顺序、属性动画、自定义View的分类和流程概述、事件的分发、滑动冲突。
1. Paint类(画笔)
我们在上篇中着重讲了绘制类Canvas的一些操作属性,我们知道单单有画布是远远不够的,还有重要的就是我们需要一个画笔用来在画布上绘制东西,所以Paint类就是Android的画笔。
除了最普通的操作对画笔上色调用setColor、setAGB这些操作,画笔对颜色的处理还有其他两个重要操作,Shader着色器和ColorFilter。
1.1 颜色-Shader着色器
5种:LinearGradient(线性渐变)、RadialGradient(辐射渐变)、SweepGradient(扫描渐变)、BitmapShader、ComposeShader(混合着色器)。
1.2 颜色-ColorFilter过滤器
LightingColorFilter,对各个色值进行调节;
ColorMatrixColorFilter,设置饱和度等。
1.3 Xfermode
其实就是要你以绘制的内容作为源图像,以 View 中已有的内容作为目标图像,选取一个 PorterDuff.Mode 作为绘制内容的颜色处理方案,离屏缓存。
1.4 效果
抗锯齿、填充风格、宽度、线头形状、拐角形状等属性。
1.5 色彩优化
抖动、双线性过滤
1.6 轮廓-PathEffect
CornerPathEffect,所有拐角变成圆角;
DiscretePathEffect,线条进行随机的偏离;
DashPathEffect,虚线来绘制线条;
PathDashPathEffect,使用一个 Path 来绘制「虚线」;
SumPathEffect,组合分别绘制;
ComposePathEffect,合并绘制;
1.7 阴影
setShadowLayer、setMaskFilter
2. 绘制顺序
3. 属性动画Property Animation
3.1 简介
动画包含Animation、Transition两种;
Animation包含视图动画ViewAnimation和属性动画PropertyAnimation;
Transition是过渡,一般不用;
我们99%的场合用的都是PropertyAnimation属性动画,属性动画分为两种,ViewPropertyAnimator和ObjectPropertyAnimator。
3.2 ViewPropertyAnimator
用法直接拿View.animate().xxx使用即可,但是仅限于以下的这些方法,不可自定义。
3.3 ObjectPropertyAnimator
ObjectPropertyAnimator比ViewPropertyAnimator高级,它不仅可以使用提供的api,还能根据自己的需求自定义View的属性,一般分为三步。
1.如果是自定义控件,需要添加 setter / getter 方法;
2.用 ObjectAnimator.ofXXX() 创建 ObjectAnimator 对象;
3.用 start() 方法执行动画。
比如我们画一个圆环,百分比进度用float progress来表示,我们就可以用ObjectPropertyAnimator来展示动画。
3.4 插值器Interpolator
有多种插值器。
3.5 多动画执行
PropertyValuesHolder同时执行,AnimatorSet多动画根据设定顺序执行。
3.6 TypeEvaluator
自定义动画的属性,针对特殊属性来做属性动画,比如我们可以将Point来作为属性。
4. 自定义View的分类和流程概述
自定义View应该分为两类:
- 自定义ViewGroup,一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout,包含有子View。
- 自定义View,在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View,不包含子View。
4.1 测量View大小-onMeasure()
View的大小不仅由自身所决定,同时也会受到父控件的影响,为了我们的控件能更好的适应各种情况,一般会自己进行测量。
MeasureSpec中的Mode有三种:
- UNSPECIFIED,子View大小没有任何限制(0, +∞);
- EXACTLY,父控件明确地指定了子View的大小;
- AT_MOST,没有具体尺寸,但是最大为父控件大小(0, parentSize)。
4.2 确定View大小-onSizeChanged()
View的大小不仅由View本身控制,而且受父控件的影响,所以我们在确定View大小的时候最好使用系统提供的onSizeChanged回调函数。
4.3 确定子View布局的位置-onLayout()
确定布局的函数是onLayout,它用于确定子View的位置,在自定义ViewGroup中会用到,他调用的是子View的layout函数。
在自定义ViewGroup中,onLayout一般是循环取出子View,然后经过计算得出各个子View位置的坐标值,然后用以下函数设置子View位置。
child.layout(l, t, r, b);
4.4 绘制内容-onDraw()
onDraw是实际绘制的部分,也就是我们真正关心的部分,使用的是Canvas绘图。
5. 事件的分发
- 事件分发原理: 责任链模式,事件层层传递,直到被消费;
- View 的 dispatchTouchEvent 主要用于调度自身的监听器和 onTouchEvent;
- View的事件的调度顺序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener ;
- 不论 View 自身是否注册点击事件,只要 View 是可点击的就会消费事件;
- 事件是否被消费由返回值决定,true 表示消费,false 表示不消费,与是否使用了事件无关;
- ViewGroup 中可能有多个 ChildView 时,将事件分配给包含点击位置的 ChildView;
- ViewGroup 和 ChildView 同时注册了事件监听器(onClick等),由 ChildView 消费;
- 一次触摸流程中产生事件应被同一 View 消费,全部接收或者全部拒绝;
- 只要接受 ACTION_DOWN 就意味着接受所有的事件,拒绝 ACTION_DOWN 则不会收到后续内容;
- 如果当前正在处理的事件被上层 View 拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来。