欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-03-02 20:15:22
先看看电影票在线选座功能实现的效果图: 界面比较粗糙,主要看原理。 这个界面主要包括以下几部分 1、座位 2、左边的排数 3、左上方的缩略图...

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

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的注意事项即可

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

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还原淘宝电影在线选座功能

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