Android自訂view實現電影票線上選座功能_Android

來源:互聯網
上載者:User

先看看電影票線上選座功能實現的效果圖:

介面比較粗糙,主要看原理。

這個介面主要包括以下幾部分

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還原淘寶電影線上選座功能

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.