Android自定义view实现电影票在线选座功能

先看看电影票在线选座功能实现的效果图:

界面比较粗糙,主要看原理。

这个界面主要包括以下几部分

1、座位
2、左边的排数
3、左上方的缩略图
4、缩略图中的红色区域
5、手指移动时跟随移动
6、两个手指缩放时跟随缩放

主要技术点

1、矩阵Matrix
2、GestureDetector与ScaleGestureDetector
3、Bitmap的一下基本用法
4、这里只需要重写view的onDraw就可实现全部功能

可以发现这个其实没什么难度,主要就是一些位置的计算。

为了能便于理解首先把要用到的知识点进行一下梳理

1、矩阵Matrix

Matrix由3*3矩阵中9个值来决定,我们对Matrix的所有设置, 就是对这9个值的操作。

{MSCALE_X,MSKEW_X,MTRANS_X,
MSKEW_Y,MSCALE_Y,MTRANS_Y,
MPERSP_0,MPERSP_1,MPERSP_2}

这是矩阵的9个值,看名字也知道他们是什么意思了。

这里主要用到缩放和平移,下面以缩放为例来了解一下缩放的控制
通过Android提供的api我们可以调用setScale、preScale、postScale来改变MSCALE_X和MSCALE_Y的值达到缩放的效果

所以只要理解setScale、preScale、postScale这三个方法的区别我们就可以简单的进行缩放控制了

1、setScale(sx,sy),首先会将该Matrix设置为对角矩阵,即相当于调用reset()方法,然后在设置该Matrix的MSCALE_X和MSCALE_Y直接设置为sx,sy的值
2、preScale(sx,sy),不会重置Matrix,而是直接与Matrix之前的MSCALE_X和MSCALE_Y值结合起来(相乘),M' = M * S(sx, sy)。
3、postScale(sx,sy),不会重置Matrix,而是直接与Matrix之前的MSCALE_X和MSCALE_Y值结合起来(相乘),M' = S(sx, sy) * M。

这么说其实也有些不好理解,举个栗子一看就明白

1、pre….的执行顺序

 Matrix matrix=new Matrix();
 float[] points=new float[]{10.0f,10.0f};
 matrix.preScale(2.0f, 3.0f);
 matrix.preTranslate(8.0f,7.0f);
 matrix.mapPoints(points);
 Log.i("test", points[0]+"");
 Log.i("test", points[1]+"");

结果为点坐标为(36.0,51.0)
可以得出结论,进行变换的顺序是先执行preTranslate(8.0f,7.0f),在执行的preScale(2.0f,3.0f)。即对于一个Matrix的设置中,所有pre….是倒着向后执行的。

2、post…的执行顺序

 Matrix matrix=new Matrix();
 float[] points=new float[]{10.0f,10.0f};
 matrix.postScale(2.0f, 3.0f);
 matrix.postTranslate(8.0f,7.0f);
 matrix.mapPoints(points);
 Log.i("test", points[0]+"");
 Log.i("test", points[1]+"");

结果为点坐标为(28.0,37.0)
可以得出结论,进行变换的顺序是先执行postScale(2.0f,3.0f),在执行的postTranslate(8.0f,7.0f)。即对于一个Matrix的设置中,所有post….是顺着向前执行的。

这里主要知道set…和post…方法就行,因为只用到了这两个。
自我理解其实和scrollTo、scrollBy类似。

2、GestureDetector与ScaleGestureDetector

GestureDetector主要用于识别一些特定手势,只要调用GestureDetector.onTouchEvent()把MotionEvent传递进去就可以了
ScaleGestureDetector用于处理缩放的攻击类用法和GestureDetector类似

3、Bitmap的一下基本用法
参考: 关于bitmap你不知道的一些事

了解一下bitmap的注意事项即可

下面开始正式画这个选座的功能了

1、画座位:

