很多應(yīng)用的都會(huì)使用到地圖控件,我們目前使用到地圖的場(chǎng)景是規(guī)劃無人機(jī)的飛行路線。最常見的是規(guī)劃一塊要飛行的區(qū)域,然后生成巡航的路線,最后用拍到的照片進(jìn)行建模。有了模型后就可以獲得場(chǎng)地的一些數(shù)字化信息。
我們有些客戶要建模的區(qū)域在郊區(qū),因此地圖的信息準(zhǔn)確對(duì)于我們規(guī)劃飛行區(qū)域的幫助很大。我們調(diào)研后發(fā)現(xiàn)高德和百度的圖源質(zhì)量各有長(zhǎng)短,因此我們決定同時(shí)接入多個(gè)地圖源??蛻艨梢愿鶕?jù)現(xiàn)場(chǎng)的具體情況選擇地圖源。
我們的地圖控件在規(guī)劃路線時(shí)有很多的自定義 UI 需要展示,下面是我們的規(guī)劃正射飛行的一張?jiān)O(shè)計(jì)圖:
如果我們使用地圖 SDK 提供的繪制方法,同樣的自定義 UI 在不同的地圖源上就要重復(fù)的實(shí)現(xiàn)一遍。如果我們支持三個(gè)地圖源,那么就要針對(duì)三個(gè)地圖源提供的方法實(shí)現(xiàn)三套 UI。這顯然不是一個(gè)可以接受的方案。因此我們決定將自定義 UI 繪制抽離出來,這樣最復(fù)雜的自定義 UI 繪制只要維護(hù)一份代碼,地圖源當(dāng)做組件可以替換。
最后在手機(jī)上看起來就像下圖這樣,地圖源在最底層,自定義 UI 在地圖上層。這樣更換地圖源只是最底層的視圖發(fā)生了變化:
數(shù)據(jù)流
下面展示自定義 UI 和地圖源映射的過程:
如果要添加一個(gè)新元素,那么調(diào)用地圖的接口獲取這個(gè)標(biāo)注的地理坐標(biāo)在地圖上的視圖坐標(biāo),接著在自定義視圖上添加標(biāo)注。顯然如果地圖的展示區(qū)域變了,標(biāo)注的坐標(biāo)也需要一起改變。因此需要監(jiān)聽地圖的區(qū)域變動(dòng)事件,一旦改變,需要重新計(jì)算標(biāo)注的平面坐標(biāo),更新標(biāo)注位置。
換成偽代碼是這樣的:
除了自定義的 UI 的展示外,有時(shí)自定義的 UI 還要支持交互。在我眼前的業(yè)務(wù)場(chǎng)景里,需要交互的元素交互時(shí)都是處在視圖的最頂層。如果可交互元素和展示元素放在一個(gè)視圖里,就需要有狀態(tài)區(qū)分哪些元素會(huì)響應(yīng)交互事件。因?yàn)樽畹讓拥牡貓D也要接收交互事件(用戶可能會(huì)調(diào)整地圖)。因此視圖的 hittest 處理起來就會(huì)有些雜亂,因此我將可交互的元素都統(tǒng)一放在一個(gè)圖層里。這個(gè)圖層里所有的元素都響應(yīng)用戶交互事件。其他的交互事件最后都會(huì)傳給底部的地圖。
整體的視圖結(jié)構(gòu)大概就是這樣:
缺陷
這個(gè)方案的優(yōu)勢(shì)是將自定義 UI 的實(shí)現(xiàn)與具體的地圖 SDK 隔離,當(dāng)?shù)貓D源切換時(shí),自定義 UI 可以直接復(fù)用。但是這個(gè)方案也有一個(gè)固有缺陷:當(dāng)?shù)貓D持續(xù)響應(yīng)用戶交互,自定義的 UI 繪制會(huì)略有卡頓。
如果將 UI 通過地圖 SDK 添加到地圖上,自定義 UI 的繪制與地圖的渲染發(fā)生在同一幀里。因此任意一幀里,自定義 UI 和地圖的位置總是對(duì)應(yīng)的,從用戶的角度看就是圖形的變換很順滑。但是我們的選擇的方案,自定義的 UI 變換前有兩個(gè)步驟:收到地圖區(qū)域變化通知,重新計(jì)算圖形在地圖上的平面坐標(biāo)。假設(shè)手機(jī)沒有性能問題,那么地圖原生圖層的繪制頻率是 60Hz。當(dāng)用戶進(jìn)行連續(xù)縮放時(shí),地圖的圖層每秒繪制 60 次。用戶感覺很順滑。地圖區(qū)域變化的外界通知的頻率由于性能考慮會(huì)低于 60Hz,因此即便重新計(jì)算圖形坐標(biāo)、繪制可以在 1/60 秒完成,自定義 UI 的渲染頻率還是會(huì)低于 60Hz。給用戶的體驗(yàn)就是連續(xù)移動(dòng)時(shí),自定義 UI 沒有那么跟手。
至于自定義 UI 的繪制延遲是否可以接受這就看各自的場(chǎng)景了。但是從我的測(cè)試結(jié)果看,雖然性能會(huì)差一些,但是大部分場(chǎng)景不會(huì)影響到用戶的使用。