
يمكنك استخدام ميزة تخصيص التنسيق للتحكّم في هيكلية طرق العرض وموضع الشاشة لكلّ من طرق العرض التي توفّرها Google وطرق العرض المخصّصة لمؤسستك.
بدلاً من إضافة طرق عرض مخصّصة إلى خانات العناوين أو التذييلات المحدّدة مسبقًا، يمكنك تنفيذ
مفوَّض تنسيق لإدارة تنسيق الشاشة بأكملها. أثناء عمليات انتقال حالة التنقّل، يتلقّى مفوَّض التنسيق المكوّنات التي توفّرها Google، مثل بطاقة الاتجاهات وبطاقة الوقت المقدّر للوصول والأزرار. يمكنك وضع هذه العناصر بجانب المحتوى المخصّص باستخدام أنظمة تنسيق Android العادية، مثل ConstraintLayout أو CoordinatorLayout أو LinearLayout.
استخدِم هذا الإطار لعرض معلومات مخصّصة عن المؤسسة، مثل حالة الطلب أو تعليمات الاستلام، بدقة على الشاشة مع منع تداخل طرق العرض.
طريقة عمل ميزة "تخصيص التنسيق"
يستخدم تخصيص التنسيق نمط تصميم التفويض. بدلاً من رسم مكوّنات واجهة المستخدم أو تحديد موضعها تلقائيًا على الشاشة، تمرّر حزمة تطوير البرامج (SDK) هذه المكوّنات مباشرةً إلى فئة مخصّصة تكتبها أنت: مفوَّض التنسيق.
لتخصيص التنسيق، أنشئ فئة تمدّد الفئة المجرّدةNavigationLayoutDelegate وعيِّن مثيلاً إلى NavigationView
أو SupportNavigationFragment. عندما تتغيّر حالة التنقّل، مثل الانتقال من الخريطة الأساسية إلى الإرشادات المفصّلة النشطة، تنفّذ حزمة تطوير البرامج (SDK) طرق ردّ الاتصال على المفوَّض وتوفّر مكوّنات واجهة المستخدم المتاحة لتلك الحالة المحدّدة.
لتوفير تجربة مستخدم سلسة ومتكاملة، يضع إطار العمل هذا فصلاً واضحًا للمسؤولية بين تطبيقك وحزمة تطوير البرامج (SDK). استخدِم مفوَّض التنسيق لتخصيص ما يلي:
- إنشاء هيكلية طرق العرض: اختَر مكوّنات Google وطرق العرض المخصّصة للنشاط التجاري التي تريد إضافتها إلى الشاشة لكل حالة من حالات واجهة المستخدم.
- تحديد موضع كل عنصر: يمكنك ضبط نقاط تثبيت الشاشة والهوامش وموضع التنسيق بدقة. تجنَّب تطبيق قيود مخصّصة على العرض أو الارتفاع في مكوّنات Google، لأنّ مكوّنات Google تحسب أبعادها الداخلية.
- تحديد إطار الخريطة الأساسية: حدِّد الحدود المرئية لكاميرا الخريطة باستخدام إحداثيات مكوّن إطار العرض.
- تحديد طبقات الشاشة: يمكنك تحديد ما إذا كانت طرق العرض المخصّصة ستظهر فوق عناصر التحكّم المضمّنة في Google أو تحتها أو بجانبها.
في الوقت نفسه، لا يمكنك تخصيص مكوّنات Google التالية باستخدام مفوّض التنسيق:
- أبعاد المكوّنات: حجم المكوّنات التي توفّرها Google وأبعادها الداخلية، والتي تحتسبها حزمة تطوير البرامج (SDK) تلقائيًا.
- شروط التفعيل: متى تظهر التنبيهات أو الطلبات الديناميكية استنادًا إلى بيانات المسار في الوقت الفعلي؟
مبادئ تنفيذ
عند كتابة مفوَّض تنسيق، يجب مراعاة القواعد التالية لمنع أخطاء التنسيق أو الأعطال أثناء التشغيل:
- الإعداد التلقائي هو شاشة فارغة: لا تظهر المكوّنات التي توفّرها Google إلا إذا أضافها المفوَّض بشكل صريح إلى هيكلية طرق العرض وحدّد مواضعها.
- واجهات برمجة التطبيقات القديمة غير متاحة: لن تكون السمات والطرق التي تتحكّم في نموذج التنسيق القديم المستند إلى الخانات متاحة، وقد لا تعمل على النحو المتوقّع عندما يكون المفوَّض المخصّص نشطًا.
- عدم تعديل هياكل طرق العرض الداخلية:
لا تستخدِم طرقًا مثل
findViewById()للتنقّل في هياكل طرق العرض للمكوّنات التي توفّرها Google (مثل بطاقة الاتجاه أو بطاقة الوقت المقدّر للوصول) أو تعديلها. بما أنّ هياكل طرق العرض الداخلية هذه هي تفاصيل تنفيذ أساسية، فهي عرضة للتغيير في إصدارات حزمة تطوير البرامج (SDK). وقد يؤدي تعديلها إلى تعطُّل التنسيق أثناء تحديثات حزمة تطوير البرامج (SDK) المستقبلية.
التوافق مع واجهة برمجة التطبيقات القديمة
لضمان سلوك تخطيط موثوق عند استخدام مفوّض تخطيط مخصّص، تجنَّب استخدام واجهات برمجة التطبيقات القديمة التالية المستندة إلى الخانات، والتي من المقرر إيقافها نهائيًا. لنقل تطبيق حالي، استبدِل استخدامك لواجهات برمجة التطبيقات هذه برمز في مفوَّض التنسيق المخصّص:
عرض واجهات برمجة التطبيقات القديمة المستندة إلى الخانات
| واجهة برمجة التطبيقات القديمة | بديل Layout Delegate |
|---|---|
setCustomControl(View, CustomControlPosition) |
أضِف مشاهداتك مباشرةً إلى ConstraintLayout أو مجموعة مشاهدات أخرى. |
removeCustomControl(View) |
إزالة طرق العرض مباشرةً من هيكلية طرق العرض |
setEtaCardEnabled(boolean) |
قراءة طريقة العرض "etaCard" في "onEnterActiveGuidance" |
setHeaderEnabled(boolean) |
قراءة طريقة العرض "turnCard" في "onEnterActiveGuidance" |
setReportIncidentButtonEnabled(boolean) |
ابحث عن زر "REPORTING" في "getActiveGuidanceButtons()". |
setTripProgressBarEnabled(boolean) |
قراءة طريقة العرض "tripProgressBar" في "onEnterActiveGuidance" |
addOnNavigationUiChangedListener(...) |
الاعتماد على عمليات الاستدعاء الخاصة بالحالة الانتقالية في NavigationLayoutDelegate |
removeOnNavigationUiChangedListener(...) |
يمكنك إدارة عمليات انتقال حالة التنسيق مباشرةً باستخدام المفوَّض. |
addPromptVisibilityChangedListener(...) |
الاعتماد على عمليات الاستدعاء لطلبات NavigationLayoutDelegate (مثل onShowPrompt()) |
removePromptVisibilityChangedListener(...) |
إدارة منطق عرض الطلب مباشرةً باستخدام المفوَّض |
setCompassEnabled(boolean) |
ابحث عن البوصلة في getNavigationReadyButtons() أو getActiveGuidanceButtons(). |
قائمة التحقّق من الأساسيات
اتّبِع هذه الخطوات والمتطلبات الأساسية لتنفيذ تفويض التنسيق بنجاح:
-
إعداد المفوَّض قبل إنشاء واجهة المستخدم: يمكنك استدعاء
setLayoutDelegate()قبل أن تبدأ حزمة تطوير البرامج (SDK) في إعداد واجهة مستخدم التنقّل. اطّلِع على أمثلة تنفيذ الرموز التالية للتحقّق من توقيت الإعداد الدقيق لبنية تطبيقك. يؤدي إعداد المفوَّض بعد إنشاء واجهة المستخدم إلى ظهور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)
جافا
// 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);
-
إنشاء حاوية تنسيق خاصة بك: يمكنك إنشاء مجموعة طرق عرض مخصّصة، مثل
ConstraintLayout، لتضمين عناصر واجهة المستخدم المخصّصة وطرق العرض التي توفّرها Google. -
إرفاق طرق العرض الإلزامية: يجب إضافة شعار Google (
getGoogleLogo()) وإطار العرض (getViewport()) إلى حاوية التنسيق في جميع حالات التنقّل. ويؤدي عدم تضمين كلتا الطريقتين إلى ظهور خطأApiIllegalStateExceptionفي وقت التشغيل وتعطُّل التطبيق. -
الالتزام بالأبعاد الثابتة: تحدّد حزمة تطوير البرامج (SDK) أبعاد العديد من المكوّنات التي توفّرها Google. لا تطبِّق قيودًا مخصّصة على العرض أو الارتفاع على العناصر التالية:
- قلب البطاقة
- بطاقة الوقت المقدّر للوصول
- الطلبات الواردة
- شعار Google
- تطبيق مصغّر للسرعة
-
إرفاق التنسيق بطريقة العرض: يمكنك استدعاء
navigationView.setNavigationLayout()داخل عمليات الاستدعاءonEnterNavigationReady()وonEnterActiveGuidance()لإرفاق الحاوية بهيكلية طريقة عرض الخريطة. -
التنظيف عند الخروج من الحالة: يمكنك استدعاء
navigationView.removeNavigationLayout()وإزالة طرق العرض المخصّصة داخل عمليات الاستدعاءonLeaveلمنع تسرُّب الذاكرة وتداخل حالات واجهة المستخدم. -
تجنُّب واجهات برمجة التطبيقات القديمة للتنسيق: لا تستدعِ واجهات برمجة التطبيقات المتوقفة نهائيًا المستندة إلى الخانات، مثل
setCustomControl()أوsetHeaderEnabled()، عندما يكون المفوَّض المخصّص نشطًا. قد تتجاهل حزمة تطوير البرامج (SDK) مكالمات عبر ميزة الاتصال السابقة هذه عند إرفاق مفوَّض مخصّص.
حالات واجهة المستخدم ومكوّنات Google
عند حدوث انتقال في حالة التنقّل، تمرّر حزمة تطوير البرامج (SDK) عنصر UiState للقراءة فقط إلى دالة ردّ الاتصال المفوَّضة. يجمع هذا العنصر علامات ضبط التنسيق الحالية مع مكوّنات Google (مثل نُسخ View) التي تحتاج إليها لعرض الشاشة.
يدير مفوَّض التنسيق هيكلية طرق العرض في أربع حالات تشغيلية. يوضّح مخطّط الحالة التالي كيفية انتقال حزمة تطوير البرامج (SDK) بين حالات التنقّل وطرق ردّ الاتصال التي تنفّذها على المفوَّض:
تعتمد مكوّنات Google المحدّدة التي يتم توفيرها للمفوَّض على المرحلة الحالية من مراحل التنقّل.
المكوّنات المطلوبة (جميع حالات التنقّل)
بغض النظر عن مرحلة التنقّل الحالية، يجب تضمين مكوّنات Google التالية ووضعها وإبقاؤها مرئية في هيكلية طرق العرض:
شعار Google (
getGoogleLogo()): يعرض هذا المكوّن شعار "خرائط Google" الإلزامي. إذا كان زر إعادة التوسيط مفعَّلاً، يتحوّل الشعار تلقائيًا إلى هذا الزر عندما يمرّر السائق الخريطة بعيدًا عن مركبته. نتيجةً لذلك، يحدّد موضع الشعار مكان ظهور زر إعادة التوسيط. ننصحك بوضع الشعار في الزاوية السفلية اليمنى من التنسيق ليتوافق مع واجهة المستخدم العادية للخريطة.إطار العرض (
getViewport()): هوViewغير مرئي يحدّد حدود إطار الكاميرا. يحدّد موضع إطار العرض بدقة المكان الذي تضع فيه حزمة تطوير البرامج (SDK) علامة شيفرون المركبة في المنتصف وترسم خط المسار النشط. اضبط موضع إطار العرض بحيث يغطي المنطقة المفتوحة وغير المحجوبة من شاشتك، مع تجنُّب العناصر المركّبة المعتمة بأمان، مثل البطاقات السفلية المخصّصة.
مكوّن جاهز للتنقّل
أثناء حالة الاستعداد للتنقّل، تظل واجهة المستخدم في الحد الأدنى للحفاظ على التركيز على
الخريطة الأساسية. يتيح عنصر NavigationReadyUiState الوصول إلى ما يلي:
- getNavigationReadyButtons(): قائمة بطرق عرض الإجراءات العائمة التي تم ضبطها للخريطة الأساسية (عادةً ما تكون زر "البوصلة" فقط). بما أنّ Google قد تضيف أزرارًا أو تعيد ترتيبها في الإصدارات المستقبلية من حزمة تطوير البرامج (SDK)، تجنَّب الاعتماد على فهارس القوائم الثابتة. بدلاً من ذلك، يمكنك تكرار القائمة وقراءة نوع كل زر من خلال مقارنة
getType()معButtonKnownType.COMPASS، واستخراج العرض المادي من خلال استدعاءgetView(). يُرجى العِلم أنّ الأزرار المحدّدة في هذه القائمة تختلف عن تلك المتاحة أثناء الإرشاد النشط.
مكوّنات إرشادية نشطة
عند بدء الاتجاهات المفصّلة، تتيح حزمة تطوير البرامج (SDK) الوصول إلى المجموعة الكاملة من عناصر التحكّم في التنقّل. يتيح عنصر ActiveGuidanceUiState الوصول إلى مكوّنات Google التالية:
getTurnCard(): بانر العنوان الأساسي الذي يعرض اتجاهات المناورة القادمة وقياسات المسافة وإرشادات المسار. ضَعه في أعلى التنسيق لإنشاء تسلسل هرمي مألوف للتنقّل وتثبيت طرق العرض المخصّصة حوله.getEtaCard(): بانر التذييل الذي يعرض وقت الوصول المقدَّر والوقت المتبقي للرحلة والمسافة المتبقية إلى الوجهة يمكنك وضعه على طول الحافة السفلية من الشاشة، أو دمج إحداثياته مع أوراق إدارة المهام المخصصة.getTripProgressBar(): شريط تقدّم عمودي يعرض للسائق المسافة التي قطعها على طول المسار الحالي على عكس التنسيقات القديمة التي كانت تثبّت هذا الزر على حافة الخريطة بشكل صارم، لديك الآن الحرية الكاملة في تثبيته في أي مكان، على سبيل المثال، على طول الحافة الجانبية للحاوية المخصّصة.
getSpeedWidget(): عنصر تحكّم عائم لعدّاد السرعة والحدود القصوى للسرعة المنشورة استنادًا إلى إعدادات واجهة برمجة التطبيقات ومدى توفّر البيانات، يتم تغيير حجم طريقة العرض هذه ديناميكيًا بين أربع حالات مرئية في وقت التشغيل (لا يتم عرض أي شيء، أو السرعة الحالية فقط، أو الحد الأقصى للسرعة فقط، أو كلتا القراءتين). Because the widget can shift between these sizes without notice, always anchor surrounding views using constraints so your layout automatically adapts to prevent spatial overlaps.
-
getActiveGuidanceButtons(): قائمة موسّعة بطرق عرض الإجراءات الرئيسية لحالة الإرشاد النشطة (تتضمّن عادةً كلاً من "البوصلة" وزر "الإبلاغ عن حادثة"). كما هو الحال في حالة الاستعداد للتنقّل، يمكنك تحديد موقع طرق عرض الأزرار الفردية واستخراجها من خلال فلترة القائمة حسبButtonKnownType(COMPASSأوREPORTING) واستخراج طريقة العرض باستخدامgetView(). يمكنك بعد ذلك وضعها بشكل مستقل أو استخدام تنسيق مثلAutoHidingLinearLayoutلتجميع المصفوفة بأمان بدون تداخل مكاني.
مكوّنات في "الطلب الديناميكي"
يتم تفعيل الطلبات، مثل تنبيهات الحوادث أو تحذيرات كاميرات السلامة، بشكل مستقل أثناء الإرشاد النشط.
عندما تكون الرسالة جاهزة للعرض، تستدعي حزمة SDK دالة الاستدعاء onShowPrompt() الخاصة بالمفوَّض وتمرّر طريقة عرض newPrompt. يكون المفوَّض مسؤولاً عن تحديد موضع هذا الطلب بسلاسة فوق التنسيق (عادةً ما يكون مثبّتًا على الحافة السفلية لحاوية الخريطة).
بما أنّ الطلب الوارد يغطي الجزء السفلي من الشاشة، عليك تعديل التنسيق لمنع تداخل الطلب مع إطار العرض أو شعار Google أو أي أزرار محاذية للأسفل.
التعامل مع حجم الشاشة والوضع العريض
للتعامل مع التغييرات في أبعاد الخريطة واتجاه الشاشة، يستخدم مفوَّض التنسيق الميزات التالية:
تغيير حجم العرض: يضبط التنسيق كلما تغيّر الحجم الفعلي للخريطة.
الوضع العريض: يتم التبديل إلى صيغة تنسيق عريض عندما تكون الخريطة عريضة بما يكفي.
الاستجابة لتغيير حجم العرض
عندما تتغيّر الأبعاد المادية لحاوية الخريطة، تنفّذ حزمة تطوير البرامج (SDK) دالة الاستدعاءonSizeChanged(). عادةً ما تؤدي تنسيقات تقسيم الشاشة وأشرطة تمرير التنسيق وتدوير الجهاز إلى تشغيل دالة الاستدعاء onSizeChanged(). استخدِم هذه الدالة لإجراء تعديلات عامة متجاوبة على واجهة المستخدم المخصّصة. يمكنك تنفيذ
onSizeChanged() لإعادة تحديد موضع العناصر المخصّصة لنسبة عرض إلى ارتفاع جديدة للشاشة،
وتطبيق نقاط توقّف مخصّصة للعرض أو الارتفاع، ورصد وقت تبديل حدث تغيير الحجم
إلى الوضع العريض لتبديل خيارات التنسيق بأمان.
لمحة عن الوضع العريض
يتم تفعيل الوضع العريض عندما تكون حاوية الخريطة عريضة بما يكفي لعرض مكوّنات واجهة المستخدم جنبًا إلى جنب.
اقرأ القيمة المنطقية isWideMode() من عنصر الحالة لإعادة تحديد موضع عناصر في واجهة المستخدم المخصّصة وإبقاء وسط الخريطة واضحًا للسائق. بما أنّ المكوّنات التي توفّرها Google، مثل بطاقة الاتجاهات وبطاقة الوقت المقدّر للوصول، تتقلّص وتتغيّر تلقائيًا في الوضع العريض، فإنّ قراءة هذه القيمة المنطقية تضمن تكيُّف التنسيق في اللحظة نفسها التي يتم فيها تعديل مكوّنات Google.
اطّلِع على الأمثلة التالية لتحديد موضع مكوّنات التنسيق في الوضعَين العادي والعريض:
وضع "بورتريه" العادي: يتم وضع بطاقة الاتجاهات في أعلى الشاشة وبطاقة الوقت المقدّر للوصول في أسفلها.
الوضع العريض: يمكنك نقل بطاقة الانعطاف إلى جانب بداية الشاشة وبطاقة الوقت المقدّر للوصول إلى جانب النهاية.
التحقّق من الوضع العريض أثناء عمليات انتقال الحالة
إذا كان التنسيق يتيح استخدام وضع عريض، عليك تقييم القيمة المنطقية isWideMode() داخل كل دالة استدعاء لانتقال الحالة، بدلاً من الاعتماد فقط على onSizeChanged().
onEnterNavigationReady() وonEnterActiveGuidance()، للتعامل مع عمليات تشغيل التطبيق الأولية في الوضع الأفقي. يضمن هذا النهج حماية تنسيقك من أحداث مراحل النشاط في نظام Android، مثل إعادة إنشاء النشاط التلقائي، والتي تتجاوز تمامًا طلب معاودة الاتصال لتغيير الحجم، ما يضمن تفعيل الصيغة الصحيحة للتنسيق العادي أو العريض في اللحظة التي تبدأ فيها حالة تنقّل جديدة.
التوافق مع أسلوب Google
للمساعدة في مطابقة واجهة المستخدم المخصّصة مع الإيقاع المرئي من Google، توفّر حزمة تطوير البرامج (SDK) فئة الأداة المساعدة StyleValues. يمكنك قراءة هذه القيم بوحدات بكسل مستقلة الكثافة (dp) لمحاذاة طرق العرض بشكل مثالي مع مكونات Google.
على سبيل المثال، إذا أردت وضع زر مخصّص في الزاوية العلوية من الشاشة مقابل بطاقة الاتجاه، يمكنك استدعاء StyleValues.headerTopPaddingDp() وتعيين القيمة المعروضة كهامش علوي للزر. يضمن ذلك محاذاة الزر المخصّص بصريًا مع الحافة العلوية لبطاقة الاتجاه، ما يحافظ على تناسق الشاشة.
تشمل قياسات الهوامش والأنماط المتاحة ما يلي:
StyleValues.headerNominalHeightDp()StyleValues.headerTopPaddingDp()StyleValues.headerFooterSidePaddingDp()StyleValues.mapControlSidePaddingDp()StyleValues.buttonMapControlSidePaddingDp()
مثال: تنفيذ التنسيق المستند إلى القيود
يوضّح المثال التالي مفوّض تنسيق أساسي يدير عمليات الانتقال بين الحالات باستخدام تعريفات ConstraintLayout وConstraintSet الآلية.
على الرغم من أنّ هذا المثال ينشئ قيودًا على العرض في الرمز البرمجي، يمكن لمفوّضي التنسيق أيضًا توسيع تنسيقات XML العادية في 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. */ 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() } }
جافا
/* * 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()); } }
للاطّلاع على الرمز النموذجي الكامل أو استكشاف عمليات تنفيذ إضافية لمفوّض التنسيق، يمكنك الاطّلاع على نماذج مفوّض التنسيق على GitHub. يمكنك أيضًا الاطّلاع على ملفات المصدر الفردية لهذا المثال على GitHub: