
लेआउट को पसंद के मुताबिक बनाने की सुविधा का इस्तेमाल करके, Google की ओर से उपलब्ध कराए गए व्यू और कारोबार के लिए बनाए गए कस्टम व्यू, दोनों के लिए व्यू हाइरार्की और स्क्रीन प्लेसमेंट को कंट्रोल किया जा सकता है.
हेडर या फ़ुटर के पहले से तय किए गए स्लॉट में कस्टम व्यू जोड़ने के बजाय, पूरी स्क्रीन के लिए लेआउट मैनेज करने के लिए, लेआउट डेलिगेट लागू करें. नेविगेशन की स्थिति में बदलाव के दौरान, आपके लेआउट डेलिगेट को Google की ओर से उपलब्ध कराए गए कॉम्पोनेंट मिलते हैं. जैसे, टर्न कार्ड, ईटीए कार्ड, और बटन. ConstraintLayout, CoordinatorLayout या LinearLayout जैसे स्टैंडर्ड Android लेआउट सिस्टम का इस्तेमाल करके, इन एलिमेंट को अपने कस्टम कॉन्टेंट के साथ रखें.
इस फ़्रेमवर्क का इस्तेमाल करके, कारोबार की कस्टम जानकारी को स्क्रीन पर सटीक तरीके से रखें. जैसे, ऑर्डर की स्थिति या पिकअप के निर्देश. साथ ही, यह भी पक्का करें कि आपके व्यू एक-दूसरे पर ओवरलैप न करें.
लेआउट को पसंद के मुताबिक बनाने की सुविधा कैसे काम करती है
लेआउट को पसंद के मुताबिक बनाने की सुविधा, डेलिगेशन डिज़ाइन पैटर्न का इस्तेमाल करती है. स्क्रीन पर यूज़र इंटरफ़ेस (यूआई) के कॉम्पोनेंट को अपने-आप ड्रॉ या पोज़िशन करने के बजाय, एसडीके उन्हें सीधे आपके लिखे गए कस्टम क्लास को पास करता है. इसे लेआउट डेलिगेट कहा जाता है.
अपने लेआउट को पसंद के मुताबिक बनाने के लिए, एक ऐसा क्लास बनाएं जोNavigationLayoutDelegate के ऐब्स्ट्रैक्ट क्लास को एक्सटेंड करता हो. इसके बाद, NavigationView या SupportNavigationFragment को कोई इंस्टेंस असाइन करें. नेविगेशन की स्थिति में बदलाव होने पर, जैसे कि बुनियादी मैप से सक्रिय मोड़-दर-मोड़ निर्देशों पर जाना, एसडीके आपके डेलिगेट पर कॉलबैक के तरीके लागू करता है और उस खास स्थिति के लिए उपलब्ध यूज़र इंटरफ़ेस (यूआई) के कॉम्पोनेंट उपलब्ध कराता है.
उपयोगकर्ताओं को बेहतर और इंटिग्रेटेड अनुभव देने के लिए, यह फ़्रेमवर्क आपके ऐप्लिकेशन और एसडीके के बीच ज़िम्मेदारी को साफ़ तौर पर अलग करता है. इन चीज़ों को पसंद के मुताबिक बनाने के लिए, लेआउट डेलिगेट का इस्तेमाल करें:
- व्यू हाइरार्की बनाना: हर यूज़र इंटरफ़ेस (यूआई) की स्थिति के लिए, स्क्रीन पर Google के कौनसे कॉम्पोनेंट और कारोबार के लिए बनाए गए कस्टम व्यू जोड़ने हैं, यह तय करें.
- हर एलिमेंट को पोज़िशन करना: स्क्रीन के सटीक ऐंकर, मार्जिन, और लेआउट प्लेसमेंट सेट करें. Google के कॉम्पोनेंट पर, कस्टम चौड़ाई या ऊंचाई की सीमाएं लागू न करें, क्योंकि Google के कॉम्पोनेंट अपने इंटरनल डाइमेंशन की गणना खुद करते हैं.
- बुनियादी मैप को फ़्रेम करना: व्यूपोर्ट कॉम्पोनेंट के कोऑर्डिनेट का इस्तेमाल करके, मैप कैमरे की दिखने वाली सीमाएं तय करें.
- स्क्रीन को लेयर करना: तय करें कि कारोबार के लिए बनाए गए कस्टम व्यू, Google के बिल्ट-इन कंट्रोल के ऊपर, नीचे या साथ में दिखें.
हालांकि, लेआउट डेलिगेट की मदद से, Google के इन कॉम्पोनेंट को पसंद के मुताबिक नहीं बनाया जा सकता:
- कॉम्पोनेंट के डाइमेंशन: Google की ओर से उपलब्ध कराए गए कॉम्पोनेंट का साइज़ और इंटरनल डाइमेंशन. इनकी गणना एसडीके अपने-आप करता है.
- ट्रिगर की शर्तें: रीयल-टाइम रूट डेटा के आधार पर, डाइनैमिक चेतावनियां या प्रॉम्प्ट कब दिखेंगे.
लागू करने के सिद्धांत
लेआउट डेलिगेट लिखते समय, लेआउट में गड़बड़ियां या रनटाइम क्रैश से बचने के लिए, इन नियमों को ध्यान में रखें:
- डिफ़ॉल्ट रूप से, स्क्रीन खाली होती है: Google की ओर से उपलब्ध कराए गए कॉम्पोनेंट सिर्फ़ तब दिखते हैं, जब आपका डेलिगेट उन्हें साफ़ तौर पर व्यू हाइरार्की में जोड़ता है और उन्हें पोज़िशन करता है.
- लेगसी एपीआई काम नहीं करते: स्लॉट-आधारित लेआउट मॉडल को कंट्रोल करने वाली प्रॉपर्टी और तरीके काम नहीं करेंगे. साथ ही, कस्टम डेलिगेट के चालू होने पर, हो सकता है कि वे उम्मीद के मुताबिक काम न करें.
- इंटरनल व्यू स्ट्रक्चर में बदलाव न करें:
Google की ओर से उपलब्ध कराए गए कॉम्पोनेंट (जैसे, टर्न कार्ड या ईटीए कार्ड) की व्यू हाइरार्की को ट्रैवर्स या उनमें बदलाव करने के लिए,
findViewById()जैसे तरीकों का इस्तेमाल न करें. ऐसा इसलिए, क्योंकि ये इंटरनल व्यू हाइरार्की, लागू करने से जुड़ी जानकारी होती हैं. इसलिए, एसडीके की रिलीज़ के हिसाब से इनमें बदलाव किया जा सकता है. इनमें बदलाव करने से, एसडीके के आने वाले अपडेट के दौरान, आपका लेआउट खराब हो सकता है.
लेगसी एपीआई के साथ काम करता है या नहीं
कस्टम लेआउट डेलिगेट का इस्तेमाल करते समय, लेआउट के भरोसेमंद व्यवहार के लिए, स्लॉट-आधारित इन लेगसी एपीआई का इस्तेमाल न करें. इन्हें बंद करने की योजना है. मौजूदा ऐप्लिकेशन को माइग्रेट करने के लिए, इन एपीआई के इस्तेमाल को अपने कस्टम लेआउट डेलिगेट में मौजूद कोड से बदलें:
स्लॉट-आधारित लेगसी एपीआई दिखाएं
| लेगसी एपीआई | लेआउट डेलिगेट का विकल्प |
|---|---|
setCustomControl(View, CustomControlPosition) |
अपने व्यू को सीधे ConstraintLayout या अन्य व्यू ग्रुप में जोड़ें. |
removeCustomControl(View) |
व्यू हाइरार्की से सीधे व्यू हटाएं. |
setEtaCardEnabled(boolean) |
onEnterActiveGuidance में etaCard व्यू पढ़ें. |
setHeaderEnabled(boolean) |
onEnterActiveGuidance में turnCard व्यू पढ़ें. |
setReportIncidentButtonEnabled(boolean) |
getActiveGuidanceButtons() में REPORTING बटन ढूंढें. |
setTripProgressBarEnabled(boolean) |
onEnterActiveGuidance में tripProgressBar व्यू पढ़ें. |
addOnNavigationUiChangedListener(...) |
अपने NavigationLayoutDelegate स्टेट ट्रांज़िशन कॉलबैक पर निर्भर रहें. |
removeOnNavigationUiChangedListener(...) |
डेलिगेट का इस्तेमाल करके, लेआउट की स्थिति में बदलाव को सीधे मैनेज करें. |
addPromptVisibilityChangedListener(...) |
NavigationLayoutDelegate के प्रॉम्प्ट कॉलबैक (जैसे, onShowPrompt()) पर निर्भर रहें. |
removePromptVisibilityChangedListener(...) |
डेलिगेट का इस्तेमाल करके, प्रॉम्प्ट दिखाने के लॉजिक को सीधे मैनेज करें. |
setCompassEnabled(boolean) |
getNavigationReadyButtons() या getActiveGuidanceButtons() में कंपास ढूंढें. |
Essentials की चेकलिस्ट
अपने लेआउट डेलिगेट को सही तरीके से लागू करने के लिए, ये ज़रूरी चरण और शर्तें पूरी करें:
-
यूज़र इंटरफ़ेस (यूआई) बनाने से पहले डेलिगेट को शुरू करना: एसडीके के नेविगेशन यूज़र इंटरफ़ेस (यूआई) को शुरू करने से पहले,
setLayoutDelegate()को कॉल करें. अपने ऐप्लिकेशन स्ट्रक्चर के लिए, सेटअप का सटीक समय देखने के लिए, कोड लागू करने के ये उदाहरण देखें. यूज़र इंटरफ़ेस (यूआई) बनाने के बाद डेलिगेट को शुरू करने पर,ApiIllegalStateExceptionट्रिगर होता है.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);
-
अपना लेआउट कंटेनर बनाना: कारोबार के लिए बनाए गए कस्टम यूज़र इंटरफ़ेस (यूआई) के एलिमेंट और Google की ओर से उपलब्ध कराए गए व्यू को रखने के लिए, कस्टम व्यू ग्रुप बनाएं. जैसे,
ConstraintLayout. -
ज़रूरी व्यू अटैच करना: आपको नेविगेशन की सभी स्थितियों में, Google का लोगो (
getGoogleLogo()) और व्यूपोर्ट (getViewport()) को अपने लेआउट कंटेनर में जोड़ना होगा. इन दोनों व्यू को शामिल न करने पर, रनटाइम के दौरानApiIllegalStateExceptionट्रिगर होता है और ऐप्लिकेशन क्रैश हो जाता है. -
तय डाइमेंशन का पालन करना: एसडीके, Google की ओर से उपलब्ध कराए गए कई कॉम्पोनेंट के डाइमेंशन तय करता है. इन एलिमेंट पर, कस्टम चौड़ाई या ऊंचाई की सीमाएं लागू न करें:
- टर्न कार्ड
- ईटीए कार्ड
- आने वाले प्रॉम्प्ट
- Google का लोगो
- स्पीड विजेट
-
अपने लेआउट को व्यू से अटैच करना: अपने कंटेनर को मैप व्यू हाइरार्की से अटैच करने के लिए,
navigationView.setNavigationLayout()औरonEnterNavigationReady()कॉलबैक मेंonEnterActiveGuidance()को कॉल करें. -
स्थिति से बाहर निकलने पर साफ़ करना: मेमोरी लीक और यूज़र इंटरफ़ेस (यूआई) की स्थितियों के ओवरलैप होने से बचने के लिए,
onLeaveकॉलबैक मेंnavigationView.removeNavigationLayout()को कॉल करें और कारोबार के लिए बनाए गए कस्टम व्यू हटाएं. -
लेगसी लेआउट एपीआई का इस्तेमाल न करना: कस्टम डेलिगेट के चालू होने पर, स्लॉट-आधारित बंद किए जा चुके एपीआई को कॉल न करें. जैसे,
setCustomControl()याsetHeaderEnabled(). कस्टम डेलिगेट के अटैच होने पर, एसडीके इन लेगसी कॉल को अनदेखा कर सकता है.
यूज़र इंटरफ़ेस (यूआई) की स्थितियां और Google के कॉम्पोनेंट
नेविगेशन की स्थिति में बदलाव होने पर, एसडीके आपके डेलिगेट कॉलबैक को सिर्फ़ पढ़ने के लिए UiState ऑब्जेक्ट पास करता है. इस ऑब्जेक्ट में, मौजूदा लेआउट कॉन्फ़िगरेशन फ़्लैग के साथ-साथ, Google के कॉम्पोनेंट (जैसे, View इंस्टेंस) शामिल होते हैं. इनकी मदद से, स्क्रीन को रेंडर किया जा सकता है.
लेआउट डेलिगेट, चार स्थितियों में व्यू हाइरार्की को मैनेज करता है. स्थिति के इस डायग्राम में दिखाया गया है कि एसडीके, नेविगेशन की स्थितियों के बीच कैसे बदलाव करता है और आपके डेलिगेट पर कौनसे कॉलबैक के तरीके लागू करता है:
आपके डेलिगेट को उपलब्ध कराए गए Google के कॉम्पोनेंट, नेविगेशन लाइफ़साइकल के मौजूदा फ़ेज़ पर निर्भर करते हैं.
ज़रूरी कॉम्पोनेंट (नेविगेशन की सभी स्थितियों के लिए)
नेविगेशन के मौजूदा फ़ेज़ के बावजूद, आपको अपनी व्यू हाइरार्की में Google के इन कॉम्पोनेंट को शामिल करना, पोज़िशन करना, और हमेशा दिखाना होगा:
Google का लोगो (
getGoogleLogo()): यह कॉम्पोनेंट, Google Maps का ज़रूरी लोगो दिखाता है. अगर फिर से बीच में लाएं बटन चालू है, तो ड्राइवर के मैप को अपनी गाड़ी से दूर स्क्रोल करने पर, लोगो अपने-आप इस बटन में बदल जाता है. इसलिए, लोगो का प्लेसमेंट तय करता है कि फिर से बीच में लाएं बटन कहां दिखेगा. मानक मैप यूज़र इंटरफ़ेस (यूआई) की उम्मीदों के मुताबिक, लोगो को अपने लेआउट के सबसे नीचे बाईं ओर (बॉटम-स्टार्ट) कोने में रखने का सुझाव दिया जाता है.व्यूपोर्ट (
getViewport()): एक ऐसा न दिखने वालाViewजो कैमरे की फ़्रेमिंग की सीमाएं तय करता है. व्यूपोर्ट का प्लेसमेंट तय करता है कि एसडीके, गाड़ी के शेवरॉन को कहां पर सेंटर करता है और ऐक्टिव रूट लाइन कहां ड्रॉ करता है. व्यूपोर्ट को इस तरह पोज़िशन करें कि वह आपकी स्क्रीन के खुले और बिना रुकावट वाले हिस्से को कवर करे. साथ ही, ओपेक ओवरले से सुरक्षित तरीके से बचे. जैसे, कारोबार के लिए बनाए गए कस्टम बॉटम शीट.
नेविगेशन के लिए तैयार कॉम्पोनेंट
नेविगेशन के लिए तैयार स्थिति के दौरान, यूज़र इंटरफ़ेस (यूआई) कम से कम रहता है, ताकि फ़ोकस बेस मैप पर बना रहे. आपका NavigationReadyUiState ऑब्जेक्ट, इन चीज़ों का ऐक्सेस देता है:
- getNavigationReadyButtons(): बेस मैप के लिए कॉन्फ़िगर किए गए फ़्लोटिंग ऐक्शन व्यू की सूची (आम तौर पर, सिर्फ़ कंपास बटन). Google, एसडीके की आने वाली रिलीज़ में बटन जोड़ सकता है या उनका क्रम बदल सकता है. इसलिए, सूची के तय इंडेक्स पर निर्भर न रहें. इसके बजाय, सूची में दोहराएं,
getType()की तुलनाButtonKnownType.COMPASSसे करके, हर बटन का टाइप पढ़ें, औरgetView()को कॉल करके, फ़िज़िकल व्यू एक्सट्रैक्ट करें. ध्यान दें कि इस सूची में मौजूद बटन, ऐक्टिव निर्देशों के दौरान उपलब्ध बटन से अलग होते हैं.
ऐक्टिव निर्देशों के कॉम्पोनेंट
मोड़-दर-मोड़ निर्देश शुरू होने पर, एसडीके नेविगेशन कंट्रोल का फ़ुल सुइट अनलॉक करता है. आपका ActiveGuidanceUiState ऑब्जेक्ट, Google के इन कॉम्पोनेंट का ऐक्सेस देता है:
getTurnCard(): प्राइमरी हेडर बैनर, जो आने वाले मैन्यूवर के निर्देश, दूरी के मेज़रमेंट, और लेन गाइडेंस दिखाता है. इसे अपने लेआउट में सबसे ऊपर रखें, ताकि नेविगेशन की जानी-पहचानी हाइरार्की बनाई जा सके और कारोबार के लिए बनाए गए कस्टम व्यू को इसके आस-पास ऐंकर किया जा सके.getEtaCard(): फ़ुटर बैनर, जो पहुंचने का अनुमानित समय, यात्रा का बचा हुआ समय, और मंज़िल तक की बची हुई दूरी दिखाता है. इसे अपनी स्क्रीन के सबसे नीचे वाले किनारे पर रखें या इसके कोऑर्डिनेट को, कारोबार के लिए बनाई गई कस्टम टास्क-मैनेजमेंट शीट के साथ इंटिग्रेट करें.getTripProgressBar(): एक वर्टिकल प्रोग्रेस बार, जो ड्राइवर को दिखाता है कि उसने मौजूदा रूट पर कितनी दूरी तय की है. लेगसी लेआउट के उलट, जो इसे मैप के किनारे पर फ़िक्स करते थे, आपके पास इसे कहीं भी ऐंकर करने की पूरी आज़ादी है. जैसे, कारोबार के लिए बनाए गए कस्टम कंटेनर के किसी साइड एज पर.getSpeedWidget(): स्पीडोमीटर और पोस्ट की गई स्पीड लिमिट के लिए, एक फ़्लोटिंग कंट्रोल. आपकी एपीआई सेटिंग और डेटा की उपलब्धता के आधार पर, यह व्यू रनटाइम के दौरान, चार विज़ुअल स्थितियों के बीच डाइनैमिक तरीके से रीसाइज़ होता है. जैसे, कुछ नहीं दिखाना, सिर्फ़ मौजूदा स्पीड दिखाना, सिर्फ़ स्पीड लिमिट दिखाना या दोनों रीडिंग दिखाना. विजेट, इन साइज़ के बीच बिना किसी सूचना के शिफ़्ट हो सकता है. इसलिए, आस-पास के व्यू को हमेशा कंस्ट्रेंट का इस्तेमाल करके ऐंकर करें, ताकि आपका लेआउट, स्पेस से जुड़े ओवरलैप से बचने के लिए अपने-आप अडजस्ट हो जाए.
getActiveGuidanceButtons(): ऐक्टिव निर्देशों की स्थिति के लिए, फ़्लोटिंग ऐक्शन व्यू की एक बड़ी सूची. इसमें आम तौर पर, कंपास और घटना की रिपोर्ट करने का बटन, दोनों शामिल होते हैं. नेविगेशन के लिए तैयार स्थिति की तरह ही, सूची कोButtonKnownType(COMPASSयाREPORTING) के हिसाब से फ़िल्टर करके औरgetView()का इस्तेमाल करके व्यू एक्सट्रैक्ट करके, अलग-अलग बटन व्यू को ढूंढकर एक्सट्रैक्ट किया जा सकता है. इसके बाद, इन्हें अलग-अलग पोज़िशन किया जा सकता है याAutoHidingLinearLayoutजैसे लेआउट का इस्तेमाल करके, ऐरे को स्पेस से जुड़े टकराव के बिना सुरक्षित तरीके से स्टैक किया जा सकता है.
डाइनैमिक प्रॉम्प्ट के कॉम्पोनेंट
ऐक्टिव निर्देशों के दौरान, प्रॉम्प्ट अलग-अलग ट्रिगर होते हैं. जैसे, घटना की चेतावनियां या सुरक्षा कैमरे की चेतावनियां.
प्रॉम्प्ट के दिखने के लिए तैयार होने पर, एसडीके आपके डेलिगेट के onShowPrompt() कॉलबैक को कॉल करता है और newPrompt व्यू पास करता है. आपका डेलिगेट, इस प्रॉम्प्ट को आपके लेआउट पर आसानी से पोज़िशन करने के लिए ज़िम्मेदार होता है. आम तौर पर, इसे आपके मैप कंटेनर के सबसे नीचे वाले किनारे पर ऐंकर किया जाता है.
आने वाला प्रॉम्प्ट, स्क्रीन के निचले हिस्से को कवर करता है. इसलिए, आपको अपने लेआउट को अपडेट करना होगा, ताकि प्रॉम्प्ट, व्यूपोर्ट, Google के लोगो या सबसे नीचे अलाइन किए गए किसी भी बटन पर ओवरलैप न हो.
स्क्रीन के साइज़ और वाइड मोड को मैनेज करना
मैप के डाइमेंशन और स्क्रीन के ओरिएंटेशन में होने वाले बदलावों को मैनेज करने के लिए, लेआउट डेलिगेट इन सुविधाओं का इस्तेमाल करता है:
व्यू का साइज़ बदलना: मैप के फ़िज़िकल साइज़ में बदलाव होने पर, आपके लेआउट को अडजस्ट करता है.
वाइड मोड: मैप के काफ़ी चौड़ा होने पर, वाइड-फ़ॉर्मैट लेआउट वैरिएंट पर स्विच करता है.
व्यू का साइज़ बदलने पर जवाब देना
मैप कंटेनर के फ़िज़िकल डाइमेंशन में बदलाव होने पर, एसडीकेonSizeChanged() कॉलबैक को लागू करता है. स्प्लिट-स्क्रीन लेआउट, लेआउट स्लाइडर, और डिवाइस रोटेशन आम तौर पर onSizeChanged() कॉलबैक को ट्रिगर करते हैं. अपने कस्टम यूज़र इंटरफ़ेस (यूआई) में सामान्य रिस्पॉन्सिव अडजस्टमेंट करने के लिए, इस कॉलबैक का इस्तेमाल करें. onSizeChanged() को लागू करें, ताकि स्क्रीन के नए आसपेक्ट रेशियो के लिए, कस्टम एलिमेंट को फिर से पोज़िशन किया जा सके. साथ ही, चौड़ाई या ऊंचाई के लिए अपने कस्टम ब्रेकपॉइंट लागू किए जा सकें. इसके अलावा, यह भी पता लगाया जा सके कि वाइड मोड को टॉगल करने के लिए, रीसाइज़ इवेंट कब होता है, ताकि लेआउट वैरिएंट को सुरक्षित तरीके से बदला जा सके.
वाइड मोड को समझना
वाइड मोड तब चालू होता है, जब मैप कंटेनर इतना चौड़ा हो कि यूज़र इंटरफ़ेस (यूआई) के कॉम्पोनेंट को साइड-बाय-साइड दिखाया जा सके.
अपने कस्टम यूज़र इंटरफ़ेस (यूआई) के एलिमेंट को फिर से पोज़िशन करने और ड्राइवर के लिए मैप के बीच वाले हिस्से को साफ़ रखने के लिए, स्टेट ऑब्जेक्ट से isWideMode() बूलियन पढ़ें. टर्न कार्ड और ईटीए कार्ड जैसे Google की ओर से उपलब्ध कराए गए कॉम्पोनेंट, वाइड मोड में अपने-आप सिकुड़ जाते हैं और उनका आकार बदल जाता है. इसलिए, इस बूलियन को पढ़ने से यह पक्का होता है कि आपका लेआउट, Google के कॉम्पोनेंट के अपडेट होने के ठीक उसी समय अडजस्ट हो जाए.
स्टैंडर्ड और वाइड मोड में, अपने लेआउट कॉम्पोनेंट को पोज़िशन करने के लिए, ये उदाहरण देखें:
स्टैंडर्ड पोर्ट्रेट मोड: टर्न कार्ड को स्क्रीन के सबसे ऊपर और ईटीए कार्ड को सबसे नीचे पोज़िशन करें.
वाइड मोड: टर्न कार्ड को स्क्रीन के बाईं ओर और ईटीए कार्ड को दाईं ओर ले जाएं.
स्थिति में बदलाव के दौरान वाइड मोड की जांच करना
अगर आपका लेआउट, वाइड-मोड वैरिएंट के साथ काम करता है, तो सिर्फ़ onSizeChanged() पर निर्भर रहने के बजाय, स्थिति में बदलाव के हर कॉलबैक में isWideMode() बूलियन का आकलन करें.
onEnterNavigationReady() और onEnterActiveGuidance() जैसे कॉलबैक के दौरान, स्टेट ऑब्जेक्ट की जांच करें. इस तरीके से, आपके लेआउट को Android सिस्टम लाइफ़साइकल इवेंट से बचाया जा सकता है. जैसे, डिफ़ॉल्ट ऐक्टिविटी को फिर से बनाना. ये इवेंट, रीसाइज़िंग कॉलबैक को पूरी तरह से बायपास कर देते हैं. इससे यह पक्का होता है कि नेविगेशन की नई स्थिति शुरू होने पर, स्टैंडर्ड या वाइड लेआउट का सही वैरिएंट चालू हो.
Google की स्टाइलिंग के मुताबिक बनाना
कारोबार के लिए बनाए गए कस्टम यूज़र इंटरफ़ेस (यूआई) को Google के विज़ुअल रिदम के मुताबिक बनाने के लिए, एसडीके StyleValues यूटिलिटी क्लास उपलब्ध कराता है. Google के कॉम्पोनेंट के साथ अपने व्यू को पूरी तरह से अलाइन करने के लिए, इन वैल्यू को डेंसिटी-इंडिपेंडेंट पिक्सल (डीपी) में पढ़ा जा सकता है.
उदाहरण के लिए, अगर आपको स्क्रीन के सबसे ऊपर वाले कोने में, टर्न कार्ड के दूसरी ओर, कारोबार के लिए बनाया गया कस्टम बटन रखना है, तो StyleValues.headerTopPaddingDp() को कॉल करें और मिली हुई वैल्यू को अपने बटन के सबसे ऊपर वाले मार्जिन के तौर पर असाइन करें. इससे यह पक्का होता है कि आपका कस्टम बटन, टर्न कार्ड के सबसे ऊपर वाले किनारे के साथ विज़ुअल तौर पर अलाइन हो जाए. साथ ही, स्क्रीन की सिमेट्री बनी रहे.
स्टाइल पैडिंग और मेज़रमेंट में ये शामिल हैं:
StyleValues.headerNominalHeightDp()StyleValues.headerTopPaddingDp()StyleValues.headerFooterSidePaddingDp()StyleValues.mapControlSidePaddingDp()StyleValues.buttonMapControlSidePaddingDp()
उदाहरण: कंस्ट्रेंट-आधारित लेआउट लागू करना
यहां दिए गए उदाहरण में, एक बुनियादी लेआउट डेलिगेट दिखाया गया है. यह प्रोग्राम के ज़रिए तय किए गए ConstraintLayout और ConstraintSet की परिभाषाओं का इस्तेमाल करके, स्थिति में बदलाव को मैनेज करता है.
हालांकि, इस उदाहरण में कोड में व्यू कंस्ट्रेंट बनाए गए हैं, लेकिन लेआउट डेलिगेट, स्टैंडर्ड Android एक्सएमएल लेआउट को भी इन्फ़्लेट कर सकते हैं.
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. */ @file:Suppress("PackageName") 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()); } }