当前位置: 移动技术网 > 移动技术>移动开发>Android > Android自定义view实现电影票在线选座功能

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

2019年07月24日  | 移动技术网移动技术  | 我要评论

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

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

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

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了。

参考资料:

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

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网