Create a Custom Panel Container

Last edit: 2023.07.27

Knowledge of the system UI will help you to understand this guide. If you are not yet familiar with it, feel free to take a look at the system UI overview.

System UI in the TomTom Digital Cockpit visualizes data represented by Panels. How panels are positioned relative to each other, how they are animated or if they can be dismissed - all this coordination is defined by the PanelContainer which displays the concrete Panel. The closest counterpart of a PanelContainer in Android OS is a ViewGroup. In fact, all PanelContainers that come with TomTom Digital Cockpit are based on ViewGroup and implement the PanelContainer interface. You can see all PanelContainers used by stock system UI in the package com.tomtom.ivi.platform.systemui.api.common.systemuihost.panelcontainer.

This example shows how to create a custom PanelContainer to display two CustomPanels side-by-side.

This tutorial only describes how to create the custom PanelContainer, creation of custom Panel types is described in the Custom Panel Type example.

Create a data class to represent two panels

1internal data class DualPanelContainerData(
2 val panel1: Panel,
3 val panel2: Panel
4)

Create a custom panel container

Create a DualPanelContainer with StateDrivenPanelContainer as a superclass.

1internal class DualPanelContainer @JvmOverloads constructor(
2 context: Context,
3 attrs: AttributeSet? = null,
4 defStyleAttr: Int = 0
5) :
6 StateDrivenPanelContainer<
7 DualPanelContainerData?,
8 DualPanelSubContainerViewModel,
9 DualPanelContainerData>(
10 context,
11 attrs,
12 defStyleAttr
13 ) {
14 override fun createSubContainerController() =
15 DualPanelSubContainerController(ttiviSubContainerLayoutId)
16
17 override fun createSubContainerViewModel() =
18 DualPanelSubContainerViewModel()
19}

Create a sub-container view model

Create a DualPanelSubContainerViewModel with PanelSubContainerViewModel as a superclass. Note that the custom data type, which was created in the first step is used as a type parameter of the generic PanelSubContainerViewModel.

1internal class DualPanelSubContainerViewModel :
2 PanelSubContainerViewModel<DualPanelContainerData>() {
3
4 internal val panelFragmentContainer1Id:
5 PanelFragmentContainerId by generatePanelFragmentContainerId()
6
7 internal val panelFragmentContainer2Id:
8 PanelFragmentContainerId by generatePanelFragmentContainerId()
9}

Create a sub-container controller

Create a DualPanelSubContainerController with StateDrivenSubContainerController as a superclass. This is the class where the custom data type DualPanelContainerData is bound to views on the screen and updates of data are handled. All generic code for all sub-containers resides in the superclass, the custom panel container must implement these abstract methods:

  • getNewSubContainerData() to determine when new sub-containers must be created.
1 override fun getNewSubContainerData(
2 panelContainerViewModel: DualPanelContainerViewModel
3 ) = listOfNotNull(
4 panelContainerViewModel
5 .panelContainerData
6 ?.takeUnless { panelContainerViewModel.hasSubContainerViewModelWithData(it) }
7 )
  • createSubContainer() to inflate sub-container's layout and bind the data model to its views.
1 override fun createSubContainer(
2 panelContainer: ViewGroup,
3 subContainerViewModel: DualPanelSubContainerViewModel,
4 panelContainerViewModel: DualPanelContainerViewModel
5 ): StateDrivenSubContainerController.SubContainerCreationResult<
6 DualPanelSubContainerViewModel,
7 DualPanelContainerData
8 > {
9
10 val subContainerHolder = inflateSubContainerAndBindViewModel(
11 panelContainer,
12 subContainerLayoutId,
13 subContainerViewModel
14 )
15
16 val panelFragment1Holder = subContainerHolder.findPanelFragmentContainerAndSetId(
17 subContainerViewModel.panelFragmentContainer1Id,
18 R.id.ttivi_custompanelcontainer_panel_1
19 )
20
21 val panelFragment2Holder = subContainerHolder.findPanelFragmentContainerAndSetId(
22 subContainerViewModel.panelFragmentContainer2Id,
23 R.id.ttivi_custompanelcontainer_panel_2
24 )
25
26 val panelFragmentAdapterBuilder: PanelFragmentAdapterBuilder.() -> Unit = {
27 createSingleFragmentPanelAdapter(panelFragment1Holder) {
28 SinglePanelAttachment(subContainerViewModel.subContainerData.panel1) { it }
29 }
30
31 createSingleFragmentPanelAdapter(panelFragment2Holder) {
32 SinglePanelAttachment(subContainerViewModel.subContainerData.panel2) { it }
33 }
34 }
35
36 return StateDrivenSubContainerController.SubContainerCreationResult(
37 subContainerHolder,
38 panelFragmentAdapterBuilder
39 )
40 }
  • getSubContainerDataUpdates() to determine if data represented by sub-container has changed.
1 override fun getSubContainerDataUpdates(
2 panelContainerViewModel: DualPanelContainerViewModel
3 ): Collection<StateDrivenSubContainerController.SubContainerDataUpdate<
4 DualPanelSubContainerViewModel,
5 DualPanelContainerData>
6 > {
7 val data = panelContainerViewModel.panelContainerData ?: return emptyList()
8 val subContainerViewModel = panelContainerViewModel
9 .subContainerViewModels
10 .firstOrNull() ?: return emptyList()
11
12 val update = StateDrivenSubContainerController.SubContainerDataUpdate(
13 subContainerViewModel,
14 data
15 )
16
17 return listOf(update)
18 }

The complete code of the DualPanelSubContainerController can be found in the GitHub.

See the custom panel container

After the application is built and deployed to a target device, the custom panel container is displayed on the screen.

Custom panel container