無依賴第三方庫的室內戶型圖互動元件庫
no-dependency Indoor map view library.
專案結構
github:github.com/karonl/InDo…(歡迎stars) 該 master branch 為完整的演示專案,其中 Sample 為應用模組,InDoorView 為庫模組,LICENSE 為版權說明。
快速體驗
- 下載apk: http://leanclub.cn/InDoorViewSample.apk
- 檢視mp4: http://leanclub.cn/Screenrecord20170504.mp4
- 示例圖片:
應用場景
該控制元件可讓室內圖片上的區域擁有點選事件,可用於開發電影院選座、商場購物地圖、展位攤位線上預定、辦公場地租賃工位等需要操作不規則區域功能。 如果這正是你所需要的,可以點選該庫的 Star (thanks for your star✨),便於收藏學習和關注最新動態。
快速開始
- Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
jcenter()
maven { url 'https://jitpack.io' }
}
}
複製程式碼
- 在 app 應用模組的 build.gradle 引入
dependencies {
compile 'com.github.karonl:InDoorSurfaceView:1.0'
}
複製程式碼
- 在 xml 檔案中進行元件宣告
<com.karonl.instance.InDoorView
android:id="@+id/surface"
android:layout_width="match_parent"
android:layout_height="match_parent" />
複製程式碼
- 在對應的 activity 中進行引用,並通過設定介面卡,把底圖和區域 list 填入,最後 refreshData();
InDoorView view = (InDoorView)findViewById(R.id.surface);
....
adapter.refreshData();
複製程式碼
為何開發 InDoorView?
開發InDoorView的主要目標是解決人與圖片中特殊圖案的互動需求。
緣起之前我負責的一個app(類似共享短期工位),主要功能是允許使用者在室內戶型圖上選擇工位,並且點選某個工位後付費即可把工位點亮。這和我們常見的電影院選座有點不一樣,因為大部分割槽域是非規則圖形,裡面擺放的也是不同大小、位置不一的多邊形,不同房間也無法複用,這自然無法通過迴圈繪製固定圖形來實現互動。最後,我們決定採用最常見的標識有區域的 jpg 圖片來做底圖以節約製圖成本,這意味著我們需要和這些標記區域進行互動。
一開始我使用遊戲引擎來做,該需求並不複雜很快得到滿足,但為了個簡單功能引入整個庫非常不明智,並且體積也大了不少,載入速度也受到影響。所以我決定著手開發一個可以精巧的操作圖裡不規則區域的第三方庫。
InDoorView 的原理?
把讀取地圖底圖 bitmap 和使用 Paint 的鋼筆路徑集合一同繪製到一個 canvas 上儲存,並通過繼承 SurfaceView 把 canvas 繪製到雙緩畫布中,通過 canvas.drawBitmap 實現縮放和移動,重寫 view 點選事件結合 Region 判斷點選座標位於哪個區域內,再通過介面反饋事件。
採用把所有圖案內容事先快取到 canvas 的方法,使用非 UI 執行緒進行繪製,可實現每秒 60 次左右的介面繪製,實現流暢的移動和縮放操作;在沒互動情況下,暫停繪製及重新整理以節約計算資源。
特性
- 效能較強,繪製達到 60 幀上下
- 支援縮放以及拖動
- 直接繼承自原生 SurfaceView ,無需匯入龐大的引擎庫
介面說明
Activity 中對 view 的控制程式碼如下:
InDoorView view = (InDoorView)findViewById(R.id.surface);
DataAdapter adapter = new DataAdapter();
view.setAdapter(adapter);//初始化
adapter.setBmp(bmp);//設定圖片(底圖)
adapter.setList(list);//設定陣列(圖上的可點區域)
adapter.refreshData();
複製程式碼
程式碼中的 list 的具體設定看這裡:(輸入鋼筆路徑需要是圖片左上角的相對座標)
//每個圖案的節點座標集合
private List<PointF> getList(){
float density = getResources().getDisplayMetrics().density;
List<PointF> pointList = new ArrayList<>();
pointList.add(new PointF(99.1f * density,673.1f * density));
pointList.add(new PointF(222.1f * density,670.1f * density));
pointList.add(new PointF(227.1f * density,327.1f * density));
pointList.add(new PointF(94.1f * density,321.1f * density));
pointList.add(new PointF(100.1f * density,674.1f * density));
return pointList;
}
//區域列表
private void getUnitList(){
PathUnit unit = new PathUnit(getList());//把節點換成一個 PathUnit 元素
unit.setName("John Market");//設定元素的名字
unitList.add(unit);//新增到區域列表
}
複製程式碼
注:從資源讀取的圖片對應的座標要乘上 desity ,網路載入的圖片則不用
通過該介面可以返回點選到的區域的 PathUnit 元素,可通過此來獲取區域名字等資訊
view.setOnClickMapListener(new InDoorView.onClickMapListener() {
@Override
public void onClick(PathUnit region) {
//讀取 pathunit
}
}
複製程式碼
該介面是 fps 幀率
view.onFramesListener(new InDoorView.FramesListener() {
@Override
public void onRefresh(float number) {
//幀率
}
}
複製程式碼
庫生產環境:
compileSdkVersion 24
minSdkVersion 16
License
Apache License 2.0