前言部分

本文基本实现电影选座的效果,参考的是猫眼的效果来进行编写。2019年开年第一个月这篇可能是这个月的最后一篇了,希望今年继续做到坚持每月写博客的习惯,虽然博客的质量还不行,这主要还是因为能力上差的还多,但是不能轻易放弃,毕竟不能人人都是大神,博客能帮到别人或者帮到自己就有它的价值了。2019希望各位通过努力遇见更好的自己。

  • 下面效果图先来镇楼吧

  • 添加了缩略图,上个新图

内容部分

实现步骤

  1. 常规的自定义View部分,这里需要做的是把电影的选座的内容都绘制到画布上,包括但不限于以下:影院屏幕、行坐标、座位矩阵等。(这里我说通过定义view来实现,其实我感觉通过定义ViewGroup来实现应该效果回更好,但是目前还没有尝试。)
  2. 处理手势操作的部分,这部分的重点是GestureDetector和它的派生类。要注意变化后的画布坐标都会变化,还要处理一些滑动缩放的冲突
  3. Matrix的使用,通过矩阵来实现我们需要的效果(平移和缩放,其实还有错切、旋转等);这里数学要求多一些,也是我很不擅长的部分花费了大量的时间。
  4. 这里主要是做一些边界限制,主要的难点是由于变化Matrix导致坐标的变化,在比较的时候需要来回的转换。这里也很头大,并且我写的限制边界不是很流畅,会有比较明显的卡顿现象,这里我觉得可以加动画来修正或者添加一些其他的限制条件。

具体操作流程

