Skip to content

多屏显示

本指南:在同一应用中多块屏幕展示地图,支持 多个 TnMapViewTnClusterMapView 两种实现方式。

功能介绍

多屏显示指在同一套导航应用中,于中控屏、副屏、仪表盘等多个区域同时展示地图。SDK 提供两种常见实现路径,可按 HMI 形态与能力需求选型:

效果示意

多屏幕显示

方案 实现类 典型场景
多个 MapView 多个 TnMapView 多块标准 Android 屏、分屏/画中画、需完整 MapView 能力(内置手势等)
Cluster 副屏 TnClusterMapView 仪表盘、外接 SurfaceTextureView / SurfaceView)、与中控分离渲染

路口放大图使用独立的 TnIntersectionView,见 路口放大图

1
2
3
4
5
导航应用(示例)
├── 方案 A:主屏 TnMapView + 副屏 TnMapView     → 各自 MapViewInitConfig
└── 方案 B:主屏 TnMapView + 仪表盘 TnClusterMapView → ClusterMapViewParams + Surface
         ↓
共享:定位、算路、导航状态 → 分别更新各实例 Controller(SDK 不自动同步)

方案选型

多个 TnMapView TnClusterMapView
布局方式 XML 嵌入 <com.telenav.map.views.TnMapView> 代码创建 + 外接 Surface
初始化 MapViewInitConfig(见 快速开始 ClusterMapViewParams
Surface 生命周期 View 系统自动管理 须手动 转发 onSurfaceCreated/Changed/Destroyed
控制器 API getCameraController() cameraController()
手势 setActiveGestures、内置触摸监听 gestureProvider() / ClusterMapViewGestureBinder

说明:多屏并不要求副屏一定使用 TnClusterMapView。若副屏同样是常规 Android View 布局,直接使用第二个 TnMapView 即可,集成方式与主屏相同。


方案一:多个 TnMapView

在布局中放置 多个 TnMapView,每个实例独立 initialize,即可实现多屏地图。适用于双屏导航、主屏+预览小窗、多 Fragment 各带地图等场景。

布局示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<LinearLayout ...>
    <com.telenav.map.views.TnMapView
        android:id="@+id/main_map_view"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />
    <com.telenav.map.views.TnMapView
        android:id="@+id/secondary_map_view"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />
</LinearLayout>

初始化要点

  • 每个 TnMapView 单独调用 initialize(MapViewInitConfig),可在 viewOptions.stylePath 中为副屏指定不同 TSS(如 styles/cluster/newstyle.tss)。
  • 各自在 onReady 中配置相机、CVP、路线等;API 与单屏一致,详见 快速开始 及各专题文档。
  • 导航回调中须 分别 更新每个实例(见 多实例数据同步)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
private lateinit var mainMapView: TnMapView
private lateinit var secondaryMapView: TnMapView

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    mainMapView = view.findViewById(R.id.main_map_view)
    secondaryMapView = view.findViewById(R.id.secondary_map_view)

    mainMapView.initialize(buildMapConfig(stylePath = "styles/default/newstyle.tss") {
        onMainMapReady(mainMapView)
    })
    secondaryMapView.initialize(buildMapConfig(stylePath = "styles/cluster/newstyle.tss") {
        onSecondaryMapReady(secondaryMapView)
    })
}

fun onLocationUpdated(location: Location) {
    mainMapView.getVehicleController()?.setLocation(location)
    secondaryMapView.getVehicleController()?.setLocation(location)
}

方案二:TnClusterMapView

当副屏通过 TextureView / SurfaceView 提供渲染表面(常见于仪表盘、嵌入式 Secondary Display)时,使用 TnClusterMapView 绑定外接 Surface,无需在布局中嵌入完整 TnMapView

核心接口一览

分类 方法 说明
初始化 initialize(ClusterMapViewParams) Surface 可用后调用
生命周期 onSurfaceCreated/Changed/Destroyed Surface 事件转发
生命周期 onResume/onPause/onDestroy 对齐 Activity
控制器 cameraController() onReady 之后可用
手势 gestureProvider() 触摸 / 原始手势
其他 setFPS(fps) 帧率,默认 30
其他 isFinishedLoading(mask) 特性加载状态

