গাড়ী UI প্লাগইন

রানটাইম রিসোর্স ওভারলে (RROs) ব্যবহার না করে Car UI লাইব্রেরিতে কম্পোনেন্ট কাস্টমাইজেশনের সম্পূর্ণ বাস্তবায়ন তৈরি করতে Car UI লাইব্রেরি প্লাগইন ব্যবহার করুন। RROs আপনাকে Car UI লাইব্রেরির উপাদানগুলির শুধুমাত্র XML সংস্থানগুলি পরিবর্তন করতে সক্ষম করে, যা আপনি কাস্টমাইজ করতে পারেন তা সীমাবদ্ধ করে৷

একটি প্লাগইন তৈরি করুন

একটি কার UI লাইব্রেরি প্লাগইন হল একটি APK যাতে এমন ক্লাস রয়েছে যা প্লাগইন API- এর একটি সেট প্রয়োগ করে। প্লাগইন এপিআই একটি স্ট্যাটিক লাইব্রেরি হিসাবে একটি প্লাগইনে কম্পাইল করা যেতে পারে।

সুং এবং গ্রেডলে উদাহরণ দেখুন:

সুং

এই Soong উদাহরণ বিবেচনা করুন:

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" প্লাগইনটিকে Car UI লাইব্রেরিতে আবিষ্কারযোগ্য করে তোলে। প্রদানকারীকে রপ্তানি করতে হবে যাতে রানটাইমে এটি জিজ্ঞাসা করা যায়। এছাড়াও, যদি 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>

অবশেষে, নিরাপত্তা ব্যবস্থা হিসাবে, আপনার অ্যাপে স্বাক্ষর করুন

শেয়ার্ড লাইব্রেরি হিসেবে প্লাগইন

অ্যান্ড্রয়েড স্ট্যাটিক লাইব্রেরিগুলির বিপরীতে যা সরাসরি অ্যাপগুলিতে কম্পাইল করা হয়, অ্যান্ড্রয়েড শেয়ার করা লাইব্রেরিগুলি একটি স্বতন্ত্র APK-এ কম্পাইল করা হয় যা রানটাইমে অন্যান্য অ্যাপ দ্বারা উল্লেখ করা হয়।

যে প্লাগইনগুলি একটি Android শেয়ার্ড লাইব্রেরি হিসাবে প্রয়োগ করা হয় সেগুলির ক্লাসগুলি স্বয়ংক্রিয়ভাবে অ্যাপগুলির মধ্যে ভাগ করা ক্লাসলোডারে যুক্ত হয়৷ যখন কার UI লাইব্রেরি ব্যবহার করে এমন একটি অ্যাপ প্লাগইন শেয়ার্ড লাইব্রেরির উপর রানটাইম নির্ভরতা নির্দিষ্ট করে, তখন এর ক্লাসলোডার প্লাগইন শেয়ার করা লাইব্রেরির ক্লাস অ্যাক্সেস করতে পারে। সাধারণ অ্যান্ড্রয়েড অ্যাপ (একটি ভাগ করা লাইব্রেরি নয়) হিসাবে প্রয়োগ করা প্লাগইনগুলি অ্যাপের ঠান্ডা শুরুর সময়কে নেতিবাচকভাবে প্রভাবিত করতে পারে।

ভাগ করা লাইব্রেরি বাস্তবায়ন এবং নির্মাণ করুন

অ্যান্ড্রয়েড শেয়ার করা লাইব্রেরিগুলির সাথে বিকাশ করা অনেকটা সাধারণ অ্যান্ড্রয়েড অ্যাপের মতো, কয়েকটি মূল পার্থক্য সহ।

  • আপনার প্লাগইনের অ্যাপ ম্যানিফেস্টে প্লাগইন প্যাকেজ নামের সাথে application ট্যাগের অধীনে library ট্যাগ ব্যবহার করুন:
    <application>
        <library android:name="com.chassis.car.ui.plugin" />
        ...
    </application>
  • আপনার Soong android_app বিল্ড নিয়ম ( Android.bp ) কনফিগার করুন AAPT ফ্ল্যাগ shared-lib , যা একটি শেয়ার্ড লাইব্রেরি তৈরি করতে ব্যবহৃত হয়:
android_app {
  ...
  aaptflags: ["--shared-lib"],
  ...
}

ভাগ করা লাইব্রেরির উপর নির্ভরতা

