استخدام المكوّنات الإضافية لمكتبة واجهة مستخدم السيارة لإنشاء عمليات تنفيذ كاملة للمكوّنات عمليات التخصيص في مكتبة واجهة مستخدم السيارة بدلاً من استخدام طبقات موارد وقت التشغيل (RROs). تتيح لك سجلات الموارد المنتظمة (RRO) تغيير موارد XML فقط لمكتبة واجهة مستخدم السيارة. من المكونات، والتي تحدد مدى ما يمكنك تخصيصه.
إنشاء مكوّن إضافي
المكوّن الإضافي لمكتبة واجهة مستخدم السيارة هو حزمة APK تحتوي على فئات تنفذ مجموعة من واجهات برمجة تطبيقات المكوّنات الإضافية. يمكن تجميع واجهات برمجة تطبيقات المكوّنات الإضافية في المكون الإضافي كمكتبة ثابتة.
الاطّلاع على أمثلة في "سونغ" و"غرادل":
سونغ
ضع في الاعتبار هذا المثال لـ Sung:
android_app {
name: "my-plugin",
min_sdk_version: "28",
target_sdk_version: "30",
aaptflags: ["--shared-lib"],
sdk_version: "current",
manifest: "src/main/AndroidManifest.xml",
srcs: ["src/main/java/**/*.java"],
resource_dirs: ["src/main/res"],
static_libs: [
"car-ui-lib-oem-apis",
],
// Disable optimization is mandatory to prevent R.java class from being
// stripped out
optimize: {
enabled: false,
},
certificate: ":my-plugin-certificate",
}
قاعدة مخروطية
انظر هذا الملف بتنسيق build.gradle
:
apply plugin: 'com.android.application'
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 28
targetSdkVersion 30
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
signingConfigs {
debug {
storeFile file('chassis_upload_key.jks')
storePassword 'chassis'
keyAlias 'chassis'
keyPassword 'chassis'
}
}
}
dependencies {
implementation project(':oem-apis')
// Or use the following if you'd like to use the maven artifact
// implementation 'com.android.car.ui:car-ui-lib-plugin-apis:1.0.0'
}
Settings.gradle
:
// You can remove the ':oem-apis' if you're using the maven artifact.
include ':oem-apis'
project(':oem-apis').projectDir = new File('./path/to/oem-apis')
include ':my-plugin'
project(':my-plugin').projectDir = new File('./my-plugin')
يجب أن يكون هناك موفّر محتوى معرَّف في البيان الخاص بالمكوّن الإضافي يتضمّن السمات التالية:
android:authorities="com.android.car.ui.plugin"
android:enabled="true"
android:exported="true"
android:authorities="com.android.car.ui.plugin"
يجعل المكوّن الإضافي قابلاً للاكتشاف
إلى مكتبة واجهة مستخدم السيارة. يجب تصدير الموفر حتى يمكن الاستعلام عنه على
وقت التشغيل. بالإضافة إلى ذلك، إذا تم ضبط السمة enabled
على false
، يكون الإعداد التلقائي
تنفيذ السياسة بدلاً من تنفيذ المكوّن الإضافي. المحتوى
لا يجب أن تكون فئة مزود الخدمة موجودة. في هذه الحالة، تأكد من إضافة
tools:ignore="MissingClass"
إلى تعريف مقدّم الخدمة. الاطّلاع على العيّنة
إدخال في البيان أدناه:
<application>
<provider
android:name="com.android.car.ui.plugin.PluginNameProvider"
android:authorities="com.android.car.ui.plugin"
android:enabled="false"
android:exported="true"
tools:ignore="MissingClass"/>
</application>
وأخيرًا، كإجراء أمني، وقِّع تطبيقك.
المكوّنات الإضافية كمكتبة مشتركة
على عكس مكتبات Android الثابتة التي يتم تجميعها مباشرةً في التطبيقات، يتم تجميع مكتبات Android المشتركة في حِزمة APK مستقلة تتم الإشارة إليها. تطبيقات أخرى أثناء وقت التشغيل
تحتوي المكوّنات الإضافية التي يتم تنفيذها كمكتبة مشتركة في Android على فصولها تتم إضافته تلقائيًا إلى أداة تحميل الفئات المشتركة بين التطبيقات. عندما يكون التطبيق الذي يستخدم مكتبة واجهة مستخدم السيارة لتحديد التبعية لوقت التشغيل على المكتبة المشتركة للمكوّن الإضافي، يمكن لأداة تحميل الفئات الوصول إلى فئات المكتبة المشتركة للمكوّن الإضافي. تم تنفيذ المكوّنات الإضافية لأنّ تطبيقات Android العادية (وليست مكتبة مشتركة) يمكن أن تؤثّر سلبًا في برودة التطبيق أوقات البدء.
تنفيذ المكتبات المشتركة وإنشاؤها
يشبه تطوير مكتبات Android المشتركة إلى حدٍ كبيرٍ نظام Android العادي التطبيقات، مع بعض الاختلافات الرئيسية.
- استخدام العلامة
library
ضمن العلامةapplication
مع حزمة المكونات الإضافية الاسم في بيان تطبيق المكون الإضافي:
<application>
<library android:name="com.chassis.car.ui.plugin" />
...
</application>
- ضبط قاعدة إصدار
android_app
(Android.bp
) في تطبيق Sayg باستخدام AAPT علامةshared-lib
، التي تُستخدم لإنشاء مكتبة مشتركة:
android_app {
...
aaptflags: ["--shared-lib"],
...
}
التبعيات في المكتبات المشتركة
لكل تطبيق على النظام الذي يستخدم مكتبة واجهة مستخدم السيارة، قم بتضمين ملف
uses-library
في بيان التطبيق ضمن
علامة application
باسم حزمة المكون الإضافي:
<manifest>
<application
android:name=".MyApp"
...>
<uses-library android:name="com.chassis.car.ui.plugin" android:required="false"/>
...
</application>
</manifest>
تثبيت مكوّن إضافي
يجب أن يتم تثبيت المكوّنات الإضافية مسبقًا على قسم النظام من خلال تضمين الوحدة.
في PRODUCT_PACKAGES
. يمكن تحديث الحزمة المثبتة مسبقًا بشكل مشابه
أي تطبيق آخر مثبَّت.
في حال تحديث مكوّن إضافي حالي على النظام، تتيح أي تطبيقات تستخدم هذا المكوّن الإضافي يتم إغلاقه تلقائيًا. بعد إعادة فتح المستخدم، ستظهر التغييرات المعدَّلة. إذا لم يكن التطبيق قيد التشغيل، فسيتم تحديث التطبيق عند بدئه في المرة التالية المكون الإضافي.
عند تثبيت أحد المكونات الإضافية باستخدام "استوديو Android"، هناك بعض الميزات الإضافية التي يجب مراعاتها. في وقت الكتابة، كان هناك خطأ في عملية تثبيت تطبيق "استوديو Android" التي تؤدي إلى إجراء تحديثات للمكوّن الإضافي لا تدخل حيز التنفيذ. يمكن حل هذه المشكلة من خلال تحديد الخيار التثبيت دائمًا باستخدام مدير الحِزم (إيقاف نشر التحسينات على الإصدار 11 من نظام Android والإصدارات الأحدث) في تكوين تصميم المكون الإضافي.
بالإضافة إلى ذلك، عند تثبيت المكوّن الإضافي، يُبلغ "استوديو Android" عن خطأ مفاده لا يمكن العثور على نشاط رئيسي لإطلاقه. وهذا أمر متوقع، حيث لا يكون للمكون الإضافي يكون لديك أي أنشطة (باستثناء الغرض الفارغ المستخدَم لتحقيق هدف). إلى إزالة الخطأ، غيِّر خيار التشغيل إلى لا شيء في الإصدار. التكوين.
الشكل 1. إعداد Android Studio للمكوّن الإضافي
المكوّن الإضافي للخادم الوكيل
تخصيص التطبيقات التي تستخدم مكتبة "واجهة مستخدم السيارة" RRO تستهدف كل تطبيق محدد يتم تعديله، بما في ذلك عندما تكون عمليات التخصيص متطابقة بين التطبيقات. وهذا يعني أن سجل الموارد المنتظم (RRO) التطبيق. تعرَّف على التطبيقات التي تستخدم مكتبة واجهة مستخدم السيارة.
يعد المكوّن الإضافي لخادم وكيل مكتبة واجهة مستخدم السيارة مثالاً للمكوّن الإضافي المكتبة المشتركة التي تفوض عمليات تنفيذ مكوناتها إلى الخادم من مكتبة واجهة مستخدم السيارة. يمكن استهداف هذا المكون الإضافي باستخدام RRO، والذي يمكن تُستخدم كنقطة تخصيص واحدة للتطبيقات التي تستخدم مكتبة واجهة مستخدم السيارة دون الحاجة إلى تنفيذ مكون إضافي وظيفي. لمزيد من المعلومات عن RROs، راجع تغيير قيمة موارد التطبيق في وقت التشغيل.
المكوّن الإضافي للخادم الوكيل هو مثال فقط ونقطة بداية لإجراء التخصيص باستخدام أحد المكونات الإضافية. للتخصيص خارج قوائم الموارد المنتظمة (RRO)، يمكن للمرء تنفيذ مجموعة فرعية من المكون الإضافي واستخدام المكون الإضافي للخادم الوكيل في الباقي، أو تطبيق كل المكونات والمكونات بالكامل من البداية.
على الرغم من أن المكوّن الإضافي للخادم الوكيل يوفر نقطة واحدة من تخصيص RRO للتطبيقات، ستظل التطبيقات التي تتوقف عن استخدام المكون الإضافي تتطلب RRO يستهدف التطبيق نفسه.
تنفيذ واجهات برمجة التطبيقات للمكوّن الإضافي
نقطة الدخول الرئيسية للمكون الإضافي هي
صف واحد (com.android.car.ui.plugin.PluginVersionProviderImpl
). يجب أن تكون جميع المكوّنات الإضافية
تضمين فئة بهذا الاسم واسم الحزمة بالضبط. يجب أن يضم هذا الصف
أداة إنشاء تلقائية وتنفيذ واجهة PluginVersionProviderOEMV1
.
يجب أن تعمل مكونات CarUi الإضافية مع التطبيقات الأقدم أو الأحدث من المكوِّن الإضافي. إلى
يسهّل ذلك، يتم إنشاء إصدارات لجميع واجهات برمجة تطبيقات المكوّنات الإضافية من خلال V#
في نهاية
اسم الفئة. إذا تم طرح إصدار جديد من مكتبة واجهة مستخدم السيارة بميزات جديدة،
فهي جزء من إصدار V2
من المكوِّن. تقوم مكتبة واجهة مستخدم السيارة
الأفضل لجعل الميزات الجديدة تعمل في نطاق مكون إضافي قديم.
على سبيل المثال، عن طريق تحويل نوع جديد من الأزرار في شريط الأدوات إلى MenuItems
.
ومع ذلك، لا يمكن لتطبيق به إصدار قديم من مكتبة واجهة مستخدم السيارة التكيف مع المكون الإضافي المكتوب مقابل واجهات برمجة التطبيقات الأحدث. لحل هذه المشكلة، نسمح للمكونات الإضافية عمليات تنفيذ مختلفة لأنفسها بناءً على إصدار واجهة برمجة تطبيقات المصنّع الأصلي للجهاز المتوافقة مع التطبيقات.
يتضمّن PluginVersionProviderOEMV1
طريقة واحدة:
Object getPluginFactory(int maxVersion, Context context, String packageName);
تُرجع هذه الطريقة كائنًا ينفذ أعلى إصدار من
PluginFactoryOEMV#
معتمد في المكون الإضافي، بينما لا تزال أقل من أو
يساوي maxVersion
. إذا لم يتم تنفيذ أحد المكونات الإضافية
PluginFactory
بهذه الطريقة القديمة، قد تعرض الدالة null
، وفي هذه الحالة تكون السمة static-
واستخدام التنفيذ المرتبط لمكونات CarUi.
للحفاظ على التوافق مع التطبيقات التي يتم تجميعها استنادًا إلى تلك الأنظمة
للإصدارات القديمة من مكتبة Car Ui الثابتة، يوصى بدعم
maxVersion
من 2 و5 وأعلى من داخل تنفيذ المكوّن الإضافي
للفئة PluginVersionProvider
. ولا يمكن استخدام الإصدارات 1 و3 و4. بالنسبة
مزيد من المعلومات، راجع
PluginVersionProviderImpl
PluginFactory
هي الواجهة التي تنشئ جميع CarUi الآخر
والمكونات. وتحدّد أيضًا إصدار واجهاتها التي يجب استخدامها. في حال حذف
لأن المكون الإضافي لا يسعى إلى تنفيذ أي من هذه المكونات، فقد يعرض
null
في دالة الإنشاء (باستثناء شريط الأدوات، الذي يحتوي على
دالة customizesBaseLayout()
منفصلة).
تفرض pluginFactory
حدودًا على إصدارات مكوّنات CarUi التي يمكن استخدامها.
على سبيل المثال، لن يتوفّر أبدًا pluginFactory
يمكن إنشاؤه.
الإصدار 100 من Toolbar
وأيضًا الإصدار 1 من RecyclerView
،
سيكون ضمانًا ضئيلاً أن تكون مجموعة متنوعة من إصدارات المكونات
معًا. لاستخدام الإصدار 100 من شريط الأدوات، على مطوّري البرامج
توفير تنفيذ لإصدار pluginFactory
يؤدي إلى إنشاء
شريط الأدوات الإصدار 100، والذي يحد بعد ذلك من الخيارات المتاحة على إصدارات
والمكونات التي يمكن إنشاؤها. قد لا تكون إصدارات المكونات الأخرى
مساويًا له، على سبيل المثال، قد ينتج عن pluginFactoryOEMV100
ToolbarControllerOEMV100
وRecyclerViewOEMV70
.
شريط الأدوات
التخطيط الأساسي
شريط الأدوات و"التنسيق الأساسي" ارتباطًا وثيقًا، وبالتالي فإن الدالة
التي تنشئ شريط الأدوات تسمى installBaseLayoutAround
. تشير رسالة الأشكال البيانية
التنسيق الأساسي
هو مفهوم يسمح بوضع شريط الأدوات في أي مكان حول قالب
المحتوى، للسماح بشريط الأدوات أعلى/أسفل التطبيق، عموديًا
على طول الجوانب، أو حتى شريط أدوات دائري يغطي التطبيق بالكامل. هذا هو
بتمرير طريقة عرض إلى installBaseLayoutAround
لشريط الأدوات/القاعدة
وتخطيطه لكي يلتف حوله.
يجب أن يأخذ المكوِّن الإضافي العرض المقدم، ويفصله عن الأصل، ويضخم
التخطيط الخاص بالمكون الإضافي في نفس الفهرس الرئيسي وبنفس
LayoutParams
باعتباره العرض الذي تم فصله للتو، ثم إعادة إرفاق العرض
في مكان ما داخل التخطيط تم تضخيمه للتو. سيبدأ التخطيط المضخَّم
سيحتوي على شريط الأدوات، إذا طلبه التطبيق.
يمكن للتطبيق طلب تنسيق أساسي بدون استخدام شريط الأدوات. إذا كان الأمر كذلك،
يجب أن تعرض الدالة installBaseLayoutAround
قيمة خالية. بالنسبة لمعظم المكونات الإضافية، هذا كل ما
ولكن إذا رغب مؤلف المكون الإضافي في تطبيقه، مثلاً زينة
حول حافة التطبيق، لا يزال من الممكن القيام بذلك باستخدام التخطيط الأساسي. هذه
والزخارف مفيدة بشكل خاص للأجهزة ذات الشاشات غير المستطيلة،
يمكنها دفع التطبيق إلى مساحة مستطيلة وإضافة انتقالات نظيفة إلى
المساحة غير المستطيلة.
تم أيضًا تجاوز installBaseLayoutAround
لـ Consumer<InsetsOEMV1>
. هذا النمط
يمكن استخدام المستهلك لإبلاغ التطبيق بأن المكون الإضافي جزء
تغطية محتوى التطبيق (باستخدام شريط الأدوات أو غير ذلك) التطبيق سوف
الاستمرار في الرسم في هذه المساحة، مع الحفاظ على أي تفاعل مهم مع المستخدم
مكونًا منها. يُستخدم هذا التأثير في التصميم المرجعي لدينا،
شريط الأدوات شبه شفاف، ولها قوائم يتم تمريرها تحتها. إذا كانت هذه الميزة
لم يتم تنفيذه، فسيتم تعليق العنصر الأول في القائمة أسفل شريط الأدوات
ولا يمكن النقر عليه إذا لم يكن هذا التأثير مطلوبًا، يمكن للمكون الإضافي تجاهل
المستهلك:
الشكل 2. تمرير المحتوى أسفل شريط الأدوات
من منظور التطبيق، عندما يرسل المكوّن الإضافي مساحات داخلية جديدة، سيتلقّى
من أي أنشطة أو أجزاء تؤدي إلى تنفيذ InsetsChangedListener
. في حال حذف
لا يؤدي النشاط أو الجزء إلى تنفيذ InsetsChangedListener
، فواجهة برمجة التطبيقات Car Ui
ستتعامل المكتبة مع الإدخالات تلقائيًا من خلال تطبيق الإدخالات كمساحة متروكة على
Activity
أو FragmentActivity
التي تحتوي على الجزء. ولا تسمح المكتبة
وتطبيق المجموعات الداخلية افتراضيًا على الأجزاء. في ما يلي مثال لمقتطف
عملية تنفيذ تُطبّق الإدخالات كمساحة متروكة في RecyclerView
في
التطبيق:
public class MainActivity extends Activity implements InsetsChangedListener {
@Override
public void onCarUiInsetsChanged(Insets insets) {
CarUiRecyclerView rv = requireViewById(R.id.recyclerview);
rv.setPadding(insets.getLeft(), insets.getTop(),
insets.getRight(), insets.getBottom());
}
}
وأخيرًا، يظهر تلميح fullscreen
للمكوّن الإضافي، ويتم استخدامه للإشارة إلى ما إذا
فالعرض الذي ينبغي أن يتم التفافه يشغل التطبيق بأكمله أو قسمًا صغيرًا فقط.
ويمكن استخدامه لتجنّب تطبيق بعض الزخارف على طول الحافة
إلا إذا ظهرت على حافة الشاشة بأكملها، العينة
تطبيق يستخدم تخطيطات أساسية غير بملء الشاشة هو "الإعدادات"، حيث يكون كل جزء من
تخطيط ثنائي اللوحة له شريط أدوات خاص به.
لأنّه من المتوقّع أن تعرض الدالة installBaseLayoutAround
قيمة خالية عند
toolbarEnabled
هي false
، حيث يشير المكون الإضافي إلى أنها لا
تريد تخصيص التخطيط الأساسي، يجب أن يعرض false
من
customizesBaseLayout
يجب أن يحتوي التنسيق الأساسي على FocusParkingView
وFocusArea
دعم عناصر التحكم الدوارة. ويمكن حذف طرق العرض هذه على الأجهزة التي
لا تتوافق مع وحدة القياس الدوّارة. يتم تنفيذ FocusParkingView/FocusAreas
في
مكتبة CarUi ثابتة، لذلك يتم استخدام setRotaryFactories
لتوفير المصانع
لإنشاء طرق العرض من السياقات.
يجب أن تكون السياقات المستخدمة لإنشاء طرق عرض التركيز هي سياق المصدر، وليس
لسياق المكون الإضافي. يجب أن يكون FocusParkingView
هو الأقرب إلى العرض الأول.
في الشجرة قدر الإمكان، لأنّ ذلك هو ما يتم التركيز عليه عندما يكون
أن يكون التركيز عليها مرئيًا للمستخدم. على "FocusArea
" التفاف شريط الأدوات في
التصميم الأساسي للإشارة إلى أنها منطقة دفع دوار. إذا لم يكن FocusArea
المقدمة، لا يتمكن المستخدم من الانتقال إلى أي أزرار في شريط الأدوات
وحدة التحكّم الدوارة.
وحدة التحكم في شريط الأدوات
يجب أن تكون قيمة ToolbarController
الفعلية التي تم إرجاعها أكثر وضوحًا
تنفيذها أكثر من التخطيط الأساسي. وتتمثل وظيفتها في نقل المعلومات التي يتم تمريرها إلى
البيانات وعرضها في التخطيط الأساسي. راجع Javadoc للحصول على معلومات عن
لمعظم الطرق. وتتم مناقشة بعض الطرق الأكثر تعقيدًا أدناه.
تُستخدم getImeSearchInterface
لعرض نتائج البحث في لوحة المفاتيح (IME)
نافذة. يمكن الاستفادة من ذلك في عرض/تحريك نتائج البحث بجانب الزر
لوحة المفاتيح، مثلاً إذا كانت لوحة المفاتيح تشغل نصف الشاشة فقط. معظم
تنفيذ الوظيفة في مكتبة CarUi الثابتة، يفحص نظام
في المكون الإضافي طرق للمكتبة الثابتة
TextView
وonPrivateIMECommand
طلبات معاودة الاتصال. ولدعم ذلك، يمكن للمكون الإضافي
يجب استخدام فئة فرعية TextView
تلغي السمة onPrivateIMECommand
وتمرر
المكالمة الموجّهة إلى المستمع المقدَّم على أنّها TextView
في شريط البحث.
يعرض "setMenuItems
" عناصر MenuItems على الشاشة، ولكن سيتم تسميتها
في كثير من الأحيان بشكل مفاجئ. ونظرًا لأن واجهة برمجة التطبيقات للمكوّن الإضافي لـ MenuItems غير قابلة للتغيير، عندما
تم تغيير MenuItem، وبالتالي سيتم إجراء مكالمة setMenuItems
جديدة بالكامل. يمكن أن
يحدث لشيء تافه، مثل نقر المستخدم على زر "التبديل" في ListItem، وأن
النقر فوق ما تسبب في تبديل المفتاح. لأسباب تتعلق بالأداء والرسوم المتحركة،
لذا ننصح بحساب الفرق بين البيانات القديمة والجديدة
قائمة عناصر القائمة، وتحديث طرق العرض التي تغيرت بالفعل فقط. عناصر قائمة الطعام
توفير حقل key
يمكنه المساعدة في ذلك، حيث يجب أن يكون المفتاح مماثلاً
في مكالمات مختلفة إلى "setMenuItems
" لعنصر MenuItem نفسه.
عرض نمط التطبيق
AppStyledView
هي حاوية لملف شخصي غير مخصّص على الإطلاق. أُنشأها جون هنتر، الذي كان متخصصًا
يمكن استخدامها لتوفير حدود حول هذا العرض تُبرز عن
باقي التطبيق، وتوضح للمستخدم أن هذا نوع مختلف من
من واجهة pyplot. يتم توفير العرض الذي يلتفه AppStyledView في
setContent
يمكن أن يحتوي "AppStyledView
" أيضًا على زر الرجوع أو الإغلاق
يطلبه التطبيق.
لا يُدرج AppStyledView
طرق العرض مباشرةً في التدرج الهرمي لطريقة العرض.
مثلما تفعل installBaseLayoutAround
، فإنه بدلاً من ذلك يعرض طريقة العرض إلى
مكتبة ثابتة من خلال getView
، والتي تقوم بعد ذلك بالإدراج. يتم تعيين المنصب
يمكن أيضًا التحكم في حجم AppStyledView
من خلال تنفيذ
getDialogWindowLayoutParam
السياقات
يجب توخّي الحذر في المكوّن الإضافي عند استخدام السياقات، حيث يوجد كل من المكون الإضافي
"المصدر" والسياقات. يتم تقديم سياق المكون الإضافي كوسيطة إلى
getPluginFactory
، وهو السياق الوحيد الذي
موارد المكون الإضافي فيها. وهذا يعني أنه السياق الوحيد الذي يمكن استخدامه
تضخيم التخطيطات في المكون الإضافي.
ومع ذلك، قد لا يتم ضبط الإعدادات الصحيحة على سياق المكوّن الإضافي. إلى
الحصول على التكوين الصحيح، ونوفر سياقات المصدر بالطرق التي تنشئ
والمكونات. يكون سياق المصدر عادةً نشاطًا، ولكن في بعض الحالات قد
أن يكون أيضًا أحد مكونات الخدمات أو أي مكونات أخرى في Android. لاستخدام الإعدادات من
سياق المصدر مع الموارد من سياق المكون الإضافي، يجب تضمين سياق جديد
تم إنشاؤه باستخدام createConfigurationContext
. إذا لم تكن التهيئة الصحيحة
مخالفة الوضع المتشدد في Android، ما قد يؤدي إلى تضخم طرق العرض
ذات الأبعاد الصحيحة.
Context layoutInflationContext = pluginContext.createConfigurationContext(
sourceContext.getResources().getConfiguration());
تغييرات الوضع
يمكن لبعض المكوّنات الإضافية أن تدعم أوضاعًا متعددة لمكوناتها، مثل وضع رياضي أو وضع اقتصادي تبدو مختلفة بصريًا لا يوجد للحصول على دعم مدمج لمثل هذه الوظيفة في CarUi، ولكن لا يوجد شيء المكون الإضافي من تنفيذه داخليًا بالكامل. يمكن للمكون الإضافي مراقبة والظروف التي تريدها لمعرفة وقت التبديل بين الأوضاع، مثل والاستماع إلى البث. لا يمكن للمكوّن الإضافي تغيير الإعداد لتغيير الأوضاع، لا يُنصح بالاعتماد على تغييرات الضبط على أي حال، حيث يكون التحديث اليدوي لمظهر كل مكون أكثر سلاسة إلى المستخدم كما تسمح أيضًا بعمليات الانتقال غير الممكنة تغييرات التهيئة.
Jetpack Compose
يمكن تنفيذ المكوّنات الإضافية باستخدام Jetpack Compose، ولكن هذا الإصدار مستوى ألفا الجديدة ولا ينبغي اعتبارها مستقرة.
يمكن للمكونات الإضافية استخدام
ComposeView
لإنشاء مساحة عرض تتوفّر فيها ميزة "الكتابة" سيكون ComposeView
هذا
ما يتم عرضه من إلى التطبيق من خلال الطريقة getView
في المكوّنات.
من المشاكل الرئيسية في استخدام ComposeView
أنها تضبط العلامات في الملف الشخصي الجذر.
في التخطيط لتخزين المتغيرات العمومية التي تتم مشاركتها عبر
طرق ComposeView مختلفة في التسلسل الهرمي ونظرًا لأن معرّفات موارد المكون الإضافي غير متوفرة
مساحة اسم منفصلة عن معرّف التطبيق، فقد يؤدي ذلك إلى حدوث تعارض عند
التطبيق وعلامات مجموعة المكون الإضافي في العرض نفسه. مخصّص
ComposeViewWithLifecycle
التي تنقل هذه المتغيّرات العمومية لأسفل إلى
تم توفير ComposeView
أدناه. ومرة أخرى، لا يجب اعتبار هذا الفارق مستقرًا.
ComposeViewWithLifecycle
:
class ComposeViewWithLifecycle @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr),
LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner {
private val lifeCycle = LifecycleRegistry(this)
private val modelStore = ViewModelStore()
private val savedStateRegistryController = SavedStateRegistryController.create(this)
private var composeView: ComposeView? = null
private var content = @Composable {}
init {
ViewTreeLifecycleOwner.set(this, this)
ViewTreeViewModelStoreOwner.set(this, this)
ViewTreeSavedStateRegistryOwner.set(this, this)
compositionContext = createCompositionContext()
}
fun setContent(content: @Composable () -> Unit) {
this.content = content
composeView?.setContent(content)
}
override fun getLifecycle(): Lifecycle {
return lifeCycle
}
override fun getViewModelStore(): ViewModelStore {
return modelStore
}
override fun getSavedStateRegistry(): SavedStateRegistry {
return savedStateRegistryController.savedStateRegistry
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
savedStateRegistryController.performRestore(Bundle())
lifeCycle.currentState = Lifecycle.State.RESUMED
composeView = ComposeView(context)
composeView?.setContent(content)
addView(composeView, LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
lifeCycle.currentState = Lifecycle.State.DESTROYED
modelStore.clear()
removeAllViews()
composeView = null
}
// Exact copy of View.createCompositionContext() in androidx's WindowRecomposer.android.kt
private fun createCompositionContext(): CompositionContext {
val currentThreadContext = AndroidUiDispatcher.CurrentThread
val pausableClock = currentThreadContext[MonotonicFrameClock]?.let {
PausableMonotonicFrameClock(it).apply { pause() }
}
val contextWithClock = currentThreadContext + (pausableClock ?: EmptyCoroutineContext)
val recomposer = Recomposer(contextWithClock)
val runRecomposeScope = CoroutineScope(contextWithClock)
val viewTreeLifecycleOwner = checkNotNull(ViewTreeLifecycleOwner.get(this)) {
"ViewTreeLifecycleOwner not found from $this"
}
viewTreeLifecycleOwner.lifecycle.addObserver(
LifecycleEventObserver { _, event ->
@Suppress("NON_EXHAUSTIVE_WHEN")
when (event) {
Lifecycle.Event.ON_CREATE ->
// Undispatched launch since we've configured this scope
// to be on the UI thread
runRecomposeScope.launch(start = CoroutineStart.UNDISPATCHED) {
recomposer.runRecomposeAndApplyChanges()
}
Lifecycle.Event.ON_START -> pausableClock?.resume()
Lifecycle.Event.ON_STOP -> pausableClock?.pause()
Lifecycle.Event.ON_DESTROY -> {
recomposer.cancel()
}
}
}
)
return recomposer
}
// TODO: ComposeViewWithLifecycle should handle saving state and other lifecycle things
// override fun onSaveInstanceState(): Parcelable? {
// val superState = super.onSaveInstanceState()
// val bundle = Bundle()
// savedStateRegistryController.performSave(bundle)
// }
}