接口详细说明

1. initialize — 初始化 Cluster 地图

1
fun initialize(params: ClusterMapViewParams)
参数 类型 默认 说明
context Context 必填 建议 applicationContext
surface Surface 必填 渲染目标;来自 TextureViewSurface(SurfaceTexture))或 SurfaceViewholder.surface
width / height Int 0 逻辑尺寸(像素)
density Float 0f 屏幕密度
defaultLocation LatLon 初始中心
createCVP Boolean true 是否创建默认车辆图标
zoomLevel Float 5f 初始缩放
readyListener MapViewReadyListener<ClusterMapView?> 空实现 就绪回调
autoZoomLevel AutoZoomLevel DEFAULT 自动缩放幅度
viewOptions ViewOptions? null 样式、日/夜等;副屏常用 styles/cluster/newstyle.tss

初始化顺序initialize(params)onSurfaceCreated()onReady 回调中配置 Controller。


2. Surface 来源与生命周期

ClusterMapViewParams.surface 可使用任意有效的 Android Surface,常见两种来源:

来源 获取方式 说明
TextureView Surface(surfaceTexture) 灵活叠层、支持动画与透明度;可与 ClusterMapViewGestureBinder 绑定手势
SurfaceView surfaceView.holder.surface 独立 Surface 层,性能稳定;通过 SurfaceHolder.Callback 管理生命周期

无论哪种 View,都须将 SurfaceHolder / SurfaceTexture 的创建、变更、销毁事件 转发TnClusterMapView 对应方法,并配合 Activity 的 onResume / onPause / onDestroy

方法 调用时机
onSurfaceCreated() Surface 首次可用
onSurfaceChanged(width, height) 尺寸变化
onSurfaceChanged(surface, w, h) Surface 对象与尺寸均变化
onSurfaceDestroyed() Surface 销毁
onResume() / onPause() / onDestroy() 对齐 Activity 生命周期

TextureView 时序

1
2
3
4
onSurfaceTextureAvailable
  → Surface(texture) → initialize(params) → onSurfaceCreated() → onReady
onSurfaceTextureSizeChanged → onSurfaceChanged(w, h)
onSurfaceTextureDestroyed → onSurfaceDestroyed() → onDestroy()

SurfaceView 时序

1
2
3
4
surfaceCreated
  → initialize(params)(surface = holder.surface)→ onSurfaceCreated() → onReady
surfaceChanged → onSurfaceChanged(w, h)
surfaceDestroyed → onSurfaceDestroyed() → onDestroy()

3. 手势(MapGestureProvider)

方法 适用平台 说明
motionEventGesture().onTouchEvent(event) Android 触摸屏 转发 MotionEvent

TextureView:可使用 ClusterMapViewGestureBinder 自动转发触摸:

1
2
3
ClusterMapViewGestureBinder.bind(textureView, clusterMapView)
// 销毁时
ClusterMapViewGestureBinder.unbind(textureView)

SurfaceView:在 View 的 onTouchEventsetOnTouchListener 中手动转发:

1
2
3
4
surfaceView.setOnTouchListener { _, event ->
    clusterMapView?.gestureProvider()?.motionEventGesture()?.onTouchEvent(event)
    true
}

TnClusterMapView 同时支持 setOnAnnotationTouchListenersetOnRouteTouchListener 等,用法同 触摸与手势


多实例数据同步

无论采用 多个 TnMapView 还是 TnMapView + TnClusterMapView,SDK 不会 自动跨屏同步车辆位置、路线与图层状态,须在业务层统一广播:

1
2
3
4
5
fun updateAllScreens(location: Location) {
    mainMapView.getVehicleController()?.setLocation(location)
    secondaryMapView.getVehicleController()?.setLocation(location)  // 方案一
    clusterMapView?.vehicleController()?.setLocation(location)       // 方案二
}

路线、标注、特性开关、updateRouteProgress 等同理,在导航或定位回调中分别调用各实例对应 Controller。


集成示例(TnClusterMapView)