কার UI লাইব্রেরি ব্যবহার করে এমন সিস্টেমের প্রতিটি অ্যাপের জন্য, প্লাগইন প্যাকেজ নামের application ট্যাগের অধীনে অ্যাপ ম্যানিফেস্টে uses-library ট্যাগ অন্তর্ভুক্ত করুন:

<manifest>
  <application
      android:name=".MyApp"
      ...>
    <uses-library android:name="com.chassis.car.ui.plugin" android:required="false"/>
    ...
  </application>
</manifest>

একটি প্লাগইন ইনস্টল করুন

PRODUCT_PACKAGES এ মডিউলটি অন্তর্ভুক্ত করে সিস্টেম পার্টিশনে প্লাগইনগুলি অবশ্যই আগে থেকে ইনস্টল করা উচিত। প্রি-ইনস্টল করা প্যাকেজটি অন্য যেকোন ইন্সটল করা অ্যাপের মতোই আপডেট করা যেতে পারে।

আপনি যদি সিস্টেমে একটি বিদ্যমান প্লাগইন আপডেট করেন তবে সেই প্লাগইন ব্যবহার করে যেকোন অ্যাপ স্বয়ংক্রিয়ভাবে বন্ধ হয়ে যাবে। ব্যবহারকারীর দ্বারা পুনরায় খোলার পরে, তাদের আপডেট করা পরিবর্তনগুলি রয়েছে৷ অ্যাপটি চলমান না থাকলে, পরের বার এটি চালু হলে এতে আপডেট করা প্লাগইন থাকে।

অ্যান্ড্রয়েড স্টুডিওর সাথে একটি প্লাগইন ইনস্টল করার সময়, কিছু অতিরিক্ত বিবেচনা বিবেচনা করতে হবে। লেখার সময়, অ্যান্ড্রয়েড স্টুডিও অ্যাপ ইনস্টলেশন প্রক্রিয়ায় একটি বাগ রয়েছে যার কারণে একটি প্লাগইন আপডেট কার্যকর হয় না। প্লাগইনের বিল্ড কনফিগারেশনে প্যাকেজ ম্যানেজারের সাথে সর্বদা ইনস্টল করুন (Android 11 এবং পরবর্তীতে অপ্টিমাইজেশানগুলিকে নিষ্ক্রিয় করে) বিকল্পটি নির্বাচন করে এটি ঠিক করা যেতে পারে।

উপরন্তু, প্লাগইন ইনস্টল করার সময়, অ্যান্ড্রয়েড স্টুডিও একটি ত্রুটি রিপোর্ট করে যে এটি চালু করার জন্য একটি প্রধান কার্যকলাপ খুঁজে পায় না। এটি প্রত্যাশিত, কারণ প্লাগইনটির কোনো কার্যক্রম নেই (একটি অভিপ্রায় সমাধান করতে ব্যবহৃত খালি অভিপ্রায় ব্যতীত)। ত্রুটিটি দূর করতে, বিল্ড কনফিগারেশনে লঞ্চ বিকল্পটি কিছুই নয় এ পরিবর্তন করুন।

প্লাগইন অ্যান্ড্রয়েড স্টুডিও কনফিগারেশন চিত্র 1. প্লাগইন অ্যান্ড্রয়েড স্টুডিও কনফিগারেশন

প্রক্সি প্লাগইন

কার UI লাইব্রেরি ব্যবহার করে অ্যাপ্লিকেশানগুলির কাস্টমাইজেশনের জন্য একটি RRO প্রয়োজন যা প্রতিটি নির্দিষ্ট অ্যাপকে লক্ষ্য করে যা সংশোধন করা হবে, সহ যখন কাস্টমাইজেশনগুলি অ্যাপ জুড়ে অভিন্ন হয়। এর মানে প্রতি অ্যাপের জন্য একটি RRO প্রয়োজন। কোন অ্যাপগুলি কার UI লাইব্রেরি ব্যবহার করে তা দেখুন।

কার UI লাইব্রেরি প্রক্সি প্লাগইন হল একটি উদাহরণ প্লাগইন শেয়ার্ড লাইব্রেরি যা কার UI লাইব্রেরির স্ট্যাটিক সংস্করণে এর উপাদান বাস্তবায়ন অর্পণ করে৷ এই প্লাগইনটিকে একটি RRO দিয়ে টার্গেট করা যেতে পারে, যা একটি কার্যকরী প্লাগইন বাস্তবায়নের প্রয়োজন ছাড়াই কার UI লাইব্রেরি ব্যবহার করে এমন অ্যাপগুলির জন্য কাস্টমাইজেশনের একক পয়েন্ট হিসাবে ব্যবহার করা যেতে পারে। RRO সম্পর্কে আরও তথ্যের জন্য, রানটাইমে অ্যাপের সম্পদের মান পরিবর্তন দেখুন।

একটি প্লাগইন ব্যবহার করে কাস্টমাইজেশন করার জন্য প্রক্সি প্লাগইনটি শুধুমাত্র একটি উদাহরণ এবং শুরুর পয়েন্ট। RRO-এর বাইরে কাস্টমাইজেশনের জন্য, কেউ প্লাগইন উপাদানগুলির একটি উপসেট প্রয়োগ করতে পারে এবং বাকিগুলির জন্য প্রক্সি প্লাগইন ব্যবহার করতে পারে, অথবা সমস্ত প্লাগইন উপাদান সম্পূর্ণরূপে স্ক্র্যাচ থেকে প্রয়োগ করতে পারে।

যদিও প্রক্সি প্লাগইন অ্যাপগুলির জন্য RRO কাস্টমাইজেশনের একক পয়েন্ট প্রদান করে, যে অ্যাপগুলি প্লাগইন ব্যবহার করা থেকে অপ্ট-আউট করে তার এখনও একটি RRO প্রয়োজন হবে যা সরাসরি অ্যাপটিকে লক্ষ্য করে।

প্লাগইন এপিআই প্রয়োগ করুন

প্লাগইনের প্রধান এন্ট্রিপয়েন্ট হল com.android.car.ui.plugin.PluginVersionProviderImpl ক্লাস। সমস্ত প্লাগইন এই সঠিক নাম এবং প্যাকেজ নামের একটি ক্লাস অন্তর্ভুক্ত করা আবশ্যক. এই শ্রেণীর একটি ডিফল্ট কনস্ট্রাক্টর থাকতে হবে এবং PluginVersionProviderOEMV1 ইন্টারফেস প্রয়োগ করতে হবে।

CarUi প্লাগইনগুলিকে প্লাগইনের চেয়ে পুরানো বা নতুন অ্যাপগুলির সাথে কাজ করতে হবে৷ এই সুবিধার জন্য, সমস্ত প্লাগইন API তাদের ক্লাসের নামের শেষে একটি V# দিয়ে সংস্করণ করা হয়। যদি কার UI লাইব্রেরির একটি নতুন সংস্করণ নতুন বৈশিষ্ট্য সহ প্রকাশ করা হয় তবে সেগুলি উপাদানটির V2 সংস্করণের অংশ। কার UI লাইব্রেরি একটি পুরানো প্লাগইন উপাদানের সুযোগের মধ্যে নতুন বৈশিষ্ট্যগুলিকে কাজ করার জন্য সর্বোত্তম চেষ্টা করে৷ উদাহরণস্বরূপ, টুলবারে একটি নতুন ধরনের বোতামকে MenuItems এ রূপান্তর করে।

যাইহোক, কার UI লাইব্রেরির একটি পুরানো সংস্করণ সহ একটি অ্যাপ নতুন এপিআইগুলির বিরুদ্ধে লেখা একটি নতুন প্লাগইনের সাথে মানিয়ে নিতে পারে না। এই সমস্যাটি সমাধান করার জন্য, আমরা অ্যাপস দ্বারা সমর্থিত OEM API-এর সংস্করণের উপর ভিত্তি করে প্লাগইনগুলিকে নিজেদের বিভিন্ন বাস্তবায়নের অনুমতি দিই।

PluginVersionProviderOEMV1 এর একটি পদ্ধতি রয়েছে:

Object getPluginFactory(int maxVersion, Context context, String packageName);

এই পদ্ধতিটি এমন একটি বস্তু ফেরত দেয় যা প্লাগইন দ্বারা সমর্থিত PluginFactoryOEMV# এর সর্বোচ্চ সংস্করণ প্রয়োগ করে, যদিও এখনও maxVersion এর থেকে কম বা সমান। যদি একটি প্লাগইনে পুরানো একটি PluginFactory বাস্তবায়ন না থাকে, তাহলে এটি null ফেরত দিতে পারে, এই ক্ষেত্রে CarUi উপাদানগুলির স্ট্যাটিক্যালি-লিঙ্কড বাস্তবায়ন ব্যবহার করা হয়।

স্ট্যাটিক কার Ui লাইব্রেরির পুরানো সংস্করণগুলির সাথে সংকলিত অ্যাপগুলির সাথে পিছনের সামঞ্জস্য বজায় রাখতে, আপনার প্লাগইন-এর PluginVersionProvider ক্লাসের বাস্তবায়নের মধ্যে থেকে 2, 5 এবং উচ্চতর maxVersion সমর্থন করার পরামর্শ দেওয়া হয়৷ সংস্করণ 1, 3, এবং 4 সমর্থিত নয়। আরও তথ্যের জন্য, PluginVersionProviderImpl দেখুন।

PluginFactory হল সেই ইন্টারফেস যা অন্যান্য সমস্ত CarUi উপাদান তৈরি করে। এটি তাদের ইন্টারফেসের কোন সংস্করণ ব্যবহার করা উচিত তাও নির্ধারণ করে। যদি প্লাগইন এই উপাদানগুলির কোনোটি বাস্তবায়নের চেষ্টা না করে, তবে এটি তাদের তৈরি ফাংশনে null হতে পারে (টুলবার বাদে, যার একটি পৃথক customizesBaseLayout() ফাংশন রয়েছে)।

pluginFactory CarUi উপাদানগুলির কোন সংস্করণগুলি একসাথে ব্যবহার করা যেতে পারে তা সীমাবদ্ধ করে। উদাহরণস্বরূপ, এমন একটি pluginFactory কখনই থাকবে না যা একটি Toolbar সংস্করণ 100 এবং একটি RecyclerView এর সংস্করণ 1 তৈরি করতে পারে, কারণ উপাদানগুলির বিভিন্ন সংস্করণ একসাথে কাজ করবে এমন খুব কম গ্যারান্টি থাকবে। টুলবার সংস্করণ 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 ইঙ্গিত দেওয়া হয়, যা বোঝানোর জন্য ব্যবহৃত হয় যে ভিউটি মোড়ানো উচিত পুরো অ্যাপ বা শুধুমাত্র একটি ছোট অংশ নেয়। এটি প্রান্ত বরাবর কিছু সজ্জা প্রয়োগ এড়াতে ব্যবহার করা যেতে পারে যেগুলি পুরো পর্দার প্রান্ত বরাবর প্রদর্শিত হলেই বোঝা যায়। একটি নমুনা অ্যাপ যা নন-ফুলস্ক্রিন বেস লেআউটগুলি ব্যবহার করে সেটি হচ্ছে সেটিংস, যেখানে ডুয়াল-পেন লেআউটের প্রতিটি প্যানের নিজস্ব টুলবার রয়েছে।

যেহেতু toolbarEnabled false হলে installBaseLayoutAround এর জন্য নাল ফিরে আসবে বলে প্রত্যাশিত, প্লাগইনটি নির্দেশ করার জন্য যে এটি বেস লেআউট কাস্টমাইজ করতে চায় না, এটি অবশ্যই customizesBaseLayout থেকে false ফেরত দেবে।

বেস লেআউটে অবশ্যই একটি FocusParkingView এবং একটি FocusArea থাকতে হবে যাতে রোটারি কন্ট্রোল সম্পূর্ণরূপে সমর্থন করা যায়। রোটারি সমর্থন করে না এমন ডিভাইসগুলিতে এই দৃশ্যগুলি বাদ দেওয়া যেতে পারে। FocusParkingView/FocusAreas স্ট্যাটিক CarUi লাইব্রেরিতে প্রয়োগ করা হয়, তাই একটি setRotaryFactories ব্যবহার করা হয় প্রসঙ্গ থেকে মতামত তৈরি করার জন্য কারখানাগুলি প্রদান করতে।

ফোকাস ভিউ তৈরি করতে ব্যবহৃত প্রসঙ্গগুলি অবশ্যই উত্স প্রসঙ্গ হতে হবে, প্লাগইনের প্রসঙ্গ নয়। FocusParkingView গাছের প্রথম দৃশ্যের সবচেয়ে কাছাকাছি হওয়া উচিত যতটা যুক্তিসঙ্গতভাবে সম্ভব, কারণ ব্যবহারকারীর কাছে কোন ফোকাস দৃশ্যমান না হলে এটি ফোকাস করা হয়। FocusArea অবশ্যই টুলবারটিকে বেস লেআউটে মুড়ে দিতে হবে যাতে বোঝা যায় এটি একটি ঘূর্ণমান নাজ জোন। যদি FocusArea প্রদান না করা হয়, ব্যবহারকারী ঘূর্ণমান কন্ট্রোলারের সাথে টুলবারে কোনো বোতামে নেভিগেট করতে অক্ষম।