@Override
 protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 /**
 * 如果第一次进入 使座位图居中
 */
 if (mViewH != 0 && mViewW != 0&&isFrist) {
 isFrist = false;
 matrix.setTranslate(-(mViewW-getMeasuredWidth())/2, 0);
 }
 /**
 * 画座位
 */
 drawSeat(canvas);
 /**
 * 画排数
 */
 drawText(canvas);
 /**
 * 画缩略图
 */
 drawOverView(canvas);
 /**
 * 画缩略图选择区域
 */
 drawOvewRect(canvas);

 }
private void drawSeat(Canvas canvas) {
 float zoom = getMatrixScaleX();
 scale1 = zoom;
 tranlateX = getTranslateX();
 tranlateY = getTranslateY();
 /**
 * 使用两层for循环来画出所有座位
 */
 for (int i = 0; i < row; i++) {
 float top = i * SeatHight * scale * scale1 + i * mSpaceY * scale1
 + tranlateY;
 for (int j = 0; j < column; j++) {

 float left = j * SeatWidth * scale * scale1 + j * mSpaceX
 * scale1 + tranlateX;

 tempMatrix.setTranslate(left, top);
 tempMatrix.postScale(scale, scale, left, top);
 tempMatrix.postScale(scale1, scale1, left, top);

 /**
 * 获取每个位置的信息
 */
 int state = getSeatType(i, j);
 /**
 * 根据位置信息画不同的位置图片
 */
 switch (state) {
 case SEAT_TYPE_SOLD:
 canvas.drawBitmap(SeatLock, tempMatrix, null);
 break;
 case SEAT_TYPE_SELECTED:
 canvas.drawBitmap(SeatChecked, tempMatrix, null);
 break;
 case SEAT_TYPE_AVAILABLE:
 canvas.drawBitmap(SeatNormal, tempMatrix, null);
 break;
 case SEAT_TYPE_NOT_AVAILABLE:
 break;

 }
 }
 }

 }

这里其实没什么难度,主要就是使用两层for循环,一层画行,一层画列

另外要注意的就是当前位置的计算 top = (当前位置i)(座位图标大小SeatHight 它本身的缩放比scale*缩放时的缩放比scale1)+(当前位置i* 垂直方向的间距mSpaceY*缩放时的缩放比scale1)+垂直方向移动是的移动距离

2、画排数

private void drawText(Canvas canvas) {
 mTextPaint.setColor(bacColor);
 RectF rectF = new RectF();
 rectF.top = getTranslateY() - mNumberHeight/2;
 rectF.bottom = getTranslateY()+ mViewH* getMatrixScaleX() + mNumberHeight/2;
 rectF.left = 0;
 rectF.right = mTextWidth;

 canvas.drawRoundRect(rectF, mTextWidth/2, mTextWidth/2, mTextPaint);
 mTextPaint.setColor(Color.WHITE);
 for (int i = 0; i < row; i++) {
 float top = (i *SeatHight*scale + i * mSpaceY) * getMatrixScaleX() + getTranslateY();
 float bottom = (i * SeatHight*scale + i * mSpaceY + SeatHight) * getMatrixScaleX() + getTranslateY();
 float baseline = (bottom + top - lineNumberPaintFontMetrics.bottom - lineNumberPaintFontMetrics.top ) / 2-6;
 canvas.drawText(lineNumbers.get(i), mTextWidth / 2, baseline, mTextPaint);
 }
 }

3、画缩略图

