自定义View实战:影院在线选座
不知道周末有没有小伙伴使用在线选座app看电影?今天来自 起风的清晨 的投稿,将高度还原淘票票APP,大家可以自己评判一下还原度噢~~
起风的清晨 的博客地址:
http://blog.****.net/qifengdeqingchen
不知道大家有没有跟我一样的感觉,看了那么多的介绍自定义控件原理、事件分发机制的书籍,文章,教程,依然还是不能随心所欲的自定义控件。甚至是看了再忘,忘了再看,很尴尬有木有。有的时候真正遇到了事件冲突一脸懵逼有木有。其实导致这些问题原因很简单,一句话就可以说明问题了“纸上得来终觉浅,绝知此事要躬行”。
正如《怎样练习一万小时》文章里所说的,从不会到会,秘诀是重复。我们需要一遍一遍仔细地阅读理解,并用代码实践来验证,学到的这些概念流程知识,这样才会在脑海里留下比较深刻的印象,才能自如的应用学到的知识。
《怎样练习一万小时》
http://www.geekonomics10000.com/519/comment-page-4
学习view的绘制原理,事件分发机制的目的是为了自定义控件,所以学了这些知识后,就需要通过实战多自定义几个控件,来不停的应用,消化这些知识。当你真正自己写了几个自定义控件后,你会发现view的绘制原理,事件分发机制这些东西都是死的,真正麻烦的是绘制逻辑,绘图逻辑,计算逻辑以及一些相关的数学知识。
下面开始正题,不知道大家有没有用过,淘宝电影客户端(淘票票)买过电影票,纵观各类在线选座app的 在线选座功能,淘宝在线选座功能用户体验最好,用起来最顺手,夸张点说已经到了炉火纯青的地步,下面我们看一下效果:
整个控件分成几个部分,座位图区域、座位缩略图区域、行号区域、屏幕区域 :
-
座位图可以自由的移动缩放,放大缩小移动后会自动回弹到合适的位置,选中座位会自动放大到合适比例。
-
行号部分跟着座位图缩放以及上下移动,屏幕区域跟着座位图左右移动缩放。
当手指按下的时候会出现缩略图,缩略图上有个红色的方框表示,当前能看到的区域,并且跟随缩略图的移动。
view的绘制原理、事件分发机制这些就不说了,这些是基础,这里并不打算介绍,网上有非常多的这方面的资料。
-
矩阵 Matrix 使用,通过 Matrix 来进行移动、缩放 。
-
弹性移动、弹性缩放。
手势监听的使用通过 GestureDetector、ScaleGestureDetector 来获得缩放比例幅度。
通过以下几个核心部分来介绍,其他部分都是类似的思路实现 :
-
绘制座位图 。
-
座位图的缩放和移动 。
-
座位图自动回弹、自动缩放 。
缩略图部分的绘制实现 。
至于其他部分比如影院荧幕,左侧的行号部分思路跟座位图的实现思路是一致的。
绘制座位图
座位图实际上就是个 二维矩阵,有 行数 和 列数,我们只需要根据行数和列数加上一定的间距绘制即可。
getSeatType() 方法是用来判断当前的座位是否可用,是否已经卖出去,是否已经选中,根据这些状态绘制不同的座位图。
座位图的缩放和移动
移动缩放功能使用 Matirx 来实现,Matrix 在Android中可以用来对图片进行缩放、移动、旋转等变换。
matrix 本身是 一个3*3的矩阵,矩阵中的每一个值都代表一个变换属性,如下:
MSCALE_X MSKEW_X MTRANS_X
MSKEW_Y MSCALE_Y MTRANS_Y
MPERSP_0 MPERSP_1 MPERSP_2
实际上就是一个有9个元素的数组:
float[] value=new float[9]。
通过 matrix.getValues(value),可以获得具体的值:
value[0] 表示的是缩放的x值
value[1] 表示的是斜切的x值
value[2] 表示x轴上平移的值
value[3] 表示的是斜切的y值
value[4] 表示的y轴上的缩放比例
value[5] 表示的是y轴上的平移的值
Matrix类 有一些方法可以对这些值进行改变 :
setScale(float sx, float sy, float px, float py) :设置x轴和y周上的缩放比例,px,py表示缩放的中心点。
setTranslate(float dx, float dy) :设置x轴和y周上的偏移量。
与之对应的还有这么两个方法:
postScale(float sx, float sy, float px, float py)
postTranslate(float dx, float dy)
那么 post 跟 set 有什么区别呢,简单理解就是 set直接把之前的值给覆盖了,而 post是在之前的值的基础上进行变换。比如现在你已经向左移动了10个像素,这时候你用setTranslate(5,5)这个时候直接变成了移动5个像素了,而用post就是在10的基础上在移动5个像素就变成15了。
以上是 Matrix 的使用方法,Canvas 对象有个 drawBitmap 方法可以接收一个 matrix,这样就可以在绘图的时候使用 matrix 进行变换了。
座位图平移缩放
要做两件事情来实现座位图的缩放移动。
1、获取平移的值和放大缩小的比例
重写 onTouchEvent 方法来计算获取移动的x值和y值。
使用 ScaleGestureDetector 这个类来帮我们获取放大缩小的比例,使用非常简单,创建一个 ScaleGestureDetector 的对象,然后在 onTouchEvent 方法了调用一下 ScaleGestureDetector.onTouchEvent(event) 即可。
2、根据获取到的值对座位图进行平移和缩放
获取到了平移的值和缩放的比例后,使用 matrix.postScale(x,y) 和 matrix.postTrans(x,y) 进行对应的变换即可,变换完了调用 view的invalidate 方法让view 重新绘制matrix 即可生效。
下面只列出了核心代码,省掉了一些逻辑:
onTouchEvent 处理逻辑:
onDraw 的时候:
@Override
protected void onDraw(Canvas canvas) {
.....
canvas.drawBitmap(seat, matrix, paint);
.....
}
3、座位图的自动回弹、自动缩放效果的实现
为什么要自动回弹呢,因为你操作的时候有可能把座位图移到屏幕外,缩放的时候把图缩放的比较小,或者比较大,这个时候程序通过计算给你自动的移动到一个比较合适的位置,比较合适的缩放大小。这样就有着不错的使用体验。
自动回弹实现的思路:
当我们手指屏幕上移动然后抬起的时候会触发 MotionEvent.ACTION_UP 事件,这个时候我们可以通过 matrix 对象来获取当前的移动的位置,如果当前移动的值不符合我们的规则,我们就将座位图按照规则移动到指定位置。
移动规则如下(参考的淘票票客户端的移动逻辑):
座位图整个大小不超过控件大小的时候:
往左边滑动,自动回弹到行号右边
往右边滑动,自动回弹到右边
往上,下滑动,自动回弹到顶部
座位图整个大小超过控件大小的时候:
往左侧滑动,回弹到最右边,往右侧滑回弹到最左边
往上滑动,回弹到底部,往下滑动回弹到顶部
以上的移动规则的实现大家可以查看具体源码中的 autoScroll() 方法的实现,这里就不贴出来了。
移动和缩放涉及到一个 弹性移动和缩放 的问题,所谓 弹性移动就是有动画效果的移动。因为如果你当前在100,100 这个位置,你需要移动到800,100这个位置,如果你直接移动到800这个位置,而不是通过,先移动到110,在移动到120。。。一直到800这样一段一段的移动。那么移动效果将是非常僵硬的,刷的闪过去的感觉,效果非常不好。
弹性移动的实现思路就是:
比如要从100,100 移动到800,100这个位置,很明显 x轴要移动700个像素。那么把这700个像素的移动我们分成10次移动来实现,每次移动700/10=70个像素,两次移动之间间隔50毫秒,就跟帧动画似的,这样就会有一个弹性的动画效果。
通过 Handler 来实现代码如下:
弹性缩放的原理跟弹性移动的原理一致。
4、缩略图部分的绘制实现
缩略图是座位图的缩小版,缩略图的宽高和座位图的宽高有一定比例,比如五分之一。当然这是可以根据效果来调整的。之所以必须是座位图的一定比例,是因为缩略图上有一个动态的红色方框表示当前可见的座位区域,这个红色方框是需要根据座位图的移动而移动的。比例确定后,就这可以根据座位图的移动来移动红色方框。举个例子来说-如果当前座位图向上移动了100个像素,那么缩略图中对应的红色方框部分向下移动 100/4=250个像素即可。
绘制概览图代码:
绘制概览图上的红色方框:
千辛万苦终于把控件做出来了,结果一运行卡的不要不要的。特别是行数列数一多,卡顿的懵逼了。这个时候呢我们要对性能进行优化,总结下来主要从以下几个方面:
1、避免在 onDraw 中创建对象,分配内存,把 paint对象 的创建放在初始化函数里面。这一步其实非常重要因为我们使用 canvas 绘图的时候需要 paint对象,往往不同的地方需要 不同paint,这样一来,创建的 paint对象 就比较多了,在加上 onDraw方法 可能会执行多次。频繁的创建对象会造成gc,导致卡顿。当然了不止是paint对象,其他的对象创建也要能少则少。
2、避免不必要的绘制逻辑,在需要的时候才绘制。这个需要我们根据控件的绘制逻辑来进行调整,同样也是非常重要。
3、总的原则就是想尽一切办法把onDraw方法的执行控制在16ms以内,就不会卡了。
点击最后 阅读原文 查看源码。
如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。
欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号: