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了。
参考资料:
andriod 打造炫酷的电影票在线选座控件,1比1还原淘宝电影在线选座功能
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读