private void drawOverView(Canvas canvas) {
 /**
 * 1、先画张背景图片
 */
 mBitMapOverView = Bitmap.createBitmap((int)mOverViewWidth,(int)mOverViewHight,Bitmap.Config.ARGB_8888);
 Canvas OverViewCanvas = new Canvas(mBitMapOverView);
 Paint paint = new Paint();
 paint.setColor(bacColor);
 scaleoverX = mOverViewWidth / mViewW;
 scaleoverY = mOverViewHight / mViewH;
 float tempX = mViewW * scaleoverX;
 float tempY = mViewH * scaleoverY;
 OverViewCanvas.drawRect(0, 0, (float)tempX, (float)tempY, paint);

 Matrix tempoverMatrix = new Matrix();
 /**
 * 2、和画座位图一样在缩略图中画座位
 */
 for (int i = 0; i < row; i++) {
 float top = i * SeatHight * scale * scaleoverY+ i * mSpaceY * scaleoverY;
 for (int j = 0; j < column; j++) {
 float left = j * SeatWidth * scale * scaleoverX + j * mSpaceX * scaleoverX+mTextWidth*scaleoverX;
 tempoverMatrix.setTranslate(left, top);
 tempoverMatrix.postScale(scale*scaleoverX, scale*scaleoverY, left, top);

 int state = getSeatType(i, j);
 switch (state) {
 case SEAT_TYPE_SOLD:
 OverViewCanvas.drawBitmap(SeatLock, tempoverMatrix, null);
 break;
 case SEAT_TYPE_SELECTED:
 OverViewCanvas.drawBitmap(SeatChecked, tempoverMatrix, null);
 break;
 case SEAT_TYPE_AVAILABLE:
 OverViewCanvas.drawBitmap(SeatNormal, tempoverMatrix, null);
 break;
 case SEAT_TYPE_NOT_AVAILABLE:
 break;

 }

 }
 }

 canvas.drawBitmap(mBitMapOverView,0,0,null);

 }

4、缩略图中的红色区域

private void drawOvewRect(Canvas canvas) {

 OverRectPaint = new Paint();
 OverRectPaint.setColor(Color.RED);
 OverRectPaint.setStyle(Paint.Style.STROKE);
 OverRectPaint.setStrokeWidth(overRectLineWidth);
 int tempViewW ;
 int tempViewH;
 if(getMeasuredWidth()<mViewW){
 tempViewW = getMeasuredWidth();
 }else{
 tempViewW = mViewW;
 }
 if(getMeasuredHeight()<mViewH){
 tempViewH = getMeasuredHeight();
 }else{
 tempViewH = mViewH;
 }

 try{
 Rect rect ;
 if(getMatrixScaleX()>= 1.0f){
 rect = new Rect((int)(scaleoverX*Math.abs(getTranslateX())/getMatrixScaleX()),
  (int)(scaleoverY*Math.abs(getTranslateY())/getMatrixScaleX()),
  (int)(scaleoverX*Math.abs(getTranslateX())/getMatrixScaleX()+tempViewW*scaleoverX/getMatrixScaleX()),
  (int)(scaleoverY*Math.abs(getTranslateY())/getMatrixScaleX()+tempViewH*scaleoverY/getMatrixScaleX()));
 }else{
 rect = new Rect((int)(scaleoverX*Math.abs(getTranslateX())),
 (int)(scaleoverY*Math.abs(getTranslateY())),
 (int)(scaleoverX*Math.abs(getTranslateX())+tempViewW*scaleoverX),
 (int)(scaleoverY*Math.abs(getTranslateY())+tempViewH*scaleoverY));
 }
 canvas.drawRect(rect, OverRectPaint);
 }catch(Exception e){
 e.printStackTrace();
 }
 }

5、手指移动时跟随移动

@Override
 public boolean onTouchEvent(MotionEvent event) {

 /**
 * 缩放事件交由ScaleGestureDetector处理
 */
 scaleGestureDetector.onTouchEvent(event);
 /**
 * 移动和点击事件交由GestureDetector处理
 */
 gestureDetector.onTouchEvent(event);

 return true;
 }
