
Dostosowywanie układu pozwala kontrolować hierarchię widoków i umieszczanie na ekranie zarówno widoków udostępnianych przez Google, jak i własnych widoków biznesowych.
Zamiast dodawać widoki niestandardowe do wstępnie zdefiniowanych boksów nagłówka lub stopki, zaimplementuj delegata układu, aby zarządzać układem całego ekranu. Podczas zmian stanu nawigacji Twój delegat układu otrzymuje komponenty dostarczone przez Google, takie jak karta skrętu, karta szacowanego czasu dotarcia na miejsce i przyciski. Umieść te elementy obok własnych treści niestandardowych, korzystając ze standardowych systemów układu Androida, takich jak ConstraintLayout, CoordinatorLayout lub LinearLayout.
Użyj tej platformy, aby precyzyjnie umieścić na ekranie niestandardowe informacje o firmie, takie jak stan zamówienia lub instrukcje odbioru, i zapobiec nakładaniu się widoków.
Jak działa dostosowywanie układu
Dostosowywanie układu korzysta ze wzorca projektowego przekazywania. Zamiast automatycznie rysować lub pozycjonować komponenty interfejsu na ekranie, pakiet SDK przekazuje je bezpośrednio do niestandardowej klasy, którą tworzysz: delegata układu.
Aby dostosować układ, utwórz klasę, która rozszerza klasę abstrakcyjnąNavigationLayoutDelegate, i przypisz instancję do NavigationView lub SupportNavigationFragment. Za każdym razem, gdy zmienia się stan nawigacji – na przykład gdy przechodzisz z mapy podstawowej do aktywnej nawigacji zakręt po zakręcie – pakiet SDK wykonuje metody wywołania zwrotnego w obiekcie delegowanym i udostępnia komponenty interfejsu dostępne dla danego stanu.
Aby zapewnić użytkownikom płynne i zintegrowane działanie, ta platforma wyraźnie rozdziela odpowiedzialność między aplikację a pakiet SDK. Użyj delegata układu, aby dostosować te elementy:
- Utworzyć hierarchię widoków: wybierz dokładnie, które komponenty Google i niestandardowe widoki biznesowe chcesz dodać do ekranu w przypadku każdego stanu interfejsu.
- Pozycjonowanie każdego elementu: ustawianie precyzyjnych punktów zakotwiczenia, marginesów i miejsca w układzie. Unikaj stosowania niestandardowych ograniczeń szerokości lub wysokości do komponentów Google, ponieważ komponenty Google obliczają własne wymiary wewnętrzne.
- Kadrowanie mapy podstawowej: określ widoczne granice kamery mapy za pomocą współrzędnych komponentu widocznego obszaru.
- Warstwy ekranu: zdecyduj, czy widoki niestandardowe mają być wyświetlane nad, pod czy obok wbudowanych elementów sterujących Google.
Nie możesz jednak dostosować tych komponentów Google za pomocą delegata układu:
- Wymiary komponentu: rozmiar i wymiary wewnętrzne komponentów dostarczanych przez Google, które pakiet SDK oblicza automatycznie.
- Warunki aktywacji: kiedy dynamiczne alerty lub prośby pojawiają się na podstawie danych o trasie w czasie rzeczywistym.
Zasady implementacji
Podczas pisania delegata układu pamiętaj o tych regułach, aby zapobiec błędom układu lub awariom w czasie działania:
- Domyślnie jest to pusty ekran: komponenty dostarczane przez Google pojawiają się tylko wtedy, gdy delegat wyraźnie doda je do hierarchii widoków i ustawi ich pozycję.
- Starsze interfejsy API nie są obsługiwane: właściwości i metody, które kontrolują starszy model układu opartego na boksach, nie będą obsługiwane i mogą nie działać zgodnie z oczekiwaniami, gdy aktywny jest niestandardowy obiekt delegowany.
- Nie modyfikuj wewnętrznych struktur widoku: nie używaj metod takich jak
findViewById()do przechodzenia przez hierarchie widoków komponentów dostarczanych przez Google (takich jak karta skrętu lub karta szacowanego czasu dotarcia na miejsce) ani do ich modyfikowania. Te wewnętrzne hierarchie widoków są szczegółami implementacji, więc mogą się zmieniać w poszczególnych wersjach pakietu SDK. Ich zmodyfikowanie może spowodować uszkodzenie układu podczas przyszłych aktualizacji pakietu SDK.
Zgodność ze starszą wersją interfejsu API
Aby zapewnić niezawodne działanie układu podczas korzystania z niestandardowego delegata układu, unikaj używania tych starszych interfejsów API opartych na boksach, które planujemy wycofać. Aby przeprowadzić migrację istniejącej aplikacji, zastąp użycie tych interfejsów API kodem w niestandardowym delegacie układu:
Pokaż starsze interfejsy API oparte na boksach
| Starszy interfejs API | Zastąpienie delegata układu |
|---|---|
setCustomControl(View, CustomControlPosition) |
Dodaj widoki bezpośrednio do grupy ConstraintLayout lub innej grupy widoków. |
removeCustomControl(View) |
Usuń widoki bezpośrednio z hierarchii widoków. |
setEtaCardEnabled(boolean) |
Odczyt widoku etaCard w usłudze onEnterActiveGuidance. |
setHeaderEnabled(boolean) |
Odczyt widoku turnCard w usłudze onEnterActiveGuidance. |
setReportIncidentButtonEnabled(boolean) |
Znajdź przycisk REPORTING w aplikacji getActiveGuidanceButtons(). |
setTripProgressBarEnabled(boolean) |
Odczyt widoku tripProgressBar w usłudze onEnterActiveGuidance. |
addOnNavigationUiChangedListener(...) |
Polegaj na wywołaniach zwrotnych zmiany stanu NavigationLayoutDelegate. |
removeOnNavigationUiChangedListener(...) |
Zarządzaj przejściami stanu układu bezpośrednio za pomocą delegata. |
addPromptVisibilityChangedListener(...) |
Polegaj na wywołaniach zwrotnych promptów NavigationLayoutDelegate (takich jak onShowPrompt()). |
removePromptVisibilityChangedListener(...) |
Zarządzaj logiką wyświetlania próśb bezpośrednio za pomocą delegata. |
setCompassEnabled(boolean) |
Znajdź kompas w aplikacji getNavigationReadyButtons() lub getActiveGuidanceButtons(). |
Lista kontrolna Essentials
Aby prawidłowo wdrożyć delegata układu, wykonaj te podstawowe czynności i spełnij te wymagania:
-
Zainicjuj obiekt delegowany przed utworzeniem interfejsu: wywołaj funkcję
setLayoutDelegate(), zanim pakiet SDK zainicjuje interfejs nawigacji. Zapoznaj się z poniższymi przykładami implementacji kodu, aby sprawdzić dokładny czas konfiguracji struktury aplikacji. Inicjowanie obiektu delegowanego po utworzeniu interfejsu powoduje wywołanie błęduApiIllegalStateException.Kotlin
// For SupportNavigationFragment override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val navFragment = supportFragmentManager.findFragmentById(R.id.nav_fragment) as SupportNavigationFragment navFragment.setLayoutDelegate(MyLayoutDelegate()) } // For a programmatic NavigationView val navigationView = NavigationView(context) navigationView.setLayoutDelegate(MyLayoutDelegate()) navigationView.onCreate(savedInstanceState)
Java
// For SupportNavigationFragment @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SupportNavigationFragment navFragment = (SupportNavigationFragment) getSupportFragmentManager().findFragmentById(R.id.nav_fragment); navFragment.setLayoutDelegate(new MyLayoutDelegate()); } // For a programmatic NavigationView NavigationView navigationView = new NavigationView(context); navigationView.setLayoutDelegate(new MyLayoutDelegate()); navigationView.onCreate(savedInstanceState);
-
Utwórz własny kontener układu: utwórz niestandardową grupę widoków, np.
ConstraintLayout, która będzie zawierać niestandardowe elementy interfejsu i widoki udostępniane przez Google. -
Dołącz obowiązkowe widoki: we wszystkich stanach nawigacji musisz dodać do kontenera układu logo Google (
getGoogleLogo()) i widoczny obszar (getViewport()). Jeśli nie uwzględnisz obu tych widoków, zostanie wywołany błąd czasu działaniaApiIllegalStateExceptioni aplikacja ulegnie awarii. -
Uwzględnianie stałych wymiarów: pakiet SDK określa wymiary kilku komponentów dostarczanych przez Google. Nie stosuj niestandardowych ograniczeń szerokości ani wysokości do tych elementów:
- Karta obrotu
- Karta szacowanego czasu dotarcia na miejsce
- Przychodzące prompty
- Logo Google
- Widżet prędkości
-
Dołącz układ do widoku: wywołaj
navigationView.setNavigationLayout()w wywołaniach zwrotnychonEnterNavigationReady()ionEnterActiveGuidance(), aby dołączyć kontener do hierarchii widoków mapy. -
Czyszczenie przy wyjściu ze stanu: wywołaj
navigationView.removeNavigationLayout()i usuń widoki niestandardowe w wywołaniach zwrotnychonLeave, aby zapobiec wyciekom pamięci i nakładaniu się stanów interfejsu. -
Unikaj starszych interfejsów API układu: nie wywołuj wycofanych interfejsów API opartych na boksach, takich jak
setCustomControl()lubsetHeaderEnabled(), gdy aktywny jest niestandardowy obiekt delegowany. Pakiet SDK może ignorować te połączenia starszego typu, gdy dołączony jest niestandardowy obiekt delegowany.
Stany interfejsu i komponenty Google
Gdy następuje zmiana stanu nawigacji, pakiet SDK przekazuje do wywołania zwrotnego obiektu delegowanego obiekt UiState tylko do odczytu. Ten obiekt łączy bieżące flagi konfiguracji układu z komponentami Google (np. instancjami View), które są potrzebne do renderowania ekranu.
Delegat układu zarządza hierarchią widoków w 4 stanach operacyjnych. Poniższy diagram stanów pokazuje, jak pakiet SDK przechodzi między stanami nawigacji i jakie metody wywołania zwrotnego wykonuje na delegacie:
Konkretne komponenty Google udostępniane delegatowi zależą od bieżącego etapu cyklu życia nawigacji.
Wymagane komponenty (wszystkie stany nawigacji)
Niezależnie od bieżącego etapu nawigacji musisz uwzględnić, umieścić i utrzymać widoczność tych komponentów Google w hierarchii widoków:
Logo Google (
getGoogleLogo()): ten komponent wyświetla obowiązkowe logo Map Google. Jeśli przycisk Wyśrodkuj jest włączony, logo automatycznie przekształca się w ten przycisk, gdy kierowca przewija mapę z dala od pojazdu. W związku z tym umiejscowienie logo określa, gdzie pojawi się przycisk Wyśrodkuj. Zalecamy umieszczenie logo w lewym dolnym rogu układu, aby odpowiadało standardowym oczekiwaniom dotyczącym interfejsu mapy.Widoczny obszar (
getViewport()): niewidoczny elementView, który określa granice kadrowania kamery. Umiejscowienie widocznego obszaru określa, gdzie pakiet SDK wyśrodkuje ikonę pojazdu i narysuje aktywną linię trasy. Ustaw widoczny obszar tak, aby obejmował otwarty, niezasłonięty obszar ekranu, bezpiecznie unikając nieprzezroczystych nakładek, takich jak niestandardowe plansze dolne.
komponenty gotowe do nawigacji
W stanie gotowości do nawigacji interfejs pozostaje minimalistyczny, aby skupić uwagę na mapie podstawowej. Obiekt NavigationReadyUiState zapewnia dostęp do tych funkcji:
- getNavigationReadyButtons(): lista widoków pływających działań skonfigurowanych dla mapy podstawowej (zwykle tylko przycisk kompasu). Google może dodawać lub zmieniać kolejność przycisków w przyszłych wersjach pakietu SDK, dlatego unikaj polegania na stałych indeksach list. Zamiast tego przejdź przez listę, odczytaj typ każdego przycisku, porównując
getType()zButtonKnownType.COMPASS, i wyodrębnij widok fizyczny, wywołującgetView(). Pamiętaj, że przyciski na tej liście różnią się od tych dostępnych podczas aktywnego prowadzenia.
aktywne komponenty wskazówek
Gdy rozpocznie się szczegółowa nawigacja, pakiet SDK odblokuje pełny zestaw elementów sterujących nawigacją. Obiekt ActiveGuidanceUiState zapewnia dostęp do tych komponentów Google:
getTurnCard():główny baner nagłówka wyświetlający wskazówki dotyczące nadchodzącego manewru, pomiary odległości i wskazówki dotyczące pasa ruchu. Umieść go u góry układu, aby utworzyć znajomą hierarchię nawigacji i zakotwiczyć wokół niego widoki niestandardowe.getEtaCard():baner w stopce wyświetlający szacowany czas dotarcia na miejsce, pozostały czas podróży i pozostałą odległość do miejsca docelowego. Umieść go wzdłuż dolnej krawędzi ekranu lub zintegruj jego współrzędne z niestandardowymi arkuszami zarządzania zadaniami.getTripProgressBar():pionowy pasek postępu, który pokazuje kierowcy, jaką część bieżącej trasy już pokonał. W przeciwieństwie do starszych układów, które sztywno mocowały ten element do krawędzi mapy, masz pełną swobodę zakotwiczenia go w dowolnym miejscu, np. wzdłuż bocznej krawędzi niestandardowego kontenera.getSpeedWidget(): pływający element sterujący prędkościomierzem i wyświetlanymi ograniczeniami prędkości. W zależności od ustawień interfejsu API i dostępności danych ten widok dynamicznie zmienia rozmiar między 4 stanami wizualnymi w czasie działania (nie wyświetla niczego, tylko bieżącą prędkość, tylko ograniczenie prędkości lub oba odczyty). Ponieważ widżet może zmieniać rozmiar bez powiadomienia, zawsze zakotwiczaj otaczające widoki za pomocą ograniczeń, aby układ automatycznie dostosowywał się i zapobiegał nakładaniu się elementów.
getActiveGuidanceButtons(): rozwinięta lista pływających widoków działań dla aktywnego stanu wskazówek (zwykle obejmuje zarówno kompas, jak i przycisk raportu o zdarzeniu). Podobnie jak w stanie gotowości do nawigacji, możesz znaleźć i wyodrębnić poszczególne widoki przycisków, filtrując listę wedługButtonKnownType(COMPASSlubREPORTING) i wyodrębniając widok za pomocągetView(). Możesz je następnie umieścić niezależnie lub użyć układu takiego jakAutoHidingLinearLayout, aby bezpiecznie ułożyć tablicę bez konfliktów przestrzennych.
Komponenty promptu dynamicznego:
Podpowiedzi, takie jak alerty o incydentach lub ostrzeżenia o fotoradarach, są uruchamiane niezależnie podczas aktywnego prowadzenia.
Gdy prośba będzie gotowa do wyświetlenia, pakiet SDK wywoła wywołanie zwrotne onShowPrompt() i przekaże widok newPrompt. Delegat odpowiada za płynne umieszczenie tego komunikatu nad układem (zwykle jest on zakotwiczony na dolnej krawędzi kontenera mapy).
Ponieważ przychodzący komunikat zakrywa dolną część ekranu, musisz zaktualizować układ, aby komunikat nie nakładał się na widoczny obszar, logo Google ani żadne przyciski wyrównane do dołu.
Obsługa rozmiaru ekranu i trybu szerokiego
Aby obsługiwać zmiany wymiarów mapy i orientacji ekranu, delegat układu korzysta z tych funkcji:
Zmiana rozmiaru widoku: dostosowuje układ, gdy zmienia się fizyczny rozmiar mapy.
Tryb szeroki: przełącza się na wariant układu szerokoformatowego, gdy mapa jest wystarczająco szeroka.
Reagowanie na zmianę rozmiaru widoku
Za każdym razem, gdy zmieniają się fizyczne wymiary kontenera mapy, pakiet SDK wykonuje wywołanie zwrotneonSizeChanged(). Układy podzielonego ekranu, suwaki układu i obracanie urządzenia zwykle wywołują wywołanie zwrotne onSizeChanged(). Użyj tego wywołania zwrotnego, aby wprowadzić ogólne zmiany w niestandardowym interfejsie użytkownika, które pozwolą dostosować go do różnych urządzeń. Zaimplementuj onSizeChanged(), aby zmienić pozycję elementów niestandardowych w przypadku nowego formatu obrazu, zastosować własne niestandardowe punkty przerwania szerokości lub wysokości i wykryć, kiedy zdarzenie zmiany rozmiaru przełącza tryb szeroki, aby bezpiecznie zamienić warianty układu.
Informacje o trybie szerokim
Tryb szeroki aktywuje się, gdy kontener mapy jest wystarczająco szeroki, aby wyświetlać komponenty interfejsu obok siebie.
Odczytaj wartość logiczną isWideMode() z obiektu stanu, aby zmienić położenie niestandardowych elementów interfejsu i zachować środek mapy wolny dla kierowcy. Komponenty dostarczane przez Google, takie jak karta skrętu i karta szacowanego czasu dotarcia na miejsce, automatycznie zmniejszają się i zmieniają kształt w trybie szerokim, dlatego odczytanie tej wartości logicznej zapewnia, że układ dostosuje się dokładnie w tym samym momencie, w którym aktualizują się komponenty Google.
Poniżej znajdziesz przykłady pozycjonowania komponentów układu w trybie standardowym i szerokim:
Standardowy tryb portretowy: karta skrętu znajduje się u góry ekranu, a karta szacowanego czasu dotarcia na miejsce – u dołu.
Tryb szeroki: przesuń kartę skrętu na początek ekranu, a kartę szacowanego czasu dotarcia na miejsce na koniec.
Sprawdzanie trybu szerokiego podczas zmian stanu
Jeśli układ obsługuje wariant trybu szerokiego, sprawdź wartość logiczną isWideMode() w każdym wywołaniu zwrotnym przejścia stanu, zamiast polegać tylko na onSizeChanged().
onEnterNavigationReady() i onEnterActiveGuidance(), aby obsługiwać pierwsze uruchomienia aplikacji w trybie poziomym. Takie podejście chroni układ przed zdarzeniami cyklu życia systemu Android, takimi jak domyślne ponowne tworzenie aktywności, które całkowicie pomijają wywołanie zwrotne zmiany rozmiaru. Dzięki temu w momencie rozpoczęcia nowego stanu nawigacji aktywuje się prawidłowy wariant układu standardowego lub szerokiego.
Dostosowywanie do stylu Google
Aby ułatwić dopasowanie niestandardowego interfejsu do rytmu wizualnego Google, pakiet SDK udostępnia klasę narzędziową StyleValues. Możesz odczytać te wartości w pikselach niezależnych od gęstości (dp), aby idealnie dopasować widoki do komponentów Google.
Jeśli na przykład chcesz umieścić przycisk niestandardowy w górnym rogu ekranu naprzeciwko karty skrętu, możesz wywołać funkcję StyleValues.headerTopPaddingDp() i przypisać zwróconą wartość jako górny margines przycisku. Dzięki temu przycisk niestandardowy będzie wizualnie wyrównany z górną krawędzią karty skrętu, co pozwoli zachować symetrię ekranu.
Dostępne dopełnienia i wymiary stylów:
StyleValues.headerNominalHeightDp()StyleValues.headerTopPaddingDp()StyleValues.headerFooterSidePaddingDp()StyleValues.mapControlSidePaddingDp()StyleValues.buttonMapControlSidePaddingDp()
Przykład: implementacja układu opartego na ograniczeniach
Poniższy przykład pokazuje podstawowy obiekt delegowany układu, który zarządza przejściami stanu za pomocą definicji automatycznych ConstraintLayout i ConstraintSet.
Chociaż w tym przykładzie ograniczenia widoku są tworzone w kodzie, obiekty delegowane układu mogą też rozszerzać standardowe układy XML Androida.
Kotlin
/* * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.navigationapidemo.layoutdelegate import android.content.Context import android.util.TypedValue import android.view.View import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import com.google.android.libraries.navigation.layoutcustomization.ActiveGuidanceUiState import com.google.android.libraries.navigation.layoutcustomization.AutoHidingVerticalLayout import com.google.android.libraries.navigation.layoutcustomization.NavigationLayoutDelegate import com.google.android.libraries.navigation.layoutcustomization.NavigationReadyUiState import com.google.android.libraries.navigation.layoutcustomization.NavigationUiButton.ButtonKnownType.COMPASS import com.google.android.libraries.navigation.layoutcustomization.NavigationUiParent import com.google.android.libraries.navigation.layoutcustomization.StyleValues.headerNominalHeightDp /** Kotlin equivalent of StandardUiElementsLayoutDelegate. */ class StandardUiElementsLayoutDelegateKt : NavigationLayoutDelegate() { private val layoutId = View.generateViewId() private val buttonsContainerId = View.generateViewId() private var layout: ConstraintLayout? = null private var buttonsContainer: AutoHidingVerticalLayout? = null private var navigationReadyConstraintSet: ConstraintSet? = null private var activeGuidanceConstraintSet: ConstraintSet? = null private var activeGuidanceWithPromptConstraintSet: ConstraintSet? = null private var activeGuidanceUiState: ActiveGuidanceUiState? = null override fun onEnterNavigationReady( navigationUiParent: NavigationUiParent, newState: NavigationReadyUiState, ) { val context = navigationUiParent.viewContext var currentLayout = layout if (currentLayout == null) { currentLayout = ConstraintLayout(context).apply { layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, ) id = layoutId } layout = currentLayout } removeFromParentView(newState.viewport) currentLayout.addView( newState.viewport, ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, ), ) removeFromParentView(newState.googleLogo) currentLayout.addView( newState.googleLogo, ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, ), ) var currentButtonsContainer = buttonsContainer if (currentButtonsContainer == null) { currentButtonsContainer = AutoHidingVerticalLayout(context).apply { id = buttonsContainerId } buttonsContainer = currentButtonsContainer } removeFromParentView(currentButtonsContainer) currentLayout.addView( currentButtonsContainer, ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ConstraintLayout.LayoutParams.MATCH_CONSTRAINT, ), ) for (button in newState.navigationReadyButtons) { removeFromParentView(button.view) currentButtonsContainer.addView(button.view) } if (navigationReadyConstraintSet == null) { navigationReadyConstraintSet = buildNavigationReadyConstraintSet(newState) } navigationReadyConstraintSet?.applyTo(currentLayout) navigationUiParent.removeNavigationLayout(currentLayout) navigationUiParent.setNavigationLayout(currentLayout) } private fun buildNavigationReadyConstraintSet(uiState: NavigationReadyUiState): ConstraintSet { return ConstraintSet().apply { clone(layout) connect( uiState.viewport.id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, ) connect(uiState.viewport.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP) connect(uiState.viewport.id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END) connect( uiState.viewport.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM, ) connect( uiState.googleLogo.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM, ) connect( uiState.googleLogo.id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, ) constrainButtonsToBottomEnd() } } override fun onLeaveNavigationReady( navigationUiParent: NavigationUiParent, oldState: NavigationReadyUiState, ) { buttonsContainer?.removeAllViews() layout?.removeAllViews() layout?.let { navigationUiParent.removeNavigationLayout(it) } } override fun onEnterActiveGuidance( navigationUiParent: NavigationUiParent, oldState: NavigationReadyUiState, newState: ActiveGuidanceUiState, ) { activeGuidanceUiState = newState val context = navigationUiParent.viewContext val currentLayout = checkNotNull(layout) { "layout must be initialized" } val currentButtonsContainer = checkNotNull(buttonsContainer) { "buttonsContainer must be initialized" } removeFromParentView(newState.turnCard) currentLayout.addView( newState.turnCard, ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, ), ) removeFromParentView(newState.etaCard) currentLayout.addView( newState.etaCard, ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, ), ) for (button in oldState.navigationReadyButtons) { removeFromParentView(button.view) } for (button in newState.activeGuidanceButtons) { val buttonLayoutParams = AutoHidingVerticalLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, ) if (button.type == COMPASS) { buttonLayoutParams.isHighPriority = true } removeFromParentView(button.view) currentButtonsContainer.addView(button.view, buttonLayoutParams) } if (activeGuidanceConstraintSet == null) { activeGuidanceConstraintSet = buildActiveGuidanceConstraintSet(context, newState) } activeGuidanceConstraintSet?.applyTo(currentLayout) } override fun onLeaveActiveGuidance( navigationUiParent: NavigationUiParent, oldState: ActiveGuidanceUiState, newState: NavigationReadyUiState, ) { removeFromParentView(oldState.etaCard) removeFromParentView(oldState.turnCard) buttonsContainer?.removeAllViews() val currentButtonsContainer = checkNotNull(buttonsContainer) { "buttonsContainer must be initialized" } for (button in newState.navigationReadyButtons) { currentButtonsContainer.addView( button.view, ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, ), ) } navigationReadyConstraintSet?.applyTo(layout) } private fun buildActiveGuidanceConstraintSet( context: Context, uiState: ActiveGuidanceUiState, ): ConstraintSet { return ConstraintSet().apply { clone(layout) connect(uiState.turnCard.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP) connect( uiState.turnCard.id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, ) clear(uiState.viewport.id) connect( uiState.viewport.id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, ) connect(uiState.viewport.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP) setMargin(uiState.viewport.id, ConstraintSet.TOP, dpToPx(headerNominalHeightDp(), context)) connect(uiState.viewport.id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END) connect(uiState.viewport.id, ConstraintSet.BOTTOM, uiState.etaCard.id, ConstraintSet.TOP) clear(uiState.googleLogo.id, ConstraintSet.BOTTOM) constrainLogoToTopOfEtaCard(uiState) constrainEtaCardToBottomStart(uiState) constrainButtonsToTopOfEtaCard(context, uiState) } } private fun ConstraintSet.constrainEtaCardToBottomStart(uiState: ActiveGuidanceUiState) { connect(uiState.etaCard.id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START) connect(uiState.etaCard.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM) } private fun ConstraintSet.constrainLogoToTopOfEtaCard(uiState: ActiveGuidanceUiState) { connect(uiState.googleLogo.id, ConstraintSet.BOTTOM, uiState.etaCard.id, ConstraintSet.TOP) } private fun ConstraintSet.constrainButtonsToBottomEnd() { clear(buttonsContainerId, ConstraintSet.BOTTOM) clear(buttonsContainerId, ConstraintSet.TOP) connect(buttonsContainerId, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP) connect(buttonsContainerId, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM) connect(buttonsContainerId, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END) } private fun ConstraintSet.constrainButtonsToTopOfEtaCard( context: Context, uiState: ActiveGuidanceUiState, ) { clear(buttonsContainerId, ConstraintSet.BOTTOM) clear(buttonsContainerId, ConstraintSet.TOP) connect(buttonsContainerId, ConstraintSet.BOTTOM, uiState.etaCard.id, ConstraintSet.TOP) connect(buttonsContainerId, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP) connect(buttonsContainerId, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END) setMargin(buttonsContainerId, ConstraintSet.TOP, dpToPx(headerNominalHeightDp(), context)) } override fun onShowPrompt(navigationUiParent: NavigationUiParent, newPrompt: View) { val context = navigationUiParent.viewContext layout?.addView(newPrompt) if (activeGuidanceUiState != null) { activeGuidanceWithPromptConstraintSet = buildActiveGuidanceWithPromptConstraintSet(context, newPrompt) activeGuidanceWithPromptConstraintSet?.applyTo(layout) } } override fun onChangePrompt( navigationUiParent: NavigationUiParent, oldPrompt: View, newPrompt: View, ) { val context = navigationUiParent.viewContext activeGuidanceWithPromptConstraintSet?.clear(oldPrompt.id) val currentLayout = checkNotNull(layout) { "layout must be initialized" } currentLayout.removeView(oldPrompt) currentLayout.addView(newPrompt) if (activeGuidanceUiState != null) { activeGuidanceWithPromptConstraintSet = buildActiveGuidanceWithPromptConstraintSet(context, newPrompt) activeGuidanceWithPromptConstraintSet?.applyTo(currentLayout) } } override fun onHidePrompt(navigationUiParent: NavigationUiParent, oldPrompt: View) { activeGuidanceWithPromptConstraintSet?.clear(oldPrompt.id) layout?.removeView(oldPrompt) activeGuidanceConstraintSet?.applyTo(layout) } private fun buildActiveGuidanceWithPromptConstraintSet( context: Context, prompt: View, ): ConstraintSet { return ConstraintSet().apply { clone(layout) val state = checkNotNull(activeGuidanceUiState) { "activeGuidanceUiState must be initialized" } clear(state.viewport.id) connect(state.viewport.id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START) connect(state.viewport.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP) setMargin(state.viewport.id, ConstraintSet.TOP, dpToPx(headerNominalHeightDp(), context)) connect(state.viewport.id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END) connect(state.viewport.id, ConstraintSet.BOTTOM, prompt.id, ConstraintSet.TOP) clear(state.googleLogo.id, ConstraintSet.BOTTOM) connect(state.googleLogo.id, ConstraintSet.BOTTOM, prompt.id, ConstraintSet.TOP) connect(prompt.id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START) connect(prompt.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM) } } private fun removeFromParentView(view: View?) { if (view?.parent != null) { (view.parent as ViewGroup).removeView(view) } } private fun dpToPx(dp: Int, context: Context): Int { return TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), context.resources.displayMetrics, ) .toInt() } }
Java
/* * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.navigationapidemo.layoutdelegate; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.MATCH_CONSTRAINT; import static com.google.android.libraries.navigation.layoutcustomization.NavigationUiButton.ButtonKnownType.COMPASS; import android.content.Context; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; import com.google.android.libraries.navigation.layoutcustomization.ActiveGuidanceUiState; import com.google.android.libraries.navigation.layoutcustomization.AutoHidingVerticalLayout; import com.google.android.libraries.navigation.layoutcustomization.NavigationLayoutDelegate; import com.google.android.libraries.navigation.layoutcustomization.NavigationReadyUiState; import com.google.android.libraries.navigation.layoutcustomization.NavigationUiButton; import com.google.android.libraries.navigation.layoutcustomization.NavigationUiParent; import com.google.android.libraries.navigation.layoutcustomization.StyleValues; /** * A sample implementation of {@link NavigationLayoutDelegate} demonstrating a basic, * portrait-optimized layout using {@link ConstraintLayout}. * * <p><b>Understanding the Layout Delegate State Machine:</b> Navigation SDK transitions through * distinct states, each calling corresponding lifecycle methods on this delegate: * * <ul> * <li><b>Navigation Ready:</b> Initiated by {@link #onEnterNavigationReady}. We initialize the * layout here and add non-guidance views, then pass it to {@link NavigationUiParent} as the * navigation layout. * <li><b>Active Guidance (Turn-by-Turn Mode):</b> Initiated by {@link #onEnterActiveGuidance}. We * set up the layout for Active Guidance, adding elements such as the turn card and ETA card. * <li><b>Prompts:</b> Prompts (e.g., incident alerts) may be triggered during Active Guidance * mode and can be added to the layout via {@link #onShowPrompt}. * </ul> * * This class caches its {@link ConstraintSet}s to ensure smooth transitions without needing to * recreate or inflate layouts continuously. */ public class StandardUiElementsLayoutDelegate extends NavigationLayoutDelegate { private final int layoutId; private final int buttonsContainerId; private ConstraintLayout layout; private AutoHidingVerticalLayout buttonsContainer; // We cache our ConstraintSet definitions to avoid cloning or rebuilding // constraint configurations programmatically on every transition. This optimization // keeps UI state switches (such as entering active guidance or popping up prompts) highly // performant. private ConstraintSet navigationReadyConstraintSet; private ConstraintSet activeGuidanceConstraintSet; private ConstraintSet activeGuidanceWithPromptConstraintSet; private ActiveGuidanceUiState activeGuidanceUiState; public StandardUiElementsLayoutDelegate() { layoutId = View.generateViewId(); buttonsContainerId = View.generateViewId(); } @Override public void onEnterNavigationReady( NavigationUiParent navigationUiParent, NavigationReadyUiState newState) { Context context = navigationUiParent.getViewContext(); // Implementation Tip: For simplicity, this sample instantiates views and constraints // programmatically. In a production application, you can safely inflate standard XML // layout templates to build your layout hierarchies and define base UI constraints. // Create the root layout if (layout == null) { layout = new ConstraintLayout(context); LayoutParams layoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT); layout.setLayoutParams(layoutParams); layout.setId(layoutId); } // Add the Viewport (REQUIRED): // The viewport is an invisible bounding box used by Nav SDK to frame the vehicle // chevron and the upcoming route line. We want to position this view such that it avoids // being obscured by fully-opaque UI elements (like the turn card or the ETA card). removeFromParentView(newState.getViewport()); LayoutParams viewportLayoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT); layout.addView(newState.getViewport(), viewportLayoutParams); // Add the Google Logo / Re-center Button (REQUIRED): // This view displays the Google logo during guidance and may transition into a // "Re-center" button if the user scrolls away from the vehicle chevron. It must // be added to the view hierarchy in all states. removeFromParentView(newState.getGoogleLogo()); LayoutParams googleLogoLayoutParams = new LayoutParams(MATCH_PARENT, WRAP_CONTENT); layout.addView(newState.getGoogleLogo(), googleLogoLayoutParams); // Add the container for UI buttons if (buttonsContainer == null) { // We use AutoHidingVerticalLayout to create an adaptive vertical button container that // automatically hides or shows child views based on available screen height. buttonsContainer = new AutoHidingVerticalLayout(context); buttonsContainer.setId(buttonsContainerId); } removeFromParentView(buttonsContainer); LayoutParams buttonsContainerLayoutParams = new LayoutParams(WRAP_CONTENT, MATCH_CONSTRAINT); layout.addView(buttonsContainer, buttonsContainerLayoutParams); // Add UI buttons to the container for (NavigationUiButton button : newState.getNavigationReadyButtons()) { removeFromParentView(button.getView()); buttonsContainer.addView(button.getView()); } // Build constraint set for Navigation Ready state if (navigationReadyConstraintSet == null) { navigationReadyConstraintSet = buildNavigationReadyConstraintSet(newState); } // Apply the constraints navigationReadyConstraintSet.applyTo(layout); // Set the layout in NavigationUiParent navigationUiParent.removeNavigationLayout(layout); navigationUiParent.setNavigationLayout(layout); } private ConstraintSet buildNavigationReadyConstraintSet(NavigationReadyUiState uiState) { ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(layout); // Constrain viewport to the edges of its parent constraintSet.connect( uiState.getViewport().getId(), ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START); constraintSet.connect( uiState.getViewport().getId(), ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP); constraintSet.connect( uiState.getViewport().getId(), ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END); constraintSet.connect( uiState.getViewport().getId(), ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM); // Constrain the logo to the bottom start corner constraintSet.connect( uiState.getGoogleLogo().getId(), ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM); constraintSet.connect( uiState.getGoogleLogo().getId(), ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START); constrainButtonsToBottomEnd(constraintSet); return constraintSet; } @Override public void onLeaveNavigationReady( NavigationUiParent navigationUiParent, NavigationReadyUiState oldState) { buttonsContainer.removeAllViews(); layout.removeAllViews(); navigationUiParent.removeNavigationLayout(layout); } @Override public void onEnterActiveGuidance( NavigationUiParent navigationUiParent, NavigationReadyUiState oldState, ActiveGuidanceUiState newState) { activeGuidanceUiState = newState; Context context = navigationUiParent.getViewContext(); // Sizing Guideline: The turn card and ETA card are internally configured to adapt and size // themselves dynamically based on the layout width (non-wideMode vs. wideMode). Forcing fixed // widths or heights on these elements via layouts is unsupported. Always use WRAP_CONTENT to // let the elements determine their optimal proportions. // Add the turn card removeFromParentView(newState.getTurnCard()); LayoutParams turnCardLayoutParams = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT); layout.addView(newState.getTurnCard(), turnCardLayoutParams); // Add the ETA card removeFromParentView(newState.getEtaCard()); LayoutParams etaCardLayoutParams = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT); layout.addView(newState.getEtaCard(), etaCardLayoutParams); // Remove the Navigation Ready UI buttons for (NavigationUiButton button : oldState.getNavigationReadyButtons()) { removeFromParentView(button.getView()); } // By adding all buttons to the AutoHidingVerticalLayout, we can easily incorporate the latest // set of buttons when upgrading without any code changes required for (NavigationUiButton button : newState.getActiveGuidanceButtons()) { AutoHidingVerticalLayout.LayoutParams buttonLayoutParams = new AutoHidingVerticalLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); // Mark critical buttons (such as the compass) as high priority so they are the last to be // hidden by AutoHidingVerticalLayout when layout space is limited. if (button.getType() == COMPASS) { buttonLayoutParams.isHighPriority = true; } removeFromParentView(button.getView()); buttonsContainer.addView(button.getView(), buttonLayoutParams); } // Build constraint set for Active Guidance state if (activeGuidanceConstraintSet == null) { activeGuidanceConstraintSet = buildActiveGuidanceConstraintSet(context, newState); } // Apply the constraints activeGuidanceConstraintSet.applyTo(layout); } @Override public void onLeaveActiveGuidance( NavigationUiParent navigationUiParent, ActiveGuidanceUiState oldState, NavigationReadyUiState newState) { // Remove Active Guidance UI elements removeFromParentView(oldState.getEtaCard()); removeFromParentView(oldState.getTurnCard()); buttonsContainer.removeAllViews(); // Add Navigation Ready UI buttons for (NavigationUiButton button : newState.getNavigationReadyButtons()) { LayoutParams buttonLayoutParams = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT); buttonsContainer.addView(button.getView(), buttonLayoutParams); } navigationReadyConstraintSet.applyTo(layout); } private ConstraintSet buildActiveGuidanceConstraintSet( Context context, ActiveGuidanceUiState uiState) { ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(layout); // Constrain turn card to top start corner constraintSet.connect( uiState.getTurnCard().getId(), ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP); constraintSet.connect( uiState.getTurnCard().getId(), ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START); // Constrain viewport to top of ETA card constraintSet.clear(uiState.getViewport().getId()); constraintSet.connect( uiState.getViewport().getId(), ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START); constraintSet.connect( uiState.getViewport().getId(), ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP); // Instead of constraining the viewport's top directly to the bottom of the turn card // (which varies in height and would trigger jumpy camera framing updates), we use a fixed // nominal height to estimate the height of the turncard. constraintSet.setMargin( uiState.getViewport().getId(), ConstraintSet.TOP, dpToPx(StyleValues.headerNominalHeightDp(), context)); constraintSet.connect( uiState.getViewport().getId(), ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END); constraintSet.connect( uiState.getViewport().getId(), ConstraintSet.BOTTOM, uiState.getEtaCard().getId(), ConstraintSet.TOP); constraintSet.clear(uiState.getGoogleLogo().getId(), ConstraintSet.BOTTOM); constrainLogoToTopOfEtaCard(uiState, constraintSet); constrainEtaCardToBottomStart(uiState, constraintSet); constrainButtonsToTopOfEtaCard(context, uiState, constraintSet); return constraintSet; } private static void constrainEtaCardToBottomStart( ActiveGuidanceUiState uiState, ConstraintSet constraintSet) { constraintSet.connect( uiState.getEtaCard().getId(), ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START); constraintSet.connect( uiState.getEtaCard().getId(), ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM); } private static void constrainLogoToTopOfEtaCard( ActiveGuidanceUiState uiState, ConstraintSet constraintSet) { constraintSet.connect( uiState.getGoogleLogo().getId(), ConstraintSet.BOTTOM, uiState.getEtaCard().getId(), ConstraintSet.TOP); } private void constrainButtonsToBottomEnd(ConstraintSet constraintSet) { constraintSet.clear(buttonsContainerId, ConstraintSet.BOTTOM); constraintSet.clear(buttonsContainerId, ConstraintSet.TOP); constraintSet.connect( buttonsContainerId, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP); constraintSet.connect( buttonsContainerId, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM); constraintSet.connect( buttonsContainerId, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END); } private void constrainButtonsToTopOfEtaCard( Context context, ActiveGuidanceUiState uiState, ConstraintSet constraintSet) { constraintSet.clear(buttonsContainerId, ConstraintSet.BOTTOM); constraintSet.clear(buttonsContainerId, ConstraintSet.TOP); constraintSet.connect( buttonsContainerId, ConstraintSet.BOTTOM, uiState.getEtaCard().getId(), ConstraintSet.TOP); constraintSet.connect( buttonsContainerId, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP); constraintSet.connect( buttonsContainerId, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END); constraintSet.setMargin( buttonsContainerId, ConstraintSet.TOP, dpToPx(StyleValues.headerNominalHeightDp(), context)); } @Override public void onShowPrompt(NavigationUiParent navigationUiParent, View newPrompt) { Context context = navigationUiParent.getViewContext(); layout.addView(newPrompt); // When a prompt is displayed at the bottom of the screen, we update our active constraints so // that the invisible Viewport sits entirely above the prompt. This automatically forces the // Nav SDK camera to adjust its zoom and framing so that the route chevron is always visible to // the driver. activeGuidanceWithPromptConstraintSet = buildActiveGuidanceWithPromptConstraintSet(context, newPrompt); activeGuidanceWithPromptConstraintSet.applyTo(layout); } @Override public void onChangePrompt( NavigationUiParent navigationUiParent, View oldPrompt, View newPrompt) { Context context = navigationUiParent.getViewContext(); activeGuidanceWithPromptConstraintSet.clear(oldPrompt.getId()); layout.removeView(oldPrompt); layout.addView(newPrompt); // When a prompt is displayed at the bottom of the screen, we update our active constraints so // that the invisible Viewport sits entirely above the prompt. This automatically forces the // Nav SDK camera to adjust its zoom and framing so that the route chevron is always visible to // the driver. activeGuidanceWithPromptConstraintSet = buildActiveGuidanceWithPromptConstraintSet(context, newPrompt); activeGuidanceWithPromptConstraintSet.applyTo(layout); } @Override public void onHidePrompt(NavigationUiParent navigationUiParent, View oldPrompt) { activeGuidanceWithPromptConstraintSet.clear(oldPrompt.getId()); layout.removeView(oldPrompt); activeGuidanceConstraintSet.applyTo(layout); } private ConstraintSet buildActiveGuidanceWithPromptConstraintSet(Context context, View prompt) { ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(layout); // Constrain viewport to top of prompt constraintSet.clear(activeGuidanceUiState.getViewport().getId()); constraintSet.connect( activeGuidanceUiState.getViewport().getId(), ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START); constraintSet.connect( activeGuidanceUiState.getViewport().getId(), ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP); constraintSet.setMargin( activeGuidanceUiState.getViewport().getId(), ConstraintSet.TOP, dpToPx(StyleValues.headerNominalHeightDp(), context)); constraintSet.connect( activeGuidanceUiState.getViewport().getId(), ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END); constraintSet.connect( activeGuidanceUiState.getViewport().getId(), ConstraintSet.BOTTOM, prompt.getId(), ConstraintSet.TOP); // Constrain prompt to bottom start corner constraintSet.connect( prompt.getId(), ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START); constraintSet.connect( prompt.getId(), ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM); return constraintSet; } private void removeFromParentView(View view) { if (view != null && view.getParent() != null) { ((ViewGroup) view.getParent()).removeView(view); } } private static int dpToPx(int dp, Context context) { return (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()); } }
Aby wyświetlić pełny przykładowy kod lub zapoznać się z dodatkowymi implementacjami delegatów układu, zobacz przykłady delegatów układu na GitHubie. Możesz też wyświetlić poszczególne pliki źródłowe tego przykładu na GitHubie: