Create a Custom Panel Type
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.
The system UI in the TomTom Digital Cockpit visualizes data represented by Panel
s. They are the main
building blocks of the system UI. Each Frontend
that has a UI adds one or
more new Panel
s to visualize itself. The
System UI Overview page introduces the
anatomy of the system UI and contains examples of built-in Panel
types. Their implementations
can be found in the package
com.tomtom.ivi.platform.frontend.api.common.frontend.panels
.
Panels are displayed in a panel container. A panel container defines both the location on the screen
and the "height" a panel is displayed at (z-index). Moving panel containers to a different location
on the screen can be done in the system UI layout, a custom panel type is not needed in this case.
However, when you want to change the z-index of some information, or want to split information currently
displayed at one location, a custom panel type can be the solution. If none of the built-in Panel
types
are suitable for your use case, you can implement a custom Panel
type for your system UI.
This tutorial shows how to do this.
Create a custom panel type base class
Create a
CustomSystemUiPanel.kt
file containing the base class for a custom panel type and a variable that declares a full set of
panel types.
1abstract class CustomSystemUiPanel(frontendContext: FrontendContext) : Panel(frontendContext)23val CUSTOM_SYSTEM_UI_PANEL_TYPES: PanelTypeSet = PLATFORM_PANEL_TYPES + CustomSystemUiPanel::class
Create a custom frontend
Create a custom frontend in four steps.
Create a custom frontend implementation
Create an
ExampleFrontend.kt
file containing a custom Frontend
. Note how a custom panel is added
when this frontend is created.
1internal class ExampleFrontend(frontendContext: FrontendContext) : Frontend(frontendContext) {23 override fun onCreate() {4 super.onCreate()56 val isTaskPanelOpened = panels.map { it.any { panel -> panel is TaskPanel } }7 addPanel(ExampleCustomPanel(frontendContext, isTaskPanelOpened))8 }9}
Create a custom frontend builder
Create an
ExampleFrontendBuilder.kt
file containing a custom frontend builder implementation.
1class ExampleFrontendBuilder : FrontendBuilder() {23 override fun build(frontendContext: FrontendContext): Frontend =4 ExampleFrontend(frontendContext)5}
Create a custom menu item
Create an
ExampleMenuItem.kt
file containing a custom menu item.
1val exampleMenuItem =2 MenuItem(3 ExampleFrontend::class.qualifiedName!!,4 R.drawable.ttivi_example_menuitem,5 R.string.ttivi_systemui_custompaneltype_frontend_menuitem_name6 )
Create an implementation of a custom panel type
Create an
ExampleCustomPanel.kt
file containing an implementation of the abstract CustomSystemUiPanel
, its fragment and a view model.
1internal class ExampleCustomPanel(2 frontendContext: FrontendContext,3 val isTaskPanelOpened: LiveData<Boolean>4) : CustomSystemUiPanel(frontendContext) {5 override fun createInitialFragmentInitializer(): IviFragment.Initializer<*, *> =6 IviFragment.Initializer(ExampleCustomFragment(), this)7}89internal class ExampleCustomFragment :10 IviFragment<ExampleCustomPanel, ExampleCustomViewModel>(ExampleCustomViewModel::class) {1112 override val viewFactory: ViewFactory<*>? =13 ViewFactory(TtiviCustompaneltypeCustompanelBinding::inflate)14}1516internal class ExampleCustomViewModel(panel: ExampleCustomPanel) :17 FrontendViewModel<ExampleCustomPanel>(panel) {1819 val isTaskPanelOpened = panel.isTaskPanelOpened20}
Create a layout of a custom fragment
Create a
ttivi_custompaneltype_custompanel.xml
layout file for a custom fragment.
1<layout xmlns:android="http://schemas.android.com/apk/res/android"2 xmlns:auto="http://schemas.android.com/apk/res-auto"3 xmlns:tools="http://schemas.android.com/tools">45 <data>6 <variable7 name="viewModel"8 type="com.example.ivi.example.systemui.custompaneltype.frontend.ExampleCustomViewModel" />9 </data>1011 <LinearLayout12 android:layout_width="wrap_content"13 android:layout_height="match_parent"14 android:orientation="vertical">1516 <com.tomtom.tools.android.api.uicontrols.textview.TtTextView17 android:layout_width="wrap_content"18 android:layout_height="wrap_content"19 android:layout_margin="?attr/tt_spacing_16"20 android:gravity="start"21 android:text="@string/ttivi_systemui_custompaneltype_frontend_panel_header"22 android:textAlignment="textStart"23 android:textAppearance="?attr/tt_headline_text_style_l"24 android:textColor="?attr/tt_surface_content_color_emphasis_high" />2526 <com.tomtom.tools.android.api.uicontrols.textview.TtTextView27 android:layout_width="wrap_content"28 android:layout_height="wrap_content"29 android:layout_margin="?attr/tt_spacing_16"30 android:gravity="start"31 android:text="@string/ttivi_systemui_custompaneltype_frontend_opened"32 android:textAlignment="textStart"33 android:textAppearance="?attr/tt_headline_text_style_m"34 android:textColor="?attr/tt_surface_content_color_emphasis_high"35 auto:visibleIf="@{viewModel.isTaskPanelOpened}" />3637 <com.tomtom.tools.android.api.uicontrols.textview.TtTextView38 android:layout_width="wrap_content"39 android:layout_height="wrap_content"40 android:layout_margin="?attr/tt_spacing_16"41 android:gravity="start"42 android:text="@string/ttivi_systemui_custompaneltype_frontend_closed"43 android:textAlignment="textStart"44 android:textAppearance="?attr/tt_headline_text_style_m"45 android:textColor="?attr/tt_surface_content_color_emphasis_high"46 auto:visibleIfNot="@{viewModel.isTaskPanelOpened}" />4748 </LinearLayout>49</layout>
Add the custom frontend to an IVI instance
Add the following to the
build.gradle.kts
.
1val module = ExampleModuleReference("examples_customization_custompaneltype")23val customPanelTypes = PanelTypesConfig(4 "CUSTOM_SYSTEM_UI_PANEL_TYPES",5 module,6 "common"7)89val exampleFrontend =10 FrontendConfig(11 frontendBuilderName = "ExampleFrontendBuilder",12 implementationModule = module,13 subPackageName = "frontend",14 availablePanelTypes = customPanelTypes15 )1617val exampleMenuItem = exampleFrontend.toMenuItem("exampleMenuItem")1819ivi {20 optInToExperimentalApis = true21 application {22 enabled = true23 iviInstances {24 create(IviInstanceIdentifier.default) {25 applyGroups {26 includeDefaultPlatformGroups()27 includeDefaultAppsuiteGroups()28 }29 frontends {30 add(exampleFrontend)31 }32 menuItems {33 addFirst(exampleMenuItem to exampleFrontend)34 }35 }36 }37 }38}
Create a custom system UI host
Create a custom panel registry
Create a
CustomPanelRegistry.kt
file containing a custom panel registry implementation.
1internal class CustomPanelRegistry(2 val customPanel: LiveData<CustomSystemUiPanel?>,3 val iviPanelRegistry: IviPanelRegistry4) : PanelRegistry {56 companion object {7 fun create(8 frontendRegistry: FrontendRegistry,9 lifecycleOwner: LifecycleOwner,10 iviServiceProvider: IviInstanceBoundIviServiceProvider11 ) = CustomPanelRegistry(12 customPanel = frontendRegistry.frontends.flatMap { it.panels }.mapToSingle(),13 iviPanelRegistry = IviPanelRegistry.build(14 frontendRegistry.extractPanels(),15 lifecycleOwner,16 iviServiceProvider17 )18 )19 }20}
Create a custom system UI view model
Create a
CustomSystemUiViewModel.kt
file containing a custom system UI model implementation.
1internal class CustomSystemUiViewModel(2 coreViewModel: CoreSystemUiViewModel3) : LifecycleViewModel() {45 val frontendCoordinator = FrontendCoordinator(6 lifecycleOwner = this,7 coreViewModel.iviServiceProvider,8 coreViewModel.frontendMetadata,9 coreViewModel.defaultFrontendContextFactory,10 { frontendRegistry ->11 CustomPanelRegistry.create(12 frontendRegistry,13 lifecycleOwner = this,14 coreViewModel.iviServiceProvider15 )16 },17 { frontendRegistry, panelRegistry ->18 DefaultFrontendCoordinationRules19 .create(frontendRegistry, panelRegistry.iviPanelRegistry)20 }21 )2223 val panelRegistry = frontendCoordinator.panelRegistry2425 val menuPanel = panelRegistry.iviPanelRegistry.mainMenuPanel2627 val customPanel = panelRegistry.customPanel28}
Create a custom system UI host
Create a
CustomSystemUiHost.kt
file containing a custom system UI host implementation. Note that CustomSystemUiPanel
is listed
in the supported panel types.
1internal class CustomSystemUiHost(systemUiHostContext: SystemUiHostContext) :2 SystemUiHost(systemUiHostContext) {34 private lateinit var viewModel: CustomSystemUiViewModel56 override val viewFactory: ViewFactory =7 BindingViewFactory(TtiviCustompaneltypeCustomsystemuiBinding::inflate, ::bindSystemUiView)89 override val supportedPanelTypes: PanelTypeSet = panelTypeSetOf(10 CustomSystemUiPanel::class,11 MainMenuPanel::class,12 TaskPanel::class,13 TaskProcessPanel::class,14 )1516 override val unsupportedPanelTypes: PanelTypeSet = panelTypeSetOf(17 ControlCenterPanel::class,18 DebugPanel::class,19 ExpandedProcessPanel::class,20 HomePanel::class,21 MainProcessPanel::class,22 ModalPanel::class,23 NavigationPanel::class,24 NotificationPanel::class,25 OverlayPanel::class,26 SettingsPanel::class,27 )2829 override fun onCreate() {30 viewModel = ViewModelProvider(31 viewModelStoreOwner,32 FixedConstructorFactory(coreViewModel)33 )[CustomSystemUiViewModel::class.java]34 }3536 override fun onSystemUiPresented() {37 viewModel.frontendCoordinator.onSystemUiPresented()38 }3940 private fun bindSystemUiView(binding: TtiviCustompaneltypeCustomsystemuiBinding) {41 binding.viewModel = viewModel42 binding.panelRegistry = viewModel.panelRegistry4344 setIviOnBackPressedCallbacks(listOf(binding.exampleSystemuiTaskPanelStackContainer))45 }46}
Create a custom system UI layout
Create a
ttivi_custompaneltype_customsystemui.xml
file containing a custom system UI layout.
1<?xml version="1.0" encoding="utf-8"?>2<layout xmlns:android="http://schemas.android.com/apk/res/android"3 xmlns:auto="http://schemas.android.com/apk/res-auto"4 xmlns:tools="http://schemas.android.com/tools">56 <data>7 <variable8 name="viewModel"9 type="com.example.ivi.example.systemui.custompaneltype.systemui.CustomSystemUiViewModel" />1011 <variable12 name="panelRegistry"13 type="com.example.ivi.example.systemui.custompaneltype.systemui.CustomPanelRegistry" />1415 <variable16 name="panelContainerContext"17 type="com.tomtom.ivi.platform.systemui.api.common.systemuihost.panelcontainer.PanelContainerContext" />18 </data>1920 <LinearLayout21 android:layout_width="match_parent"22 android:layout_height="match_parent"23 android:orientation="horizontal">2425 <com.tomtom.ivi.platform.systemui.api.common.systemuihost.panelcontainer.SinglePanelContainer26 android:id="@+id/example_systemui_custompanel_container"27 android:layout_width="wrap_content"28 android:layout_height="match_parent"29 auto:ttiviPanelContainerContext="@{panelContainerContext}"30 auto:ttiviPanelContainerData="@{viewModel.customPanel}" />3132 <com.tomtom.ivi.platform.systemui.api.common.systemuihost.panelcontainer.SinglePanelContainer33 android:id="@+id/example_systemui_menu_container"34 android:layout_width="?attr/ttivi_mainmenu_panel_size"35 android:layout_height="match_parent"36 auto:ttiviPanelContainerContext="@{panelContainerContext}"37 auto:ttiviPanelContainerData="@{viewModel.menuPanel}" />3839 <com.tomtom.ivi.platform.systemui.api.common.systemuihost.panelcontainer.TaskPanelStackContainer40 android:id="@+id/example_systemui_task_panel_stack_container"41 android:layout_width="0dp"42 android:layout_height="match_parent"43 android:layout_weight="1"44 android:minWidth="800dp"45 auto:ttiviPanelContainerContext="@{panelContainerContext}"46 auto:ttiviPanelContainerData="@{panelRegistry.iviPanelRegistry.taskPanelStackData}" />47 </LinearLayout>48</layout>
Create a custom activity
Create a
CustomPanelTypeActivity.kt
file containing a sub class of the DefaultActivity
.
1class CustomPanelTypeActivity : DefaultActivity() {2 override fun createSystemUiHost(iviInstanceId: IviInstanceId): SystemUiHost =3 CustomSystemUiHost(getDefaultSystemUiHostContext(iviInstanceId))4}
See the custom panel type
After the application is built and deployed to a target device, the custom panel type is displayed on the screen.