/**
 * 移动事件 这里只是简单判断了一下,需要更细致的进行条件判断
 */
 public boolean onScroll(MotionEvent e1, MotionEvent e2,
 float distanceX, float distanceY) {
 float tempMViewW = column * SeatWidth*scale*scale1+(column -1)*mSpaceX*scale1+mTextWidth-getWidth();
 float tempmViewH = row * SeatHight * scale * scale1 + (row -1) * mSpaceY * scale1 - getHeight();

 if((getTranslateX()>mTextWidth+mSpaceX)&& distanceX<0){
 distanceX = 0.0f;
 }
 if((Math.abs(getTranslateX())>tempMViewW)&&(distanceX>0)){
 distanceX = 0.0f;
 }

 if((getTranslateY()>0)&&distanceY<0){
 distanceY=0.0f;
 }
 if((Math.abs(getTranslateY())>tempmViewH)&&(distanceY>0)){
 distanceY = 0.0f;
 }
 matrix.postTranslate(-distanceX, -distanceY);
 invalidate();
 return false;

 }
 /**
 * 单击事件
 */
 public boolean onSingleTapConfirmed(MotionEvent e) {
 int x = (int) e.getX();
 int y = (int) e.getY();

 for (int i = 0; i < row; i++) {
 for (int j = 0; j < column; j++) {
 int tempX = (int) ((j * SeatWidth * scale + j * mSpaceX) * getMatrixScaleX() + getTranslateX());
 int maxTemX = (int) (tempX + SeatWidth * scale * getMatrixScaleX());

 int tempY = (int) ((i * SeatHight * scale + i * mSpaceX) * getMatrixScaleY() + getTranslateY());
 int maxTempY = (int) (tempY + SeatHight * scale * getMatrixScaleY());

 if (x >= tempX && x <= maxTemX && y >= tempY
  && y <= maxTempY) {
 int id = getID(i, j);
 int index = isHave(id);
 if (index >= 0) {
  remove(index);
 } else {
  addChooseSeat(i, j);

 }
 float currentScaleY = getMatrixScaleY();
 if (currentScaleY < 1.7f) {
  scaleX = x;
  scaleY = y;
  /**
  * 选中时进行缩放操作
  */
  zoomAnimate(currentScaleY, 1.9f);
 }
 invalidate();
 break;

 }
 }
 }

 return super.onSingleTapConfirmed(e);
 }
 });

6、两个手指缩放时跟随缩放

public boolean onScale(ScaleGestureDetector detector) {
 float scaleFactor = detector.getScaleFactor();
 //scaleX = detector.getCurrentSpanX();
 //scaleY = detector.getCurrentSpanY();
 //直接判断大于2会导致获取的matrix缩放比例继续执行一次从而导致变成2.000001之类的数从而使
 //判断条件一直为真从而不会执行缩小动作
 //判断相乘大于2 可以是当前获得的缩放比例即使是1.9999之类的数如果继续放大即使乘以1.0001也会比2大从而
 //避免上述问题。

 if (getMatrixScaleY() * scaleFactor > 2) {
 scaleFactor = 2 / getMatrixScaleY();
 }
 if (getMatrixScaleY() * scaleFactor < 0.8) {
 scaleFactor = 0.8f / getMatrixScaleY();
 }
 matrix.postScale(scaleFactor, scaleFactor);

 invalidate();

 return true;
 }

至此这个比较粗糙的选座功能就实现了,有时间会继续优化下细节问题。

下面两个demo都比较给力我就不上传demo了。

参考资料:

