自定义View实战:影院在线选座

来源:互联网 发布:看盘软件排行 编辑:程序博客网 时间:2024/04/30 01:04


不知道周末有没有小伙伴使用在线选座app看电影?今天来自 起风的清晨 的投稿,将高度还原淘票票APP,大家可以自己评判一下还原度噢~~


起风的清晨 的博客地址:

http://blog.csdn.net/qifengdeqingchen


写在前面


不知道大家有没有跟我一样的感觉,看了那么多的介绍自定义控件原理、事件分发机制的书籍,文章,教程,依然还是不能随心所欲的自定义控件。甚至是看了再忘,忘了再看,很尴尬有木有。有的时候真正遇到了事件冲突一脸懵逼有木有。其实导致这些问题原因很简单,一句话就可以说明问题了“纸上得来终觉浅,绝知此事要躬行”


正如怎样练习一万小时》文章里所说的,从不会到会,秘诀是重复。我们需要一遍一遍仔细地阅读理解,并用代码实践来验证,学到的这些概念流程知识,这样才会在脑海里留下比较深刻的印象,才能自如的应用学到的知识。


《怎样练习一万小时》

http://www.geekonomics10000.com/519/comment-page-4


学习view的绘制原理,事件分发机制的目的是为了自定义控件,所以学了这些知识后,就需要通过实战多自定义几个控件,来不停的应用,消化这些知识。当你真正自己写了几个自定义控件后,你会发现view的绘制原理,事件分发机制这些东西都是死的,真正麻烦的是绘制逻辑,绘图逻辑,计算逻辑以及一些相关的数学知识


下面开始正题,不知道大家有没有用过,淘宝电影客户端(淘票票)买过电影票,纵观各类在线选座app的 在线选座功能,淘宝在线选座功能用户体验最好,用起来最顺手,夸张点说已经到了炉火纯青的地步,下面我们看一下效果:




效果分析


整个控件分成几个部分,座位图区域、座位缩略图区域、行号区域、屏幕区域 


  1. 座位图可以自由的移动缩放,放大缩小移动后会自动回弹到合适的位置,选中座位会自动放大到合适比例。


  2. 行号部分跟着座位图缩放以及上下移动,屏幕区域跟着座位图左右移动缩放。


  3. 当手指按下的时候会出现缩略图,缩略图上有个红色的方框表示,当前能看到的区域,并且跟随缩略图的移动。


涉及的知识点


view的绘制原理、事件分发机制这些就不说了,这些是基础,这里并不打算介绍,网上有非常多的这方面的资料。 


  • 矩阵 Matrix 使用,通过 Matrix 来进行移动、缩放 。


  • 弹性移动、弹性缩放。


  • 手势监听的使用通过 GestureDetectorScaleGestureDetector 来获得缩放比例幅度。


编码实现


通过以下几个核心部分来介绍,其他部分都是类似的思路实现 :


  1. 绘制座位图 。


  2. 座位图的缩放和移动 。


  3. 座位图自动回弹、自动缩放 。


  4. 缩略图部分的绘制实现 。


至于其他部分比如影院荧幕,左侧的行号部分思路跟座位图的实现思路是一致的。


绘制座位图


座位图实际上就是个 二维矩阵,有 行数 列数,我们只需要根据行数和列数加上一定的间距绘制即可。




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以内,就不会卡了。


我们的实现效果




点击最后 阅读原文 查看源码。






如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。


欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:


阅读全文
0 0
原创粉丝点击