TextureView

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class ClusterMapHelper(
    private val textureView: TextureView,
    private val context: Context
) {
    private var clusterMapView: TnClusterMapView? = null
    private var clusterSurface: Surface? = null

    fun start(initial: LatLon) {
        textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
            override fun onSurfaceTextureAvailable(st: SurfaceTexture, width: Int, height: Int) {
                clusterSurface = Surface(st)
                clusterMapView = TnClusterMapView().apply {
                    initialize(
                        ClusterMapViewParams(
                            context = context.applicationContext,
                            surface = clusterSurface!!,
                            width = width,
                            height = height,
                            density = context.resources.displayMetrics.density,
                            defaultLocation = initial,
                            createCVP = true,
                            viewOptions = ViewOptions(
                                stylePath = "styles/cluster/newstyle.tss"
                            ),
                            readyListener = object : MapViewReadyListener<ClusterMapView?> {
                                override fun onReady(view: ClusterMapView?) {
                                    view ?: return
                                    view.cameraController()?.apply {
                                        renderMode = Camera.RenderMode.M3D
                                        enableFollowVehicleMode(
                                            Camera.FollowVehicleMode.HeadingUp,
                                            useAutoZoom = true
                                        )
                                    }
                                    view.featuresController().traffic().setEnabled()
                                }
                                override fun getReadyFeaturesMask() =
                                    ClusterMapView.eFeatureCategory_Vital
                            }
                        )
                    )
                    onSurfaceCreated()
                }
                ClusterMapViewGestureBinder.bind(textureView, clusterMapView!!)
            }

            override fun onSurfaceTextureSizeChanged(st: SurfaceTexture, w: Int, h: Int) {
                clusterMapView?.onSurfaceChanged(w, h)
            }

            override fun onSurfaceTextureDestroyed(st: SurfaceTexture): Boolean {
                ClusterMapViewGestureBinder.unbind(textureView)
                clusterMapView?.onSurfaceDestroyed()
                clusterSurface = null
                return true
            }

            override fun onSurfaceTextureUpdated(st: SurfaceTexture) {}
        }
    }

    fun onResume() { clusterMapView?.onResume() }
    fun onPause() { clusterMapView?.onPause() }
    fun onDestroy() {
        clusterMapView?.onDestroy()
        clusterMapView = null
    }

    fun updateVehicle(location: Location) {
        clusterMapView?.vehicleController()?.setLocation(location)
    }
}

SurfaceView

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class ClusterSurfaceViewHelper(
    private val surfaceView: SurfaceView,
    private val context: Context
) {
    private var clusterMapView: TnClusterMapView? = null

    fun start(initial: LatLon) {
        surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(holder: SurfaceHolder) {
                if (clusterMapView == null) {
                    clusterMapView = TnClusterMapView().apply {
                        initialize(
                            ClusterMapViewParams(
                                context = context.applicationContext,
                                surface = holder.surface,
                                width = surfaceView.width,
                                height = surfaceView.height,
                                density = context.resources.displayMetrics.density,
                                defaultLocation = initial,
                                viewOptions = ViewOptions(
                                    stylePath = "styles/cluster/newstyle.tss"
                                )
                            )
                        )
                    }
                }
                clusterMapView?.onSurfaceCreated()
            }

            override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
                clusterMapView?.onSurfaceChanged(w, h)
            }

            override fun surfaceDestroyed(holder: SurfaceHolder) {
                clusterMapView?.onSurfaceDestroyed()
            }
        })

        surfaceView.setOnTouchListener { _, event ->
            clusterMapView?.gestureProvider()?.motionEventGesture()?.onTouchEvent(event)
            true
        }
    }

    fun onResume() { clusterMapView?.onResume() }
    fun onPause() { clusterMapView?.onPause() }
    fun onDestroy() {
        clusterMapView?.onDestroy()
        clusterMapView = null
    }
}

注意事项

通用(含多个 TnMapView)

  1. 每个 TnMapView / TnClusterMapView 均为 独立实例,数据与 Controller 须分别更新。
  2. 各屏可使用不同 stylePath,见 地图配色方案
  3. Controller 须在对应实例 onReady 之后访问。

TnClusterMapView 专用

  1. 初始化顺序:先 initialize,再 onSurfaceCreated();销毁时先 onSurfaceDestroyed(),最后 onDestroy()
  2. Surface 来源TextureViewSurfaceView 二选一,生命周期回调须与所用 View 一致。

相关指南