টুলবার কন্ট্রোলার

আসল ToolbarController বেস লেআউটের চেয়ে বাস্তবায়নের জন্য অনেক বেশি সহজবোধ্য হওয়া উচিত। এর কাজ হল তার সেটারের কাছে পাঠানো তথ্য নেওয়া এবং বেস লেআউটে প্রদর্শন করা। বেশিরভাগ পদ্ধতির তথ্যের জন্য Javadoc দেখুন। আরও কিছু জটিল পদ্ধতি নিচে আলোচনা করা হল।

getImeSearchInterface IME (কীবোর্ড) উইন্ডোতে অনুসন্ধান ফলাফল দেখানোর জন্য ব্যবহৃত হয়। এটি কীবোর্ডের পাশাপাশি সার্চ ফলাফল প্রদর্শন/অ্যানিমেশন করার জন্য উপযোগী হতে পারে, উদাহরণস্বরূপ যদি কীবোর্ড শুধুমাত্র স্ক্রীনের অর্ধেক অংশ নেয়। বেশিরভাগ কার্যকারিতা স্ট্যাটিক CarUi লাইব্রেরিতে প্রয়োগ করা হয়, প্লাগইনের অনুসন্ধান ইন্টারফেসটি স্ট্যাটিক লাইব্রেরির জন্য TextView এবং onPrivateIMECommand কলব্যাক পেতে পদ্ধতি সরবরাহ করে। এটিকে সমর্থন করার জন্য, প্লাগইনটিকে একটি TextView সাবক্লাস ব্যবহার করা উচিত যা onPrivateIMECommand ওভাররাইড করে এবং প্রদত্ত শ্রোতাকে তার অনুসন্ধান বারের TextView হিসাবে কলটি পাস করে।

setMenuItems স্ক্রিনে সহজভাবে MenuItems প্রদর্শন করে, কিন্তু এটি প্রায়ই আশ্চর্যজনকভাবে বলা হবে। যেহেতু MenuItems-এর জন্য প্লাগইন API অপরিবর্তনীয়, যখনই একটি MenuItem পরিবর্তন করা হবে, একটি সম্পূর্ণ নতুন setMenuItems কল ঘটবে। একজন ব্যবহারকারী একটি সুইচ MenuItem ক্লিক করার মতো তুচ্ছ কিছুর জন্য এটি ঘটতে পারে, এবং সেই ক্লিকের ফলে সুইচটি টগল হয়ে যায়। পারফরম্যান্স এবং অ্যানিমেশন উভয় কারণে, তাই পুরানো এবং নতুন MenuItems তালিকার মধ্যে পার্থক্য গণনা করতে এবং প্রকৃতপক্ষে পরিবর্তিত দৃষ্টিভঙ্গিগুলি আপডেট করতে উত্সাহিত করা হয়৷ MenuItems একটি key ক্ষেত্র প্রদান করে যা এতে সাহায্য করতে পারে, কারণ একই MenuItem-এর জন্য setMenuItems করতে বিভিন্ন কলে কী একই হওয়া উচিত।

AppStyledView

AppStyledView হল একটি দৃশ্যের জন্য একটি ধারক যা একেবারেই কাস্টমাইজ করা হয়নি৷ এটি সেই দৃশ্যের চারপাশে একটি সীমানা প্রদান করতে ব্যবহার করা যেতে পারে যা এটিকে অ্যাপের বাকি অংশ থেকে আলাদা করে তোলে এবং ব্যবহারকারীকে নির্দেশ করে যে এটি একটি ভিন্ন ধরনের ইন্টারফেস। AppStyledView দ্বারা মোড়ানো ভিউ setContent দেওয়া হয়েছে। AppStyledView এ অ্যাপের অনুরোধ অনুযায়ী একটি ব্যাক বা ক্লোজ বোতামও থাকতে পারে।