Android选座源码解析

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android实战教程第一篇之最简单的计算器

    从今天开始,本专栏持续更新Android简易实战类博客文章.和以往专栏不同,此专栏只有实例.每个实例尽量按照知识点对应相应一章节的内容去写,循序渐进.有些实例可能会与另一个专栏有重复的文章. 开始本专栏的第一个简易案例: 首先设置两个布局文件,一个布局文件进行输入数据,获取加法运算:另一个布局文件进行显示最终结果.Activity1启动Activity2,并传递计算结果值给Activity2. main.xml: <?xml version="1.0" encoding=&quo

  • Android自定义照相机详解

    几乎每个APP都会用的相机功能,下面小编把内容整理分享到我们平台,供大家参考,感兴趣的朋友一起学习吧! 启动相机的两种方式 1.直接启动系统相机 <code class="hljs avrasm"> Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); startActivity(intent);</code> 或者指定返回图片的名称mCurrentPho

  • Android自定义照相机倒计时拍照

    自定义拍照会用到SurfaceView控件显示照片的预览区域,以下是布局文件: 两个TextView是用来显示提示信息和倒计时的秒数的 相关教程:Android开发从相机或相册获取图片裁剪 Android启动相机拍照并返回图片 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools&qu

  • Android自定义照相机Camera出现黑屏的解决方法

    本文实例讲述了Android自定义照相机Camera出现黑屏的解决方法.分享给大家供大家参考,具体如下: 对于一些手机,像HTC,当自定义Camera时,调用Camera.Parameters的 parameters.setPreviewSize(width, height)方法时,如果width和height为奇数情况下,则会出现黑屏现象,解决办法可参考SDK提供的ApiDemos中关于Camera的 例子: List<Size> sizes = parameters.getSupporte

  • Android实战教程第四十篇之Chronometer实现倒计时

    Android提供了实现按照秒计时的API,今天就是用这个API实现简单的倒计时. 来个布局: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:lay

  • Android 实现调用系统照相机拍照和录像的功能

    本文实现android系统照相机的调用来拍照 项目的布局相当简单,只有一个Button: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_heig

  • android 调用系统的照相机和图库实例详解

    android手机有自带的照相机和图库,我们做的项目中有时用到上传图片到服务器,今天做了一个项目用到这个功能,所以把我的代码记录下来和大家分享,第一次写博客希望各位大神多多批评. 首先上一段调用android相册和相机的代码: 复制代码 代码如下: Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//调用android自带的照相机 photoUri = MediaStore.Images.Media.EXTERNAL_CON

  • 详解android使用SAX解析XML文件

    解析XML的方式有很多种,大家比较熟悉的可能就是DOM解析. DOM(文件对象模型)解析:解析器读入整个文档,然后构建一个驻留内存的树结构,然后代码就可以根据DOM接口来操作这个树结构了. 优点:整个文档读入内存,方便操作:支持修改.删除和重现排列等多种功能. 缺点:将整个文档读入内存中,保留了过多的不需要的节点,浪费内存和空间. 使用场合:一旦读入文档,还需要多次对文档进行操作,并且在硬件资源充足的情况下(内存,CPU). 为了解决DOM解析存在的问题,就出现了SAX解析.其特点为: 优点:不

  • Android 简单的照相机程序的实例代码

    复制代码 代码如下: class surface extends SurfaceView implements SurfaceHolder.Callback { SurfaceHolder sfholder; Camera camera; Bitmap bitmap; public surface(Context context) {             super(context);             // TODO Auto-generated constructor stub s

  • Android自定义view实现电影票在线选座功能

    先看看电影票在线选座功能实现的效果图: 界面比较粗糙,主要看原理. 这个界面主要包括以下几部分 1.座位 2.左边的排数 3.左上方的缩略图 4.缩略图中的红色区域 5.手指移动时跟随移动 6.两个手指缩放时跟随缩放 主要技术点 1.矩阵Matrix 2.GestureDetector与ScaleGestureDetector 3.Bitmap的一下基本用法 4.这里只需要重写view的onDraw就可实现全部功能 可以发现这个其实没什么难度,主要就是一些位置的计算. 为了能便于理解首先把要用到

  • Android打造炫酷的电影票在线选座app在线选座功能

    不知道大家有没有用过,淘宝电影客户端(淘票票)买过电影票,纵观各类在线选座app的在线选座功能 淘宝在线选座功能用户体验最好,用起来最顺手,夸张点说已经到了炉火纯青的地步,下面我们看一下效果: 效果分析: 整个控件分成几个部分,座位图区域.座位缩略图区域.行号区域.屏幕区域 1.座位图可以自由的移动缩放,放大缩小移动后会自动回弹到合适的位置,选中座位会自动放大到合适比例. 2.行号部分跟着座位图缩放以及上下移动,屏幕区域跟着座位图左右移动缩放. 3.当手指按下的时候会出现缩略图,缩略图上有个红色

  • Android 自定义View实现多节点进度条功能

    前言 最近项目有一个节点进度条的小需求,完成后,想分享出来希望可以帮到有需要的同学. 真机效果图 自定义View完整代码 开箱即用~,注释已经炒鸡详细了 /** * @description: 节点进度条 * @author: DMingO * @date: 2020/4/15 */ public class PointProcessBar extends View { /** * 未选中时的连线画笔 */ private Paint mLinePaint; /** * 选中时的连线画笔 */

  • Android自定义View仿支付宝输入六位密码功能

    跟选择银行卡界面类似,也是用一个PopupWindow,不过输入密码界面是一个自定义view,当输入六位密码完成后用回调在Activity中获取到输入的密码并以Toast显示密码.效果图如下: 自定义view布局效果图及代码如下: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/

  • Android 自定义view仿支付宝咻一咻功能

    支付宝上有一个咻一咻的功能,就是点击图片后四周有水波纹的这种效果,今天也写一个类似的功能. 效果如下所示: 思路: 就是几个圆的半径不断在变大,这个可以使用动画缩放实现,还有透明动画 还有就是这是好几个圆,然后执行的动画有个延迟效果,其实这些动画是放在一起执行的,熟悉属性动画的知道已经给我们提供了同步执行动画和顺序执行动画的实现api,也会会有人说这几个view就是在onDraw()方法中画几个圆,可能会说我还要继承容器view去onLayout()方法中这些子view添加在某个特定的区域,当然

  • Android 自定义View手写签名并保存图片功能

    GIF压缩有问题,运行很顺滑!!! 1.自定义View--支持设置画笔颜色,画笔宽度,画板颜色,清除画板,检查是否有签名,保存画板图片(复制粘贴可直接使用) /** * Created by YyyyQ on 2020/3/5. * 电子签名 */ public class SignatureView extends View { private Context context; //X轴起点 private float x; //Y轴起点 private float y; //画笔 priva

  • Android自定义View实现选座功能

    我们在安卓开发中安卓自带的控件满足不了我们的需求,因此我们就需要用到自定义View来满足我们的需求,在这里我要讲解的是自定义View实现选座功能,在安卓中一个会使用自定义View的人一定会开发出与众不同以及美观的项目 首先,我展示一下效果 以上主要就是我们需要创建一个我们自己的View继承自Viewgroup控件并实现onMeasure以及onDraw方法 具体的代码是这样的 public class SearView extends ViewGroup { private Context co

  • 基于jquery实现在线选座订座之影院篇

    先给大家展示效果图(支持源码下载哦): 查看演示          源码下载 我们在线购票时(如电影票.车票等)可以自己选座.开发者会在页面上列出座次席位,用户可以一目了然的看到可以选择的座位及支付.本文以电影院购票为例,为您展示如何选座.处理选座数据等. 在这里,我给大家介绍一款基于jQuery的在线选座插件:jQuery Seat Charts,它支持自定义座位类型和价格,支持自定义样式,支持设置不可选的座位,也支持键盘控制座位. HTML 我们假设进入电影<星际穿越>的选座页面,页面布局

  • Android自定义View之圆形进度条式按钮

    介绍 今天上班的时候有个哥们问我怎么去实现一个按钮式的进度条,先来看看他需要实现的效果图. 和普通的圆形进度条类似,只是中间的地方有两个状态表示,未开始,暂停状态.而且他说圆形进度的功能已经实现了.那么我们只需要对中间的两个状态做处理就行了. 先来看看实现的效果图: 上面说了我们只需要处理中间状态的变化就可以了,对于进度的处理直接使用了弘洋文章中实现: http://blog.csdn.net/lmj623565791/article/details/43371299 下面开始具体实现. 具体实

  • Android自定义View实现支付宝支付成功-极速get花式Path炫酷动画

    本文手把手教你图片->SVG->Path的姿势.. 从此酷炫Path动画,如此简单. 效果先随便上几个图,以后你找到的图有多精彩,gif就有多精彩: 随便搜了一个铅笔画的图,丢进去 随手复制的二维码icon 来自大佬wing的铁塔 前文回顾 这里简单回顾一下前文,GIF如下图: PathAnimView接受的唯一数据源是Path(给我一个Path,还你一个动画View) 所以内置了几种将别的资源->Path的方法: 直接传string.(A-Z,0-9 "." &qu

随机推荐