主要介绍一下关键点的代码的原理,具体的完整代码已上传到github在文末点击进去看一下就可以了。

  1. 就从onMeasure方法开始吧,主要还是针对测量规则来进行不同的操作,这里不介绍测量部分,主要是绘制部分占绝大部份内容,贴一下代码:

    //宽和高都是AT_MOST,则设置宽度所有子元素的宽度的和;高度设置为第一个元素的高度;setMeasuredDimension(measureWidth(widthMeasureSpec),measureWidth(heightMeasureSpec));/*** 宽度计算** @param widthMeasureSpec* @return*/private int measureWidth(int widthMeasureSpec) {int result = 0;int specMode = MeasureSpec.getMode(widthMeasureSpec);int specSize = MeasureSpec.getSize(widthMeasureSpec);if (specMode == MeasureSpec.EXACTLY) {result = widthMeasureSpec;} else {result = 500;if (specMode == MeasureSpec.AT_MOST) {result = Math.min(result, specSize);}}return result;}
    

    大家的代码多数都这个样子吧,主要就是是否是精确尺寸,不是的话就给你个磨人的大小就可以。

  2. 多数代码在这个绘制onDraw(Canvas canvas)方法中,这里我逐一介绍一下分为三个部分 座位区域、行数区域、电影院屏幕区域,其他也可以绘制,如猫眼在整体的下面绘制了猫眼电影这几个字。

    1. 绘制电影座位的区域,这里主要就是通过外部传入的一个二位数组来绘制一个几行几列的矩形区域,需要注意处理的地方如:没有座位的区域,如果没有座位其实我也是把位置绘制出来了,不过使用的透明的画笔;显示列号的时候要注意去掉没有座位的位置数;点击事件需要整体刷新view所以选择还是取消选择都在这里进行处理了。

      private void drawSeatView(Canvas canvas) {seatRect = new Rect();seatRect.left = seatWidth + seatWidth / 2 + margiHorizontal;seatRect.top = marginTopScreen;//绘制多少排座位for (int i = 0; i < seatList.length; i++) {int startY;if (i == 0) {startY = marginTopScreen;} else {startY = i * seatWidth + marginTopScreen;}int emptyCount = 0;//每排多少座位for (int x = 0; x < seatList[i].length; x++) {int left;//开始绘制矩阵图if (x == 0) {left = seatWidth + seatWidth / 2;} else {left = (x + 1) * seatWidth + seatWidth / 2;}int top = startY - seatWidth / 2 - margiVertical;seatRect.right = left + seatWidth;seatRect.bottom = top + seatWidth;
      //                LogUtil.i(left + "--" + top + "--" + (left + seatWidth) + "--" + (top + seatWidth));int seatState = seatList[i][x];SelectRectBean selectRectBean = new SelectRectBean();Rect rect = new Rect(left + margiHorizontal, top + margiVertical, left + seatWidth, top + seatWidth);selectRectBean.setRect(rect);selectRectBean.setSeatState(seatState);//需要计算从中间开始绘制座位switch (seatState) {case EMPTY_SEAT:emptyCount++;paintSeat.setColor(Color.TRANSPARENT);canvas.drawRect(rect, paintSeat);break;case NORMAL_SEAT:paintSeat.setColor(Color.WHITE);selectRectBean.setColumn(x + 1 - emptyCount);selectRectBean.setRow(i + 1);canvas.drawRect(rect, paintSeat);break;case SELL_SEAT:paintSeat.setColor(Color.RED);canvas.drawRect(rect, paintSeat);break;case SELECT_SEAT:paintSeat.setColor(Color.GREEN);selectRectBean.setColumn(x + 1 - emptyCount);selectRectBean.setRow(i + 1);canvas.drawRect(rect, paintSeat);break;default:paintSeat.setColor(Color.TRANSPARENT);canvas.drawRect(rect, paintSeat);break;}//收集所有的位置信息mRectList.add(selectRectBean);}}//绘制变化的if (selectList.size() > 0) {for (int i = 0; i < selectList.size(); i++) {SelectRectBean selectRectBean = selectList.get(i);Rect rect = selectRectBean.getRect();paintSeat.setColor(Color.GREEN);canvas.drawRect(rect, paintSeat);canvas.drawText(selectRectBean.getRow() + "排" + selectRectBean.getColumn() + "列", rect.left, rect.top + seatWidth / 2, textPaint);}}}
      
    2. 绘制行数的列表比较简单,代码如下:

       private void drawRowIndex(Canvas canvas) {RectF rect = new RectF(transformOldCoordX(0), marginTopScreen - seatWidth / 2, transformOldCoordX(seatWidth), marginTopScreen + seatWidth * row - seatWidth / 2 - margiVertical);screenPaint.setColor(Color.parseColor("#44666666"));canvas.drawRoundRect(rect, 20, 20, screenPaint);for (int i = 0; i < row; i++) {int startY;if (i == 0) {startY = marginTopScreen;} else {startY = i * seatWidth + marginTopScreen;}//绘制一下左边的排数canvas.drawText((i + 1) + "", transformOldCoordX(margiLeft - 5), startY == 0 ? seatWidth : startY, textPaint);}}
      
    3. 绘制中央屏幕区域,这里代码比较简单,主要就Path来绘制了一个梯形,在绘制一个文字,具体代码如下:

       private void drawFilmScreen(Canvas canvas) {//计算出实时的顶部位置,座位的矩阵部分其实是原始的坐标。screenPaint.setColor(Color.parseColor("#ffffff"));float centerX;if (scale == 1.0) {centerX = ((seatRect.right + seatRect.left) / 2);} else {centerX = ((seatRect.right + seatRect.left) / 2);}float newLeft = (centerX - 100);float newRight = (centerX - filmScreenHeight);float newTop = (centerX + filmScreenHeight);float newBottom = (centerX + 100);Path path1 = new Path();path1.moveTo(newLeft, transformOldCoordY(0));path1.lineTo(newRight, transformOldCoordY(filmScreenHeight / 2));path1.lineTo(newTop, transformOldCoordY(filmScreenHeight / 2));path1.lineTo(newBottom, transformOldCoordY(0));path1.close();canvas.drawPath(path1, screenPaint);canvas.drawText("屏幕", (centerX - filmScreenHeight / 4), transformOldCoordY(filmScreenHeight / 4), textPaint);
      //        LogUtil.i("drawFilmScreen = " + centerX + "---" + getMatrixTranslateY() + "***" + getMatrixTranslateX());canvas.drawLine(centerX, transformOldCoordY(0), centerX, marginTopScreen + seatWidth * row - seatWidth / 2 - margiVertical, screenPaint);}
      
  3. 手势部分操作,这里需要注意的地方是需要对手势进行一些筛选和限制操作,比如:滑动整个画布时候如果能够完全显示整个座位区域则不需要滑动;滑动区域的时候也不能无限的滑动,否则会把整个座位区域划出屏幕;下面贴代码:

     //首先你要把事件都转发给gestureDetector,然后gestureDetector内部会给我们区分手势,返回给我们需要的手势,如滑动和缩放@Overridepublic boolean onTouchEvent(MotionEvent event) {//事件分发给手势处理器进行缩放和平移gestureDetector.onTouchEvent(event);scaleGestureDetector.onTouchEvent(event);return true;}
    

    上面转发后我们就可以在监听回调的方法了,代码有点多,主要因为手势监听和过滤处理比较多,代码如下:

     private void initGesture(Context context) {scale = 1.0f;//图片完全显示的伸缩值
    //        mCanvasMatrix.postTranslate(translateX, translateY);
    //        mCanvasMatrix.postScale(scale, scale);//缩放手势scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {@Overridepublic boolean onScale(ScaleGestureDetector detector) {isScaling = true;
    //                LogUtil.i("focusX = " + detector.getFocusX());       // 缩放中心,x坐标
    //                LogUtil.i("focusY = " + detector.getFocusY());       // 缩放中心y坐标
    //                LogUtil.i("scale = " + detector.getScaleFactor());   // 缩放因子float scaleFactor = detector.getScaleFactor();//当前的缩放比例float fx = detector.getFocusX();float fy = detector.getFocusY();
    //                float[] points = mapPoint(fx, fy, mCanvasMatrix);float realScaleFactor = getRealScaleFactor(scaleFactor);mCanvasMatrix.postScale(realScaleFactor, realScaleFactor, 0, 0);invalidate();return true;}@Overridepublic void onScaleEnd(ScaleGestureDetector detector) {super.onScaleEnd(detector);isScaling = false;scale = getMatrixScaleX();reviseTranslate();}});//移动手势gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {//方向是相反的,所以需要加负号。
    //                LogUtil.i("onScroll_change = " + "获取移动的距离" + getMatrixTranslateX() + "---" + getMatrixTranslateY());//通过移动距离的大小来判断x移动或y轴移动if (Math.abs(distanceX) > Math.abs(distanceY)) {float x = seatRect.left / getMatrixScaleX() - getMatrixTranslateX() + seatWidth;float standardX = transformNewCoordX(seatRect.left);float xRight = seatRect.right * getMatrixScaleX() + getMatrixTranslateX();float standardRightX = measuredWidth - seatWidth;
    //                LogUtil.i("onScroll_change = " + "获取移动的距离" + xRight + "---" + standardRightX);if (standardX >= x) {mCanvasMatrix.preTranslate(-5, 0);return true;} else if (xRight < standardRightX) {mCanvasMatrix.preTranslate(5, 0);return true;}mCanvasMatrix.postTranslate(-distanceX, 0);} else if (Math.abs(distanceX) < Math.abs(distanceY)) {float x = seatRect.top / getMatrixScaleY() - getMatrixTranslateY();float standardX = transformNewCoordY(seatRect.top);float xRight = seatRect.bottom * getMatrixScaleY() + getMatrixTranslateY();float standardRightX = measuredHeight - seatWidth;LogUtil.i("onScroll_change = " + "获取移动的距离" + xRight + "---" + standardRightX);if (standardX >= x) {mCanvasMatrix.preTranslate(0, -5);return true;} else if (xRight < standardRightX) {mCanvasMatrix.preTranslate(0, 5);return true;}mCanvasMatrix.postTranslate(0, -distanceY);} else {}//应该限制左边的坐标invalidate();return true;}@Overridepublic boolean onSingleTapConfirmed(MotionEvent event) {//                LogUtil.i(event.getX() + "--onSingleTapConfirmed--" + event.getY());//这里做点击了处理,开始的x、y坐标float currentX = event.getX();float currentY = event.getY();//需要做坐标点转换,转换为原始的点,在进行点击事件LogUtil.i(currentX + "--before--" + currentY);currentPoint.set((int) currentX, (int) currentY);clickSeat(currentPoint);if (childSelectListener != null) {childSelectListener.onChildSelect(selectList);}return super.onSingleTapConfirmed(event);}});}

    GestureDetector中有点击的处理这里我也可以直接放到onTouchEvent中进行,速度会比这里要快一些,具体校验点击到哪个具体的位置判断方法在clickSeat()中,判断的依据主要是通过点击的点的坐标,和实际画布上的坐标做比较可以得出哪个座位被点击了。代码如下:

    private void clickSeat(Point currentPoint) {for (int i = 0; i < mRectList.size(); i++) {Rect rect = mRectList.get(i).getRect();SelectRectBean selectRectBean = mRectList.get(i);if (selectRectBean.getSeatState() == SELL_SEAT) {continue;}float newLeft = rect.left * getMatrixScaleX() + 1 * getMatrixTranslateX();float newRight = rect.right * getMatrixScaleY() + 1 * getMatrixTranslateX();if (currentPoint.x > newLeft && currentPoint.x < newRight) {float newTop = rect.top * getMatrixScaleX() + 1 * getMatrixTranslateY();float newBottom = rect.bottom * getMatrixScaleY() + 1 * getMatrixTranslateY();if (currentPoint.y > newTop && currentPoint.y < newBottom) {//点击到了某一个if (selectList.contains(selectRectBean)) {selectList.remove(selectRectBean);} else {selectList.add(selectRectBean);}//更新界面invalidate();break;}}}}
    

以上就是主要代码的主要内容了,其实还是比较清楚,整个流程下来 绘制各部分——>手势监听——>手势限制,

介绍了大概,如果需要具体看细节的话,建议看看具体的源码吧,本项目为了练习所以业务上不完整,后续会继续更新完整。


新添加了概览图

添加整体的概览图,主要是通过对整个画布进行一个等比例的缩放操作实现。在ondraw方法中添加了两个方法,这里其实是由其他方案,因为概览图的座位并不是每次都需要绘制,这里可以把座位背景单独绘制,通过标识来决定是否重绘,重绘的关键点其实就是座位状态的变化,因为其他情况是不需要重绘缩略的座位图的。

代码如下:

 //是否绘制概览区域if (isDrawOver) {//绘制概览区域drawOverView(canvas);//绘制边框drawOverBorder(canvas);}

方法分为几个步骤:

  1. 挥着整个背景区域+座位信息,这里主要需要注意的是缩放的比例要和画布保持一致,因为后来你要添加的选中可视区域的方框是要同比例的放到概览图上,所以比例一定不能有差异。下面我尝试做一些优化但是还没完成,主要是想减少概览图的绘制。
    看一下代码:
 private void drawOverView(Canvas canvas) {float left = transformOldCoordX(0);float top = transformOldCoordY(0);float right = left + transformCoverDistance(mCanvasRect.right - mCanvasRect.left);float bottom = top + transformCoverDistance(mCanvasRect.bottom - mCanvasRect.top);RectF rect = new RectF(left, top, right, bottom);
//        mCoverCanvasMatrix.reset();
//        mCoverCanvasMatrix.postScale(1 / getMatrixScaleX(), 1 / getMatrixScaleY());
//        mCoverCanvasMatrix.postTranslate(-getMatrixTranslateX()/ getMatrixScaleX() / ratioOver, -getMatrixTranslateY()/ getMatrixScaleX() / ratioOver);//绘制中心线和屏幕drawFilmScreenCover(rect, canvas);//是否绘制图,一般只有点击时间才会重绘if (isDrawOverBitmap) {}mBitmap = drawSeatRectOver(rect, canvas);
//        if (mBitmap != null) {//            canvas.drawBitmap(mBitmap, mCoverCanvasMatrix, overPaint);
//        }}
  1. 绘制可视区域的窗口框,这里其实就是view的尺寸,因为view的大小是固定的,我们通过移动画布来改变可视区域,我们只要包view的尺寸线缩放到概览图的尺寸,然后在根据手势的变化去改变这个方框的尺寸就可以了,这里我花费了比较多的时间,因为开始我只是把座位区域的rect移到概览图导致比例不对显示的框和屏幕不一致。下面看一下代码:
private void drawOverBorder(Canvas canvas) {//绘制移动的框,应该显示屏幕区域内的座位,屏幕点转换到画布上,在转换到缩略图上float left = transformOldCoordX(0) - transformCoverDistance2(getMatrixTranslateX());float top = transformOldCoordY(0) - transformCoverDistance2(getMatrixTranslateY());float right = left + transformCoverDistance2(measuredWidth);float bottom = top + transformCoverDistance2(measuredHeight);
//        float right = transformOldCoordX(measuredWidth) / getMatrixScaleX() / ratioOver - getMatrixTranslateX() / getMatrixScaleX() / ratioOver;
//        float bottom = transformOldCoordY(measuredHeight) / getMatrixScaleY() / ratioOver - getMatrixTranslateY() / getMatrixScaleY() / ratioOver;
//        LogUtil.i(left + "-右-" + right + "-下-");RectF rectBorder = new RectF(left,top,right,bottom);canvas.drawRect(rectBorder, paintBorder);}

完成上面的内容概览图基本就完成了效果还不错吧。
分为两个一个是绘制中间的屏幕和中心线,一个是绘制座位了。代码就不贴出来了,因为主要是做了缩放和位置的校正。

修改了边界限制的方式

  • 以前使用的是到边界值就直接把座位图,放到合适的位置。
  • 新的方法是把边界进行一个回弹的操作

这种方式是参照的这个项目实现的,其实我觉得也不是很好把,我以前的方案主要存在的问题是,在达到边界的时候,强制限制画布位置,会操作很僵硬,不流畅,体验不好,所以一直在寻找一种更好的方式。
代码不写了,直接到项目中了解把。

如果有更好的方案,欢迎留言给我啊,谢谢了。

设置座位初始状态的方法如下,我是通过一个二维数组来实现的,代码如下:

这种方式和实际业务可能不符合,但是可以通过自己业务需求修改,思想是座位的几个状态是通过一个int指来进行区分的。

//外层数组,这里是,默认座位状态。0等于空白位置;1等于未选择座位;2等于已经选择座位seatList = new int[9][];for (int i = 0; i < 9; i++) {int[] indes = new int[13];for (int x = 0; x < 13; x++) {if (i == 4) {if (x < 3 || x > 9) {indes[x] = 0;} else if (x == 6) {indes[x] = 2;} else {indes[x] = 1;}} else {indes[x] = 1;}}seatList[i] = indes;}searchSeat.setSeatList(seatList);

后记

点击去GitHub看源码

未完成部分功能:

    • 左上角的小的概览图
    • 边界修正的优化

有问题欢迎纠正,谢谢啦

如果对你有帮助就点个赞把。

仿猫眼电影在线选座组件相关推荐

  1. 微信小程序仿猫眼电影在线选座实现

    select-seat.js const api = require('../../../utils/api.js') const app = getApp(); let that; Page({/* ...

  2. HTML+CSS+JavaScript实现仿猫眼电影购票选座

    一.功能实现 (1)在页面左侧区域,单击"可选座位",将座位设置为"已选座位",一次最多选5 个座位.右侧座位号汇总区域显示已选中的座位号,并显示电影票总价. ...

  3. java电影票选座_jQuery仿猫眼电影票在线选座购买特效

    特效描述:电影票选座 在线选座 购买特效. 代码结构 1. 引入CSS 2. 引入JS 3. HTML代码 屏幕 电影:天将雄师 时间:03月20日 22:15 座位: 票数:0 总价:¥0 var ...

  4. HTML期末学生作业 HTML+CSS+JavaScript仿猫眼电影在线网站 Hbuilder网页制作

    ❤ HTML期末学生作业~html+css+javascript仿猫眼电影在线网站(功能齐全) web期末结课大作业 html+css+javascript网页.电影.仿京东.天猫.服装. 企业网站制 ...

  5. andriod 打造炫酷的电影票在线选座控件,1比1还原淘宝电影在线选座功能

    本篇文章已经授权微信公共账号 guolin_blog(郭霖)独家发布 不知道大家有没有跟我一样的感觉,看了那么多的介绍自定义控件原理.事件分发机制的书籍,文章,教程,依然还是不能随心所欲的自定义控件. ...

  6. Php电影在线选座实现,电影在线选座怎么实现

    鄙人不才,去年刚做过一个这样的项目,鉴于观看了你和1楼的过招,只能给你大致的思路,然后你按步骤去实践 ,才能取得真经. 步骤依次是这样的,获取电影排期->获取电影座位信息->排列填充你的座 ...

  7. 淘宝电影成全国最大在线选座平台 覆盖702家影院

    近几年,中国电影市场一片红火,热门影片也层出不穷,很多场次的票常常提前售完,影迷们赶到电影院往往要等上1个多小时才能进场,有时候甚至买不到票看不成电影.在如此红火的电影市场下,支持在线选座的订票平台越 ...

  8. 影院在线售票云平台(仿猫眼电影,附SpringBoot项目源码) 系统功能实现

    影院在线售票云平台是模仿猫眼电影开发的在线售票系统,系统分为前端网站及后台管理2部分,主要功能有影院管理,电影管理,影厅管理,排片管理,选座售票,演员管理,评论管理,影片排名,票房收入,票房排名,财务 ...

  9. 视频教程-影院在线售票云平台(仿猫眼电影,附SpringBoot项目源码)-Java

    影院在线售票云平台(仿猫眼电影,附SpringBoot项目源码) 19年软件开发经验,设计开发40多个大型软件,10年从事高等教育,主要为java系列课程,带你轻松进入java生涯. 赖国荣 ¥299 ...

最新文章

  1. 谈 JavaScript 浮点数计算精度问题(如0.1+0.2!==0.3)
  2. Android四种Activity的加载模式(转)
  3. LaTeX 的对参考文献的处理
  4. boost::hana::always用法的测试程序
  5. cmd上写的java简单代码_用cmd编辑一个超级简单的小游戏,求代码
  6. sparksql整合hive
  7. Kafka HWLEO概念入门
  8. 数据分析需要的数据集
  9. js高程读书笔记(1-3章)
  10. IndexedDb、Web Sql、localStorage以及localForage使用
  11. 中文文字校对和文档对比合并开源工具调研
  12. 基于javaweb+jsp的大学生个人财务记账系统(带报告文档)
  13. anaconda各个版本下载资源
  14. JeDate日期控件,未选择日,出现undefined错误
  15. Java集合源码剖析——基于JDK1.8中LinkedList的实现原理
  16. win10java怎么打开_图文传授win10如何打开java控制面板的解决本领
  17. 转:关于ASP操作Access数据库时出现死锁.ldb的解决方法
  18. 基于ssm框架实现网上购物管理系统【附项目源码+论文说明】
  19. 【东游记】美东大环线:华盛顿--费城--纽约--西点--耶鲁--波士顿--美加大瀑布
  20. 推荐系统(一)召回阶段

热门文章

  1. php提交道不同,【后端开发】php引用和拷贝的区别
  2. 设置windows开机隐藏启动,隐藏欢迎界面
  3. PCL实现点云选取并计算选取点法向量及可视化
  4. JTAG、JLink、ULINK、ST-LINK仿真器区别
  5. [9i] 猪年说猪,属相,本命年,十二生肖用英语该怎么说
  6. bttray.exe
  7. 快递100企业版接口(API)实时查询、订阅推送、云打印、电子面单实现.Net版
  8. 为何64位的.NET程序不能申请超过2G的空间
  9. word样式管理:如何对样式进行修改删除
  10. 基于Java毕业设计服务管理系统源码+系统+mysql+lw文档+部署软件