前言:自打学安卓第一天起就听过自定义View,记得当时实习的项目里要弄一个随机验证码,当时百度了一份代码,打开发现就一个文件,叫一个xxxCodeView,放进布局跑起来就出现了一个随机的验证码框子!当时连Canvas,Paint都没摸过的我觉得这份代码的作者好牛逼啊,廖廖十来行代码就搞定。想起来真是记忆犹新!还好在后来的工作学习中时不时的从一些公众号上有读到一些自定义View的相关知识,慢慢的积累,然后自己也会照着敲一敲。
不同的是我不喜欢一码不差的对着源码敲,读懂源码后我习惯给自己提出一点点和源码不同的需求,然后结合自己对源码的理解一点一点的去实现这个需求~ 这算是我学习自定义View的一点点小心得,在这里分享给大家先!
言归正传,这里结合本人平时从公众号上看来的几个小demo稍稍改装下做演示,简单的介绍一下常见的几种自定义View以及自定义View的大致方法步骤!水平有限,有些地方装逼了那也是点到即止,切勿拆穿,谢谢。
一、自定义View的大致步骤
1.新建自定义View的文件,继承View或继承一些系统自带组件(TextView,LinearLayout)。
2.复写一些类的构造方法,在里面获取这个自定义View的自定义属性(如果有)。
3.重写view的onMeasure方法(这里基本上就是针对MeasureSpec.EXACTLY或者MeasureSpec.UNSPECIFIED做一些处理,大多数时候代码都是相同的,当然具体需求具体再改,标准代码文末放公众号文章的链接,可以自己去下源码看)
4.重写onDraw(这个最重要了,直接决定你的自定义View呈现出什么东东,什么样子)
5.其他需要重写的方法总结:
a.涉及到触碰交互的话重写onTouchEvent等方法(事件分发机制,,,这是个大课题)
b.需要刷新或者重新绘制View的时候调用postInvalidate()和Invalidate(),前者在子线程中调用~
二、几个简单的例子,几行关键代码
示例一:进度(时钟,计数)View
如图所示,中间显示计数(或者倒计时),白色被黄色覆盖代表进度的递增(这里可以是颜色覆盖,也可以是白色慢慢消失)。可以根据具体需求做修改。
实现思路分析:这是一个比较简单的自定义View,几乎就是在一个Canvas上用画笔画N条线,自定义好线的长宽。值得一提的是将这个作为demo是为了介绍这类用Paint在Canvas上draw东西的自定义View有一个很重要的方法就是Canvas的rotate方法,图中有54条线,不可能draw的时候找到每一条的坐标然后去draw(当然如果你变态到一定要去算其实是可以算:我是sin你是cos~咋俩求tan~咳咳咳!咋们是在说三角函数对吧!)。这里我的做法是通过中心点center和View的半径以及线的长度算出
数字正上方第一条线的起始点(x.y)
PositionX =center; PositionY =center-out_radius;
然后画出第一条线 canvas.drawLine(textPositionX, textPositionY, textPositionX, textPositionY -hightOfLine,out_paint);
//不要骂人,下面有注释
每画好一条线之后旋转一定角度继续画,另外View下方有90度的区域是没有线的,所以旋转的时候注意避开135~225这90度
至于根据业务逻辑动态变化颜色下图有说明,只需要给个公开的方法设置象征进度的progressDegree~然后调用postInvalidate()(我外部是在子线程中改变进度,所以这里用postInvalidate,与大致步骤的第五点b 首尾呼应一下有木有~)
核心代码:
示例二:刮刮乐+卡片 组合View
(其实就是抄了公众号上的两个demo强行一起用做示例撑场面而已~~这么坦诚~老Fe扎Zn不?)
如图,这里展示的就是刮刮卡效果和特殊形状的view(当然也可以图片实现,不过没有直接自定义view来的灵活)
实现思路分析: 首先来说说这个刮刮卡效果
灰色的背景,手指触摸滑动的地方变成透明,从而显示出下方的图片(或者其他View)。 其实说白了,就是在一张纯灰色的bitmap上,新建Paint并且设置成透明颜色,然后画出手指的移动轨迹Path,用这个轨迹path去覆盖掉bitmap上的灰色
即:透明色覆盖掉灰色 这里就涉及到Paint一个很重要的方法setXfermode(newPorterDuffXfermode(PorterDuff.Mode.SRC_IN));
关于setXfermode的参数有个经典的图片帮助理解,这里我用的是PorterDuff.Mode.SRC_IN,也就是取 “bitmap上原色块(纯灰)和新色块(手指划过的地方的透明色)的交集的新色块” 这句话可能比较绕口,但实际你看看下面链接里的效果对比图你就能理解了(看不懂?多看几次)
http://blog.csdn.net/edisonlg/article/details/7084977
经过分析,这个自定义View的主要成员也就有了
一个Bitmap,上面绘制灰色底色,和手指划过的透明色规矩
一个Canvas,用来装载这个Bitmap
一个Paint,手指的画笔,设置覆盖模式(取交集,显示顶层)
一个Path,记录手指滑动的轨迹
每次手指触到屏幕时:
记录下x,y,连入path,并调用invalidate刷新界面(即调用onDraw重绘)
这时候与Bitmap关联的canvas会将透明轨迹画到bitmap上,同时抹去轨迹下面的灰色,然后把绘制好的bitmap通过系统提供的canvas来展示,值得注意的是ondraw里的canvas和我们自定义的canvas有些不同,这个系统提供的canvas仅用来显示最终成像的bitmap,如果直接用这个canvas来画灰色背景再画手指轨迹的话最终得不到想要的效果(原因目前没搞懂,之后发现了再补上)
擦出功能核心的代码就这么多了,最后还有一点就是当擦除的面积到达一定比例后纯灰层就直接移除了,这个粗一看实现起来完全没有思路,,这个擦去的面积咋算呢?(同心理阴影面积一样不好算~),但是公众号demo里给了个解决方法挺不错的,这里直接展示出来
该方法大致的过程就是获取bitmap的像素数据,存入int数组,对每个像素遍历,如果数值为0表示这个像素被擦除了,最后累计为0的像素数目,算出百分比,超过60%就设置isCoplete为ture,这样在ondraw的时候就不会去绘制纯灰和轨迹了,整个view透明,放在下面的图片就显示出来啦 。 当然整个方法可以会比较耗时,手指拖动不停计算,放在子线程中异步计算比较安全~嗯,必须的
(这里我大胆的猜测一下,人脸识别算法里应该会涉及到这个,分析像素,哈哈)
刮刮乐效果到此就分析完了,当然技术有限,大部分东西照搬过来有些原理还是不太清楚,这里留下了两个问题。
1.示例中这个展示效果的bitmap必须用自己new出来Canvas去绑定,最后给ondraw中系统提供的Canvas去展示,why?
2.实际上当我的画笔Paint颜色设置TRANSPARENT色不论SRC_IN还是DST_IN都实现了擦出的效果,why???
但是如果设置成别的颜色就只有SRC_IN才有效果(符合SRC_IN的效果:先画灰层,再画轨迹,取轨迹颜色)。
卡片view效果:
篇幅问题,,这个小view很简单,简单说一下
1.继承LinearLayout(或者其他)
2.在view的top和bottom画上一排的小半圆(小三角,小扇形...想画啥都行,甚至是bitmap)
重点:确定半圆半径,两个半圆间间距(view的宽度已知,这样就算出了半圆的个数circleNum)
核心代码:同样在ondraw中
根据小半圆的个数循环上下边画圆,值得注意的是启始的x是宽度减去circleNum个圆直径再减去circleNum-1个间距后剩下的不足一个直接的长度的一半,,也就是这一段
示例三:百分比原盘
效果描述:1.外部传入各个类别的名字,和该类别的数值 view自动计算百分比然后画出各色扇形~
并且扇形的最外部中心出标出名字和百分比(自定义属性,Canvas的rotate等)
2.整体view可以拖拽转动(触碰事件的处理,向量的计算)
3.惯性转动(属性动画,差值器等)*ps: 这里我的实现不太好,有bug,如果你有更好的实现思路可以留言哈
这个示例算是一个综合点的自定义view,包含的了前两个简单自定义view的大部分内容还有前面未涉及的触碰事件处理,废话不多说直接上思路代码。
1.实现基本的外形需求
数据部分很简单,自定义一个ViewItem类,包含名字name和数值value字段,你可以再加上颜色等其他属性
自定义View内部存一个List
关键代码:
文字部分:
画文字一样是通过循环的方式一 一添加,不同的是drawText需要传入文字的启始位置x和底边y,所以同demo1,如果每个类别都精确的去计算的话会灰常痛苦,这里的思路一样是转动canvas画布,每次写文字都写在圆心上方半径高度处,这里为了便于理解循环写文字时canvas转动的角度的计算,下图给出了第一次写 旅游(10.81%) 这段文字canvas转动的角度a和第二次写 美食(28.83%)时转动的角度B的示意图,,结合着理解下循环中计算角度的方法吧~(我知道图里画的是阿尔法和贝塔~~只怪win10自带输入法不给力啦)
到这里,效果的第一部分核心就已经实现了,onDraw里就三个方法~就得到了一个不能转~没鸟用的百分比原盘view
drawOutCircle(canvas);
drawInnerArc(canvas);
drawTypeText(canvas);
2.实现整体view拖拽转动,让这个圆盘看起来不那么没鸟用~
思考一下,要实现手指放在圆盘上(上下上下左右BABA)拖动的时候圆盘跟着转动
最起码的是不是应该要识别一下手指拖动的方向呢?
圆盘转动只可能是顺时针或者逆时针,但是对应不同的拖动位置和拖动方向组合有很多种,最简单的,都是向下拖动 如果拖动的位置在圆盘左侧就应该逆时针转,在右侧就是顺时针了。 那么如何来处理这个方向问题呢?正如前面写到了,用向量!!!
***以下代码纯属抄袭,如有雷同~~~要么我抄了你,要么我们一起抄了别人的***
这里我想起来以前做图片旋转的时候抄过的一段代码(图片被手指拖着绕着中心点旋转)
这段代码在当然在onTouch事件里面,用到了向量交叉相乘,PointF等知识点,,有点不可描述(都快还给数学老师了),大致的作用就是通过计算边a,b,c算出边a转到边c的角度newDegree,这个角度就是我们要转的角度,角度的正负包含了方向。
这里在手指move的过程中,这段代码会不停的跑,所以其实每次得到的newDegree都很小,有个全局的记录转动度数mDegree,把newDegree累加进去
正如最后两行,拿到了转动角度,累加计算当前角度,通过接口传给外面~然后在外面调用实例的setRotation()方法
my_custom.setRotation(mDegree)来同步的实现view的转动。
3.手指松开后,惯性转动效果
由于篇幅和时间(2017年4月28日00:00:24)的原因(其实主要是目前的实现效果自己都不能接受)暂时不写了,之后有机会再来补充。