AppStyledView অবিলম্বে এটির ভিউগুলিকে installBaseLayoutAround এর মত ভিউ হায়ারার্কিতে সন্নিবেশ করে না, এটি পরিবর্তে এটির ভিউটি getView মাধ্যমে স্ট্যাটিক লাইব্রেরিতে ফেরত দেয়, যা পরে সন্নিবেশ করে। getDialogWindowLayoutParam প্রয়োগ করে AppStyledView এর অবস্থান এবং আকারও নিয়ন্ত্রণ করা যেতে পারে।

প্রসঙ্গ

প্রসঙ্গগুলি ব্যবহার করার সময় প্লাগইনটিকে অবশ্যই সতর্কতা অবলম্বন করতে হবে, কারণ সেখানে প্লাগইন এবং "উৎস" প্রসঙ্গ উভয়ই রয়েছে৷ প্লাগইন প্রসঙ্গটি getPluginFactory এর একটি যুক্তি হিসাবে দেওয়া হয়েছে এবং এটিই একমাত্র প্রসঙ্গ যেখানে প্লাগইনের সংস্থান রয়েছে৷ এর মানে হল এটি একমাত্র প্রসঙ্গ যা প্লাগইনের লেআউটগুলিকে স্ফীত করতে ব্যবহার করা যেতে পারে।

যাইহোক, প্লাগইন প্রসঙ্গে সঠিক কনফিগারেশন সেট নাও থাকতে পারে। সঠিক কনফিগারেশন পেতে, আমরা উপাদানগুলি তৈরি করে এমন পদ্ধতিতে উত্স প্রসঙ্গ প্রদান করি। উত্স প্রসঙ্গ সাধারণত একটি কার্যকলাপ, কিন্তু কিছু ক্ষেত্রে একটি পরিষেবা বা অন্যান্য Android উপাদান হতে পারে। প্লাগইন প্রসঙ্গ থেকে সংস্থানগুলির সাথে উত্স প্রসঙ্গ থেকে কনফিগারেশন ব্যবহার করতে, createConfigurationContext ব্যবহার করে একটি নতুন প্রসঙ্গ তৈরি করতে হবে। সঠিক কনফিগারেশন ব্যবহার না করা হলে, একটি Android কঠোর মোড লঙ্ঘন হবে, এবং স্ফীত দৃশ্যের সঠিক মাত্রা নাও থাকতে পারে।

Context layoutInflationContext = pluginContext.createConfigurationContext(
        sourceContext.getResources().getConfiguration());

মোড পরিবর্তন

কিছু প্লাগইন তাদের উপাদানগুলির জন্য একাধিক মোড সমর্থন করতে পারে, যেমন একটি স্পোর্ট মোড বা এবং ইকো মোড যা দৃশ্যত আলাদা দেখায়। CarUi-তে এই ধরনের কার্যকারিতার জন্য কোনও অন্তর্নির্মিত সমর্থন নেই, তবে প্লাগইনটিকে সম্পূর্ণরূপে অভ্যন্তরীণভাবে প্রয়োগ করতে বাধা দেওয়ার কিছু নেই। প্লাগইনটি কখন মোড স্যুইচ করতে হবে, যেমন সম্প্রচারের জন্য শোনার মতো পরিস্থিতিগুলি নির্ধারণ করতে চায় তা নিরীক্ষণ করতে পারে৷ প্লাগইনটি মোড পরিবর্তনের জন্য কনফিগারেশন পরিবর্তন ট্রিগার করতে পারে না, তবে যাইহোক কনফিগারেশন পরিবর্তনের উপর নির্ভর করার পরামর্শ দেওয়া হয় না, কারণ ম্যানুয়ালি প্রতিটি উপাদানের উপস্থিতি আপডেট করা ব্যবহারকারীর জন্য মসৃণ এবং কনফিগারেশন পরিবর্তনের সাথে সম্ভব নয় এমন পরিবর্তনের অনুমতি দেয়।

জেটপ্যাক রচনা

প্লাগইনগুলি জেটপ্যাক কম্পোজ ব্যবহার করে প্রয়োগ করা যেতে পারে, তবে এটি একটি আলফা-স্তরের বৈশিষ্ট্য এবং এটিকে স্থিতিশীল হিসাবে বিবেচনা করা উচিত নয়।

প্লাগইনগুলি রেন্ডার করার জন্য একটি রচনা-সক্ষম পৃষ্ঠ তৈরি করতে ComposeView ব্যবহার করতে পারে। এই ComposeView উপাদানগুলির মধ্যে getView পদ্ধতি থেকে অ্যাপে যা ফিরে আসে তা হবে।

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)
//  }
}