Android打造炫酷的電影票在線選座app在線選座功能
不知道大家有沒有用過,淘寶電影客戶端(淘票票)買過電影票,縱觀各類在線選座app的在線選座功能 淘寶在線選座功能用戶體驗(yàn)最好,用起來最順手,夸張點(diǎn)說已經(jīng)到了爐火純青的地步,下面我們看一下效果:
效果分析:
整個(gè)控件分成幾個(gè)部分,座位圖區(qū)域、座位縮略圖區(qū)域、行號區(qū)域、屏幕區(qū)域
1、座位圖可以自由的移動(dòng)縮放,放大縮小移動(dòng)后會(huì)自動(dòng)回彈到合適的位置,選中座位會(huì)自動(dòng)放大到合適比例。
2、行號部分跟著座位圖縮放以及上下移動(dòng),屏幕區(qū)域跟著座位圖左右移動(dòng)縮放。
3、當(dāng)手指按下的時(shí)候會(huì)出現(xiàn)縮略圖,縮略圖上有個(gè)紅色的方框表示,當(dāng)前能看到的區(qū)域,并且跟隨縮略圖的移動(dòng)。
涉及到的知識點(diǎn):
view的繪制原理、事件分發(fā)機(jī)制這些就不說了,這些是基礎(chǔ),這里并不打算介紹,網(wǎng)上有非常多的這方面的資料。
1、矩陣Matrix使用,通過Matrix來進(jìn)行移動(dòng)、縮放
2、彈性移動(dòng)、彈性縮放。
3、手勢監(jiān)聽的使用通過GestureDetector、ScaleGestureDetector來獲得縮放比例幅度。
編碼實(shí)現(xiàn)
通過以下幾個(gè)核心部分來介紹,其他部分都是類似的思路實(shí)現(xiàn)
1、繪制座位圖
2、座位圖的縮放和移動(dòng)
3、座位圖自動(dòng)回彈、自動(dòng)縮放
4、縮略圖部分的繪制實(shí)現(xiàn)
至于其他部分比如影院熒幕,左側(cè)的行號部分思路跟座位圖的實(shí)現(xiàn)思路是一致的。
1、繪制座位圖
座位圖實(shí)際上就是個(gè)二維矩陣,有行數(shù)和列數(shù),我們只需要根據(jù)行數(shù)和列數(shù)加上一定的間距繪制即可。
void drawSeat() { for (int i = 0; i < row; i++) { for (int j = 0; j < column; j++) { int left = j * seatBitmap.getWidth() + j * spacing; int top = i * seatBitmap.getHeight() + i * verSpacing; int seatType = getSeatType(i, j); switch (seatType) { case SEAT_TYPE_AVAILABLE: seatCanvas.drawBitmap(seatBitmap, left, top, paint); break; case SEAT_TYPE_NOT_AVAILABLE: break; case SEAT_TYPE_SELECTED: seatCanvas.drawBitmap(checkedSeatBitmap, left, top, paint); break; case SEAT_TYPE_SOLD: seatCanvas.drawBitmap(seatSoldBitmap, left, top, paint); break; } } } isNeedDrawSeatBitmap = false; }
getSeatType()方法是用來判斷當(dāng)前的座位是否可用,是否已經(jīng)賣出去,是否已經(jīng)選中,根據(jù)這些狀態(tài)繪制不同的座位圖。
2、座位圖的縮放和移動(dòng)
移動(dòng)縮放功能使用 Matirx來實(shí)現(xiàn),Matrix在android中可以用來對圖片進(jìn)行縮放、移動(dòng)、旋轉(zhuǎn)等變換。
matrix本身是一個(gè)3*3的矩陣,矩陣中的每一個(gè)值都代表一個(gè)變換屬性,如下
MSCALE_X MSKEW_X MTRANS_X MSKEW_Y MSCALE_Y MTRANS_Y MPERSP_0 MPERSP_1 MPERSP_2
實(shí)際上就是一個(gè)有9個(gè)元素的數(shù)組
float[] value=new float[9];
通過 matrix.getValues(value);可以獲得具體的值。
value[0]表示的是縮放的x值
value[1]表示的是斜切的x值
value[2] 表示x軸上平移的值
value[3]表示的是斜切的y值
value[4]表示的y軸上的縮放比例
value[5]表示的是y軸上的平移的值
Matrix類有一些方法可以對這些值進(jìn)行改變
setScale(float sx, float sy, float px, float py) :設(shè)置x軸和y周上的縮放比例,px,py表示縮放的中心點(diǎn)。
setTranslate(float dx, float dy) :設(shè)置x軸和y周上的偏移量。
與之對應(yīng)的還有這么兩個(gè)方法:
postScale(float sx, float sy, float px, float py) postTranslate(float dx, float dy)
那么post跟set有什么區(qū)別呢,簡單理解就是set直接,把之前的值給覆蓋了,而post是在之前的值的基礎(chǔ)上進(jìn)行變換。比如現(xiàn)在你已經(jīng)向左移動(dòng)了10個(gè)像素,這時(shí)候你用setTranslate(5,5)這個(gè)時(shí)候直接變成了移動(dòng)5個(gè)像素了,而用post就是在10的基礎(chǔ)上在移動(dòng)5個(gè)像素就變成15了。
以上是Matrix的使用方法,Canvas對象有個(gè)drawBitmap方法可以接收一個(gè)matrix,這樣就可以在繪圖的時(shí)候使用matrix進(jìn)行變換了。
座位圖平移縮放的實(shí)現(xiàn)思路:
要做兩件事情來實(shí)現(xiàn)座位圖的縮放移動(dòng)
1、獲取平移的值和放大縮小的比例
重寫onTouchEvent方法來計(jì)算獲取移動(dòng)的x值和y值
使用ScaleGestureDetector這個(gè)類來幫我們獲取放大縮小的比例,使用非常簡單,創(chuàng)建一個(gè)ScaleGestureDetector的對象,然后在onTouchEvent方法了調(diào)用一下ScaleGestureDetector.onTouchEvent(event);即可
2、根據(jù)獲取到的值對座位圖進(jìn)行平移和縮放
獲取到了平移的值和縮放的比例后,使用matrix.postScale(x,y)和matrix.postTrans(x,y)進(jìn)行對應(yīng)的變換即可,變換完了調(diào)用view的invalidate方法讓view重新繪制matrix即可生效。
下面只列出了核心代碼,省掉了一些邏輯,完整代碼請到github中查看。
ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.OnScaleGestureListener() { @Override public boolean onScale(ScaleGestureDetector detector) { float scaleFactor = detector.getScaleFactor(); matrix.postScale(scaleFactor, scaleFactor, scaleX, scaleY); invalidate(); return true; } });
onTouchEvent處理邏輯
@Override public boolean onTouchEvent(MotionEvent event) { int y = (int) event.getY(); int x = (int) event.getX(); scaleGestureDetector.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX = x; downY = y; break; case MotionEvent.ACTION_MOVE: int downDX = Math.abs(x - downX); int downDY = Math.abs(y - downY); if (downDX > 10 || downDY > 10) { int dx = x - lastX; int dy = y - lastY; matrix.postTranslate(dx, dy); invalidate(); } break; case MotionEvent.ACTION_UP: .... break; } lastY = y; lastX = x; return true; }
onDraw的時(shí)候
@Override protected void onDraw(Canvas canvas) { ..... canvas.drawBitmap(seat, matrix, paint); ..... }
3、座位圖的自動(dòng)回彈、自動(dòng)縮放效果的實(shí)現(xiàn)
為什么要自動(dòng)回彈呢,因?yàn)槟悴僮鞯臅r(shí)候有可能把座位圖移到屏幕外,縮放的時(shí)候把圖縮放的比較小,或者比較大,這個(gè)時(shí)候程序通過計(jì)算給你自動(dòng)的移動(dòng)到一個(gè)比較合適的位置,比較合適的縮放大小。這樣就有著不錯(cuò)的使用體驗(yàn)。
自動(dòng)回彈實(shí)現(xiàn)的思路:
當(dāng)我們手指屏幕上移動(dòng)然后抬起的時(shí)候會(huì)觸發(fā)MotionEvent.ACTION_UP事件,這個(gè)時(shí)候我們可以通過matrix對象來獲取當(dāng)前的移動(dòng)的位置,如果當(dāng)前移動(dòng)的值不符合我們的規(guī)則,我們就將座位圖按照規(guī)則移動(dòng)到指定位置。
移動(dòng)規(guī)則如下(參考的淘票票客戶端的移動(dòng)邏輯)
座位圖整個(gè)大小不超過控件大小的時(shí)候:
往左邊滑動(dòng),自動(dòng)回彈到行號右邊
往右邊滑動(dòng),自動(dòng)回彈到右邊
往上,下滑動(dòng),自動(dòng)回彈到頂部
座位圖整個(gè)大小超過控件大小的時(shí)候:
往左側(cè)滑動(dòng),回彈到最右邊,往右側(cè)滑回彈到最左邊
往上滑動(dòng),回彈到底部,往下滑動(dòng)回彈到頂部
以上的移動(dòng)規(guī)則的實(shí)現(xiàn)大家可以查看具體源碼中的autoScroll()方法的實(shí)現(xiàn),這里就不貼出來了。
移動(dòng)和縮放涉及到一個(gè)彈性移動(dòng)和縮放的問題,所謂彈性移動(dòng)就是有動(dòng)畫效果的移動(dòng)。因?yàn)槿绻惝?dāng)前在100,100 這個(gè)位置,你需要移動(dòng)到800,100這個(gè)位置,如果你直接移動(dòng)到800這個(gè)位置,而不是通過,先移動(dòng)到110,在移動(dòng)到120。。。一直到800這樣一段一段的移動(dòng)。那么移動(dòng)效果將是非常僵硬的,刷的閃過去的感覺,效果非常不好。
彈性移動(dòng)的實(shí)現(xiàn)思路就是:
比如要從100,100 移動(dòng)到800,100這個(gè)位置,很明顯 x軸要移動(dòng)700個(gè)像素。那么把這700個(gè)像素的移動(dòng)我們分成10次移動(dòng)來實(shí)現(xiàn),每次移動(dòng)700/10=70個(gè)像素,兩次移動(dòng)之間間隔50毫秒,就跟幀動(dòng)畫似的,這樣就會(huì)有一個(gè)彈性的動(dòng)畫效果。
通過handler來實(shí)現(xiàn)代碼如下:
int FRAME_COUNT = 10; int time = 15; int count; Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (count < FRAME_COUNT) { count++; MoveInfo moveInfo = (MoveInfo) msg.obj; float moveXLength = moveInfo.moveXLength; float moveYLength = moveInfo.moveYLength; float xValue = moveXLength / FRAME_COUNT; float yValue = moveYLength / FRAME_COUNT; matrix.postTranslate(xValue, yValue); invalidate(); Message message = Message.obtain(); message.obj = msg.obj; //循環(huán)調(diào)用 handler.sendMessageDelayed(message, time); } else { count = 0; } return true; }
彈性縮放的原理跟彈性移動(dòng)的原理一致
4、縮略圖部分的繪制實(shí)現(xiàn)
縮略圖是座位圖的縮小版,縮略圖的寬高和座位圖的寬高有一定比例,比如五分之一。當(dāng)然這是可以根據(jù)效果來調(diào)整的。之所以必須是座位圖的一定比例,是因?yàn)榭s略圖上有一個(gè)動(dòng)態(tài)的紅色方框表示當(dāng)前可見的座位區(qū)域,這個(gè)紅色方框是需要根據(jù)座位圖的移動(dòng)而移動(dòng)的。比例確定后,就這可以根據(jù)座位圖的移動(dòng)來移動(dòng)紅色方框。舉個(gè)例子來說-如果當(dāng)前座位圖向上移動(dòng)了100個(gè)像素,那么縮略圖中對應(yīng)的紅色方框部分向下移動(dòng) 100/4=250個(gè)像素即可。
繪制概覽圖代碼:
Bitmap drawOverview() { //計(jì)算概覽圖上代表座位的正方形寬度、高度 //規(guī)則 :座位圖上座位的寬度/縮放比例 float rectSize = seatBitmap.getHeight() / overviewScale; //計(jì)算概覽圖的寬度和高度 rectW = column * rectWidth + (column - 1) * overviewSpacing + overviewSpacing * 2; rectH = row * rectSize + (row - 1) * overviewVerSpacing + overviewVerSpacing * 2; Canvas canvas = new Canvas(overviewBitmap); //繪制透明灰色背景 canvas.drawRect(0, 0, rectW, rectH, paint); paint.setColor(Color.WHITE); //循環(huán)繪制 for (int i = 0; i < row; i++) { float top = i * rectSize + i * overviewVerSpacing + overviewVerSpacing; for (int j = 0; j < column; j++) { //獲取座位是什么狀態(tài) 已經(jīng)售出、未選中、已經(jīng)選中 int seatType = getSeatType(i, j); switch (seatType) { case SEAT_TYPE_AVAILABLE: paint.setColor(Color.WHITE); break; case SEAT_TYPE_NOT_AVAILABLE: continue; case SEAT_TYPE_SELECTED: paint.setColor(overview_checked); break; case SEAT_TYPE_SOLD: paint.setColor(overview_sold); break; } float left; left = j * rectWidth + j * overviewSpacing + overviewSpacing; canvas.drawRect(left, top, left + rectWidth, top + rectSize, paint); } } return overviewBitmap; }
繪制概覽圖上的紅色方框
/** * 繪制概覽圖 */ void drawOverviewBorder(Canvas canvas) { //繪制紅色框 int left = (int) -getTranslateX();//獲取當(dāng)前座位圖x軸上的平移位置 if (left < 0) { left = 0; } left /= overviewScale; //overviewScale 表示概覽圖占座位圖的比例 left /= getMatrixScaleX();//getMatrixScaleX() 獲取當(dāng)前座位圖的縮放比例 //判斷當(dāng)前座位圖的寬度是否超出 整個(gè)控件的寬度,如果超出了,那么超出的部分就看不見。表示當(dāng)前能看到的位置的紅色方框就不能把超出的部分框進(jìn)來。 int currentWidth = (int) (getTranslateX() + (column * seatBitmap.getWidth() + spacing * (column - 1)) * getMatrixScaleX()); if (currentWidth > getWidth()) { currentWidth = currentWidth - getWidth(); } else { currentWidth = 0; } int right = (int) (rectW - currentWidth / overviewScale / getMatrixScaleX()); float top = -getTranslateY()+headHeight; if (top < 0) { top = 0; } top /= overviewScale; top /= getMatrixScaleY(); if (top > 0) { top += overviewVerSpacing; } //判斷當(dāng)前座位圖的寬度是否超出 整個(gè)控件的高度,如果超出了,那么超出的部分就看不見。表示當(dāng)前能看到的位置的紅色方框就不能把超出的部分框進(jìn)來。 int currentHeight = (int) (getTranslateY() + (row * seatBitmap.getHeight() + verSpacing * (row - 1)) * getMatrixScaleY()); if (currentHeight > getHeight()) { currentHeight = currentHeight - getHeight(); } else { currentHeight = 0; } int bottom = (int) (rectH - currentHeight / overviewScale / getMatrixScaleY()); canvas.drawRect(left, top, right, bottom, redBorderPaint); }
控件性能優(yōu)化
千辛萬苦終于把控件做出來了,結(jié)果一運(yùn)行卡的不要不要的。特別是行數(shù)列數(shù)一多,卡頓的懵逼了。這個(gè)時(shí)候呢我們要對性能進(jìn)行優(yōu)化,總結(jié)下來主要從以下幾個(gè)方面:
1、避免在onDraw中創(chuàng)建對象,分配內(nèi)存,把paint對象的創(chuàng)建放在初始化函數(shù)里面。這一步其實(shí)非常重要因?yàn)槲覀兪褂胏anvas繪圖的時(shí)候需要paint對象,往往不同的地方需要不同paint,這樣一來,創(chuàng)建的paint對象就比較多了,在加上onDraw方法可能會(huì)執(zhí)行多次。頻繁的創(chuàng)建對象會(huì)造成gc,導(dǎo)致卡頓。當(dāng)然了不止是paint對象,其他的對象創(chuàng)建也要能少則少。
2、避免不必要的繪制邏輯,在需要的時(shí)候才繪制。這個(gè)需要我們根據(jù)控件的繪制邏輯來進(jìn)行調(diào)整,同樣也是非常重要。
3、總的原則就是想盡一切辦法把onDraw方法的執(zhí)行控制在16ms以內(nèi),就不會(huì)卡了。
最后看看我們實(shí)現(xiàn)的效果
源碼地址:
以上所述是小編給大家介紹的Android打造炫酷的電影票在線選座app在線選座功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
Android音視頻之視頻采集(系統(tǒng)API預(yù)覽)
這篇文章主要為大家詳細(xì)介紹了Android音視頻之視頻采集,系統(tǒng)API預(yù)覽,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12Flutter banner_view 輪播圖的使用及實(shí)現(xiàn)代碼
這篇文章主要介紹了Flutter banner_view 輪播圖的使用及實(shí)現(xiàn)代碼,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-07-07Kotlin 創(chuàng)建接口或者抽象類的匿名對象實(shí)例
這篇文章主要介紹了Kotlin 創(chuàng)建接口或者抽象類的匿名對象實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03Android ApplicationContext接口深入分析
ApplicationContext是Spring應(yīng)用程序中的中央接口,由于繼承了多個(gè)組件,使得ApplicationContext擁有了許多Spring的核心功能,如獲取bean組件,注冊監(jiān)聽事件,加載資源文件等2022-11-11Android實(shí)現(xiàn)將已發(fā)送的短信寫入短信數(shù)據(jù)庫的方法
這篇文章主要介紹了Android實(shí)現(xiàn)將已發(fā)送的短信寫入短信數(shù)據(jù)庫的方法,是Android手機(jī)開發(fā)常見的技巧,需要的朋友可以參考下2014-09-09Android自定義View實(shí)現(xiàn)水波紋擴(kuò)散效果
這篇文章主要為大家詳細(xì)介紹了Android如何通過自定義View實(shí)現(xiàn)水波紋擴(kuò)散效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-08-08