
Hem Google tarafından sağlanan görünümler hem de kendi özel işletme görünümleriniz için görünüm hiyerarşisini ve ekran yerleşimini kontrol etmek üzere düzen özelleştirmeyi kullanın.
Önceden tanımlanmış üstbilgi veya altbilgi alanlarına özel görünümler eklemek yerine, tüm ekranın düzenini yönetmek için bir düzen temsilcisi uygulayın. Navigasyon durumu geçişleri sırasında, düzen temsilciniz Google tarafından sağlanan bileşenleri (ör. dönüş kartı, TVS kartı ve düğmeler) alır. Bu öğeleri ConstraintLayout, CoordinatorLayout veya LinearLayout gibi standart Android düzen sistemlerini kullanarak kendi özel içeriğinizle birlikte konumlandırın.
Sipariş durumu veya teslim alma talimatları gibi özel işletme bilgilerini ekranda tam olarak konumlandırmak ve görünümlerinizin çakışmasını önlemek için bu çerçeveyi kullanın.
Düzen özelleştirmenin işleyiş şekli
Düzen özelleştirme, yetki verme tasarım kalıbını kullanır. SDK, kullanıcı arayüzü bileşenlerini ekranda otomatik olarak çizmek veya konumlandırmak yerine doğrudan yazdığınız özel bir sınıfa (düzen temsilcisi) iletir.
Düzeninizi özelleştirmek için soyutNavigationLayoutDelegate sınıfını genişleten bir sınıf oluşturun ve NavigationView veya SupportNavigationFragment öğenize bir örnek atayın. Navigasyon durumu her değiştiğinde (ör. temel haritadan etkin adım adım rehberliğe geçiş) SDK, temsilcinizdeki geri çağırma yöntemlerini yürütür ve söz konusu durum için kullanılabilen kullanıcı arayüzü bileşenlerini sağlar.
Bu çerçeve, sorunsuz ve entegre bir kullanıcı deneyimi sunmak için uygulamanız ile SDK arasında net bir sorumluluk ayrımı oluşturur. Aşağıdakileri özelleştirmek için düzen temsilcisini kullanın:
- Görünüm hiyerarşisini oluşturma: Her kullanıcı arayüzü durumu için ekrana tam olarak hangi Google bileşenlerinin ve özel işletme görünümlerinin ekleneceğini seçin.
- Her öğeyi konumlandırma: Ekran sabitleyicileri, kenar boşluklarını ve düzen yerleşimini hassas bir şekilde ayarlayın. Google bileşenleri kendi iç boyutlarını hesapladığından Google bileşenlerine özel genişlik veya yükseklik kısıtlamaları uygulamaktan kaçının.
- Temel haritayı çerçeveleme: Görüntü alanı bileşeninin koordinatlarını kullanarak harita kamerasının görünür sınırlarını tanımlayın.
- Ekranı katmanlara ayırma: Özel görünümlerin Google'ın yerleşik kontrollerinin üstünde, altında veya yanında yer alıp almayacağına karar verin.
Aşağıdaki Google bileşenlerini ise düzen temsilcisiyle özelleştiremezsiniz:
- Bileşen boyutları: Google tarafından sağlanan bileşenlerin boyutu ve iç boyutları. SDK tarafından otomatik olarak hesaplanır.
- Tetikleme koşulları: Dinamik uyarıların veya istemlerin gerçek zamanlı rota verilerine göre ne zaman gösterileceğini belirler.
Uygulama ilkeleri
Düzen temsilcisi yazarken düzen hatalarını veya çalışma zamanı kilitlenmelerini önlemek için aşağıdaki kuralları göz önünde bulundurun:
- Varsayılan olarak boş bir ekran gösterilir: Google tarafından sağlanan bileşenler yalnızca temsilciniz bunları görünüm hiyerarşisine açıkça ekleyip konumlandırdığında görünür.
- Eski API'ler desteklenmez: Eski alan tabanlı düzen modelini kontrol eden özellikler ve yöntemler desteklenmez ve özel bir temsilci etkin olduğunda beklendiği gibi davranmayabilir.
- Dahili görünüm yapılarını değiştirmeyin:
Google tarafından sağlanan bileşenlerin (ör. dönüş kartı veya TVS kartı) görünüm hiyerarşilerini incelemek veya değiştirmek için
findViewById()gibi yöntemler kullanmayın. Bu dahili görünüm hiyerarşileri, temel uygulama ayrıntıları olduğundan SDK sürümlerine göre değişiklik gösterebilir. Bunları değiştirmek, gelecekteki SDK güncellemeleri sırasında düzeninizin bozulmasına neden olabilir.
Eski API uyumluluğu
Özel düzen temsilcisi kullanırken güvenilir düzen davranışı için, desteğinin sonlandırılması planlanan aşağıdaki eski yuvaya dayalı API'leri kullanmaktan kaçının. Mevcut bir uygulamayı taşımak için bu API'lerin kullanımını özel düzen temsilcinizdeki kodla değiştirin:
Eski alan tabanlı API'leri göster
| Eski API | Layout Delegate replacement |
|---|---|
setCustomControl(View, CustomControlPosition) |
Görünümlerinizi doğrudan ConstraintLayout veya başka bir görünüm grubuna ekleyin. |
removeCustomControl(View) |
Görünümleri doğrudan görünüm hiyerarşinizden kaldırın. |
setEtaCardEnabled(boolean) |
onEnterActiveGuidance dilinde etaCard görünümünü okuyun. |
setHeaderEnabled(boolean) |
onEnterActiveGuidance dilinde turnCard görünümünü okuyun. |
setReportIncidentButtonEnabled(boolean) |
getActiveGuidanceButtons() uygulamasında REPORTING düğmesini bulun. |
setTripProgressBarEnabled(boolean) |
onEnterActiveGuidance dilinde tripProgressBar görünümünü okuyun. |
addOnNavigationUiChangedListener(...) |
NavigationLayoutDelegate durum geçişi geri çağırmalarınızı kullanın. |
removeOnNavigationUiChangedListener(...) |
Düzen durumu geçişlerini doğrudan yetki verilmiş kullanıcıyı kullanarak yönetin. |
addPromptVisibilityChangedListener(...) |
NavigationLayoutDelegate istem geri çağırmalarınızı (ör. onShowPrompt()) kullanın. |
removePromptVisibilityChangedListener(...) |
İstem görüntüleme mantığını doğrudan yetki verilmiş kullanıcıyı kullanarak yönetin. |
setCompassEnabled(boolean) |
getNavigationReadyButtons() veya getActiveGuidanceButtons() uygulamasında pusulayı bulun. |
Essentials kontrol listesi
Düzen temsilcinizi başarıyla uygulamak için aşağıdaki temel adımları ve şartları uygulayın:
-
Temsilciyi kullanıcı arayüzünü oluşturmadan önce ilk kullanıma hazırlayın: SDK, navigasyon kullanıcı arayüzünü ilk kullanıma hazırlamadan önce
setLayoutDelegate()yöntemini çağırın. Uygulama yapınız için tam kurulum zamanlamasını doğrulamak üzere aşağıdaki kod uygulama örneklerine bakın. Kullanıcı arayüzü oluşturulduktan sonra temsilciyi başlatmakApiIllegalStateExceptionhatasını tetikler.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);
-
Kendi düzen kapsayıcınızı oluşturma: Özel kullanıcı arayüzü öğelerinizi ve Google tarafından sağlanan görünümleri barındırmak için özel bir görünüm grubu (ör.
ConstraintLayout) oluşturun. -
Zorunlu görünümleri ekleyin: Tüm navigasyon durumlarında Google logosunu (
getGoogleLogo()) ve görüntü alanını (getViewport()) düzen kapsayıcınıza eklemeniz gerekir. Bu görünümlerin her ikisinin de eklenmemesi, çalışma zamanındaApiIllegalStateExceptiontetikler ve uygulamanın kilitlenmesine neden olur. -
Sabit boyutlara uyma: SDK, Google tarafından sağlanan çeşitli bileşenlerin boyutlarını belirler. Aşağıdaki öğelere özel genişlik veya yükseklik kısıtlamaları uygulamayın:
- Kartı çevir
- TVS kartı
- Gelen istemler
- Google logosu
- Hız widget'ı
-
Düzeninizi görünüme ekleyin: Kapsayıcınızı harita görünümü hiyerarşisine eklemek için
onEnterNavigationReady()veonEnterActiveGuidance()geri çağırmalarınızdanavigationView.setNavigationLayout()yöntemini çağırın. -
Durumdan çıkışta temizleme: Bellek sızıntılarını ve kullanıcı arayüzü durumlarının çakışmasını önlemek için
navigationView.removeNavigationLayout()yöntemini çağırın veonLeavegeri çağırmalarınızdaki özel görünümlerinizi kaldırın. -
Eski düzen API'lerinden kaçının: Özel bir temsilci etkinken
setCustomControl()veyasetHeaderEnabled()gibi desteği sonlandırılmış alan tabanlı API'leri çağırmayın. SDK, özel bir temsilci eklendiğinde bu eski görüşme özelliği çağrılarını yoksayabilir.
Kullanıcı arayüzü durumları ve Google bileşenleri
Bir navigasyon durumu geçişi gerçekleştiğinde SDK, temsilci geri çağırma işlevinize salt okunur bir UiState nesnesi iletir. Bu nesne, mevcut düzen yapılandırma işaretlerini ekranı oluşturmak için gereken Google bileşenleriyle (ör. View örnekleri) birleştirir.
Düzen temsilcisi, görünüm hiyerarşisini dört çalışma durumunda yönetir. Aşağıdaki durum diyagramında, SDK'nın navigasyon durumları arasında nasıl geçiş yaptığı ve temsilcinizde hangi geri çağırma yöntemlerini yürüttüğü gösterilmektedir:
Temsilcinize sağlanan belirli Google bileşenleri, navigasyon yaşam döngüsünün mevcut aşamasına bağlıdır.
Gerekli bileşenler (tüm gezinme durumları)
Mevcut gezinme aşamasından bağımsız olarak, aşağıdaki Google bileşenlerini görünüm hiyerarşinize eklemeniz, konumlandırmanız ve görünür tutmanız gerekir:
Google logosu (
getGoogleLogo()): Bu bileşen, zorunlu Google Haritalar logosunu gösterir. Yeniden ortala düğmesi etkinleştirilirse sürücü haritayı aracından uzaklaştırdığında logo otomatik olarak bu düğmeye dönüşür. Bu nedenle, logonun yerleşimi Yeniden ortala düğmesinin nerede görüneceğini belirler. Standart harita kullanıcı arayüzü beklentilerini karşılamak için logoyu düzeninizin alt-başlangıç (sol alt) köşesine yerleştirmeniz önerilir.Görüntü alanı (
getViewport()): Kameranın kadraj sınırlarını tanımlayan görünmez birView. Görüntü alanını yerleştirme şekliniz, SDK'nın araç işaretini tam olarak nerede ortalayacağını ve etkin rota çizgisini nerede çizeceğini belirler. Görüntü alanını, ekranınızın açık ve engellenmemiş alanını kaplayacak şekilde konumlandırın. Böylece, özel alt sayfalar gibi opak yer paylaşımlarından güvenli bir şekilde kaçınabilirsiniz.
Gezinmeye hazır bileşenler
Navigasyona hazır durumdayken kullanıcı arayüzü, temel haritaya odaklanmayı sürdürmek için minimum düzeyde kalır. NavigationReadyUiState nesneniz aşağıdakilere erişim sağlar:
- getNavigationReadyButtons(): Temel harita için yapılandırılmış kayan işlem görünümlerinin listesi (genellikle yalnızca Pusula düğmesi). Google, gelecekteki SDK sürümlerinde düğme ekleyebileceği veya düğmelerin sırasını değiştirebileceği için sabit liste dizinlerine güvenmeyin. Bunun yerine, listede gezinerek
getType()ileButtonKnownType.COMPASSkarşılaştırması yapıp her düğmenin türünü okuyun vegetView()çağrısı yaparak fiziksel görünümü çıkarın. Bu listedeki düğmeler, aktif rehberlik sırasında kullanılabilen düğmelerden farklıdır.
Etkin rehberlik bileşenleri
Adım adım rehberlik başladığında SDK, navigasyon kontrollerinin tamamını etkinleştirir. ActiveGuidanceUiState nesneniz aşağıdaki Google bileşenlerine erişim sağlar:
getTurnCard(): Yaklaşan manevra talimatlarını, mesafe ölçümlerini ve şerit kılavuzunu gösteren birincil başlık banner'ı. Tanıdık bir gezinme hiyerarşisi oluşturmak ve özel görünümlerinizi bu hiyerarşiye göre sabitlemek için bu çubuğu düzeninizin üst kısmına yerleştirin.getEtaCard(): Tahmini varış zamanını, kalan yolculuk süresini ve varış noktasına kalan mesafeyi gösteren altbilgi banner'ı. Ekranınızın alt kenarına yerleştirebilir veya koordinatlarını özel görev yönetimi sayfalarınıza entegre edebilirsiniz.getTripProgressBar(): Sürücüye mevcut rota boyunca ne kadar yol kat ettiğini gösteren dikey bir ilerleme çubuğu. Bunu harita kenarına sabitleyen eski düzenlerin aksine, istediğiniz yere sabitleme konusunda tamamen özgürsünüz. Örneğin, özel kapsayıcınızın yan kenarına sabitleyebilirsiniz.getSpeedWidget(): Hız göstergesi ve yayınlanan hız sınırları için kayan bir kontrol. API ayarlarınıza ve veri kullanılabilirliğine bağlı olarak bu görünüm, çalışma zamanında dört görsel durum arasında dinamik olarak yeniden boyutlandırılır (hiçbir şey gösterilmez, yalnızca mevcut hız gösterilir, yalnızca hız sınırı gösterilir veya her iki değer de gösterilir). Widget, önceden haber verilmeksizin bu boyutlar arasında geçiş yapabildiğinden, düzeninizin otomatik olarak uyum sağlayarak alan çakışmalarını önlemesi için her zaman kısıtlamaları kullanarak çevreleyen görünümleri sabitleyin.
getActiveGuidanceButtons(): Etkin rehberlik durumu için kayan işlem görünümlerinin genişletilmiş listesi (genellikle hem pusulayı hem de olay raporlama düğmesini içerir). Gezinmeye hazır durumda olduğu gibi, listeyiButtonKnownType(COMPASSveyaREPORTING) ile filtreleyip görünümügetView()ile ayıklayarak bağımsız düğme görünümlerini bulabilir ve ayıklayabilirsiniz. Ardından bunları bağımsız olarak konumlandırabilir veyaAutoHidingLinearLayoutgibi bir düzen kullanarak diziyi mekansal çakışmalar olmadan güvenli bir şekilde yığınlayabilirsiniz.
Dinamik istem bileşeni
Olay uyarıları veya güvenlik kamerası uyarıları gibi istemler, aktif rehberlik sırasında bağımsız olarak tetiklenir.
Bir istem gösterilmeye hazır olduğunda SDK, temsilcinizin onShowPrompt() geri çağırma işlevini çağırır ve bir newPrompt görünümü iletir. Temsilciniz, bu istemi düzeninizin üzerine sorunsuz bir şekilde yerleştirmekten (genellikle harita kapsayıcınızın alt kenarına sabitlenir) sorumludur.
Gelen istem, ekranın alt kısmını kapladığından istemin görüntü alanıyla, Google logosuyla veya altta hizalanmış düğmelerle çakışmasını önlemek için düzeninizi güncellemeniz gerekir.
Ekran boyutunu ve geniş modu yönetme
Harita boyutlarındaki ve ekran yönündeki değişiklikleri işlemek için düzen temsilcisi aşağıdaki özellikleri kullanır:
Görünümü yeniden boyutlandırma: Haritanın fiziksel boyutu her değiştiğinde düzeninizi ayarlar.
Geniş mod: Harita yeterince geniş olduğunda geniş formatlı bir düzen varyantına geçer.
Görünümün yeniden boyutlandırılmasına yanıt verme
Harita kapsayıcısının fiziksel boyutları her değiştiğinde SDK,onSizeChanged() geri çağırma işlevini yürütür. Bölünmüş ekran düzenleri, düzen kaydırıcıları ve cihazın döndürülmesi genellikle onSizeChanged() geri çağırma işlevini tetikler. Özel kullanıcı arayüzünüzde genel duyarlı ayarlamalar yapmak için bu geri çağırma işlevini kullanın. Yeni bir ekran en boy oranı için özel öğeleri yeniden konumlandırmak, kendi özel genişlik veya yükseklik kesme noktalarınızı uygulamak ve yeniden boyutlandırma etkinliğinin geniş modu ne zaman açıp kapattığını algılayarak düzen varyantlarını güvenli bir şekilde değiştirmek için onSizeChanged() uygulayın.
Geniş modu anlama
Geniş mod, harita kapsayıcısı kullanıcı arayüzü bileşenlerini yan yana gösterecek kadar geniş olduğunda etkinleşir.
Özel kullanıcı arayüzü öğelerinizi yeniden konumlandırmak ve haritanın ortasını sürücü için boş tutmak amacıyla durum nesnesinden isWideMode() boole değerini okuyun. Google tarafından sağlanan dönüş kartı ve TVS kartı gibi bileşenler geniş modda otomatik olarak küçülüp yeniden şekillendirildiğinden bu boole değerini okumak, düzeninizin Google bileşenlerinin güncellendiği anda uyarlanmasını sağlar.
Düzen bileşenlerinizi standart ve geniş modlarda konumlandırmak için aşağıdaki örnekleri inceleyin:
Standart portre modu: Dönüş kartını ekranın üst kısmına, TVS kartını ise alt kısmına yerleştirin.
Geniş mod: Dönüş kartını ekranın başlangıç tarafına, TVS kartını ise bitiş tarafına taşıyın.
Durum geçişleri sırasında geniş mod kontrol ediliyor
Düzeniniz geniş mod varyasyonunu destekliyorsa yalnızca onSizeChanged() değerine güvenmek yerine her durum geçişi geri çağırmasındaki isWideMode() boole değerini değerlendirin.
onEnterNavigationReady() ve onEnterActiveGuidance() gibi geri çağırmalar sırasında durum nesnesini kontrol edin. Bu yaklaşım, düzeninizi Android sistem yaşam döngüsü etkinliklerine (ör. varsayılan Etkinlik yeniden oluşturma) karşı korur. Bu etkinlikler, yeniden boyutlandırma geri çağırmasını tamamen atlar ve yeni bir gezinme durumu başlar başlamaz doğru standart veya geniş düzen varyantının etkinleştirilmesini sağlar.
Google stiline uyarlama
Özel kullanıcı arayüzünüzün Google'ın görsel ritmine uymasına yardımcı olmak için SDK, StyleValues yardımcı sınıfını sağlar. Görünümlerinizi Google bileşenleriyle mükemmel şekilde hizalamak için bu değerleri yoğunluktan bağımsız piksel (dp) cinsinden okuyabilirsiniz.
Örneğin, ekranın üst köşesinde, dönüş kartının karşısına özel bir düğme yerleştirmek istiyorsanız StyleValues.headerTopPaddingDp() yöntemini çağırabilir ve döndürülen değeri düğmenizin üst kenar boşluğu olarak atayabilirsiniz. Böylece özel düğmeniz, dönüş kartının üst kenarıyla görsel olarak hizalanır ve ekran simetrisi korunur.
Kullanılabilir stil dolguları ve ölçümleri şunlardır:
StyleValues.headerNominalHeightDp()StyleValues.headerTopPaddingDp()StyleValues.headerFooterSidePaddingDp()StyleValues.mapControlSidePaddingDp()StyleValues.buttonMapControlSidePaddingDp()
Örnek: Kısıtlamaya dayalı düzen uygulaması
Aşağıdaki örnekte, programatik ConstraintLayout ve ConstraintSet tanımlarını kullanarak durum geçişlerini yöneten temel bir düzen temsilcisi gösterilmektedir.
Bu örnekte görünüm kısıtlamaları kodda oluşturulsa da düzen temsilcileri standart Android XML düzenlerini de şişirebilir.
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()); } }
Örnek kodun tamamını görüntülemek veya diğer düzen temsilcisi uygulamalarını incelemek için GitHub'daki düzen temsilcisi örneklerine bakın. Bu örneğin kaynak dosyalarını GitHub'da ayrı ayrı da görüntüleyebilirsiniz: