Sử dụng các trình bổ trợ thư viện Giao diện người dùng trên ô tô để tạo hoạt động triển khai hoàn chỉnh cho các thành phần các tính năng tuỳ chỉnh trong thư viện Giao diện người dùng trên ô tô thay vì sử dụng lớp phủ tài nguyên trong thời gian chạy (RRO). RRO cho phép bạn chỉ thay đổi các tài nguyên XML của thư viện Giao diện người dùng cho ô tô thành phần, giới hạn phạm vi nội dung bạn có thể tuỳ chỉnh.
Tạo trình bổ trợ
Trình bổ trợ thư viện Giao diện người dùng cho ô tô là một tệp APK chứa các lớp triển khai một tập hợp API trình bổ trợ. API Trình bổ trợ có thể được biên dịch thành một plugin dưới dạng thư viện tĩnh.
Hãy xem ví dụ trong Soong và Gradle:
Tiếng Soong
Hãy xem ví dụ về phiên bản Soong sau đây:
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",
}
Gradle
Xem tệp build.gradle
này:
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')
Trình bổ trợ này phải có một trình cung cấp nội dung được khai báo trong tệp kê khai có phần tử các thuộc tính sau:
android:authorities="com.android.car.ui.plugin"
android:enabled="true"
android:exported="true"
android:authorities="com.android.car.ui.plugin"
giúp người dùng dễ dàng tìm thấy trình bổ trợ này
vào thư viện Giao diện người dùng trên ô tô. Bạn phải xuất trình cung cấp này để có thể truy vấn nhà cung cấp này tại
thời gian chạy. Ngoài ra, nếu bạn đặt thuộc tính enabled
thành false
thì giá trị mặc định
phương thức triển khai này sẽ được dùng thay vì triển khai trình bổ trợ. Nội dung
lớp nhà cung cấp không phải tồn tại. Trong trường hợp đó, hãy nhớ thêm
tools:ignore="MissingClass"
cho định nghĩa trình cung cấp. Xem mẫu
mục kê khai bên dưới:
<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>
Cuối cùng, như một biện pháp bảo mật, Ký ứng dụng.
Trình bổ trợ dưới dạng thư viện dùng chung
Không giống như các thư viện tĩnh của Android được biên dịch trực tiếp thành ứng dụng, Thư viện dùng chung Android được biên dịch thành một tệp APK độc lập được tham chiếu bởi các ứng dụng khác trong thời gian chạy.
Các trình bổ trợ được triển khai dưới dạng thư viện chia sẻ Android có các lớp riêng tự động được thêm vào trình tải lớp dùng chung giữa các ứng dụng. Khi một ứng dụng sử dụng thư viện Giao diện người dùng trên ô tô chỉ định một phần phụ thuộc thời gian chạy trên thư viện chia sẻ trình bổ trợ, trình tải lớp có thể truy cập vào các lớp của thư viện chia sẻ của trình bổ trợ. Đã triển khai trình bổ trợ như các ứng dụng Android thông thường (không phải thư viện dùng chung) có thể ảnh hưởng tiêu cực đến tình trạng nguội ứng dụng thời gian bắt đầu.
Triển khai và xây dựng thư viện dùng chung
Việc phát triển bằng thư viện dùng chung của Android khá giống với thư viện Android thông thường ứng dụng, với một vài điểm khác biệt chính.
- Dùng thẻ
library
trong thẻapplication
cùng với gói trình bổ trợ tên trong tệp kê khai ứng dụng của trình bổ trợ:
<application>
<library android:name="com.chassis.car.ui.plugin" />
...
</application>
- Định cấu hình quy tắc bản dựng Soong
android_app
(Android.bp
) với AAPT gắn cờshared-lib
dùng để tạo thư viện dùng chung:
android_app {
...
aaptflags: ["--shared-lib"],
...
}
Phần phụ thuộc trên thư viện dùng chung
Đối với mỗi ứng dụng trên hệ thống sử dụng thư viện Giao diện người dùng cho ô tô, hãy thêm phần tử
uses-library
trong tệp kê khai ứng dụng trong
Thẻ application
có tên gói trình bổ trợ:
<manifest>
<application
android:name=".MyApp"
...>
<uses-library android:name="com.chassis.car.ui.plugin" android:required="false"/>
...
</application>
</manifest>
Cài đặt trình bổ trợ
Trình bổ trợ PHẢI được cài đặt trước trên phân vùng hệ thống bằng cách thêm mô-đun
trong PRODUCT_PACKAGES
. Gói cài đặt sẵn có thể được cập nhật tương tự như
bất kỳ ứng dụng đã cài đặt nào khác.
Nếu bạn đang cập nhật trình bổ trợ hiện có trên hệ thống, mọi ứng dụng đang sử dụng trình bổ trợ đó tự động đóng. Sau khi người dùng mở lại, họ sẽ có các thay đổi được cập nhật. Nếu ứng dụng không chạy, vào lần khởi động tiếp theo, ứng dụng sẽ có .
Khi cài đặt trình bổ trợ với Android Studio, sẽ có một số cần cân nhắc. Tại thời điểm viết, có một lỗi trong quy trình cài đặt ứng dụng Android Studio dẫn đến việc cập nhật một trình bổ trợ không có hiệu lực. Điều này có thể được khắc phục bằng cách chọn tuỳ chọn Luôn cài đặt bằng trình quản lý gói (tắt tính năng triển khai tính năng tối ưu hoá trên Android 11 trở lên) trong cấu hình bản dựng của trình bổ trợ.
Ngoài ra, khi cài đặt trình bổ trợ này, Android Studio sẽ báo cáo một lỗi không tìm thấy một hoạt động chính để khởi chạy. Điều này nằm trong dự kiến, vì trình bổ trợ không có bất kỳ hoạt động nào (ngoại trừ ý định trống dùng để phân giải một ý định). Người nhận loại bỏ lỗi này, hãy thay đổi tuỳ chọn Launch (Khởi chạy) thành None (Không có) trong bản dựng .
Hình 1. Cấu hình trình bổ trợ Android Studio
Trình bổ trợ proxy
Tuỳ chỉnh ứng dụng dùng thư viện Giao diện người dùng trên ô tô yêu cầu RRO nhắm mục tiêu đến từng ứng dụng cụ thể cần được sửa đổi, kể cả khi các yếu tố tuỳ chỉnh giống hệt nhau trên các ứng dụng. Điều này có nghĩa là RRO trên mỗi bắt buộc. Xem ứng dụng nào dùng thư viện Giao diện người dùng trên ô tô.
Trình bổ trợ proxy thư viện giao diện người dùng trên ô tô là một ví dụ thư viện chia sẻ trình bổ trợ uỷ quyền triển khai thành phần cho thư viện tĩnh phiên bản thư viện Giao diện người dùng trên ô tô. Trình bổ trợ này có thể được nhắm mục tiêu bằng RRO, có thể là được dùng làm điểm tuỳ chỉnh duy nhất cho các ứng dụng dùng thư viện Giao diện người dùng trên ô tô mà không cần triển khai trình bổ trợ chức năng. Để biết thêm thông tin về RRO, xem phần Thay đổi giá trị của tài nguyên ứng dụng tại thời gian chạy.
Plugin proxy chỉ là một ví dụ và là điểm bắt đầu để thực hiện tùy chỉnh bằng một trình bổ trợ. Để tuỳ chỉnh ngoài RRO, bạn có thể triển khai một tập hợp con trình bổ trợ các thành phần khác và sử dụng plugin proxy cho phần còn lại hoặc triển khai tất cả các plugin thành phần hoàn toàn từ đầu.
Mặc dù plugin proxy cung cấp một điểm tuỳ chỉnh RRO duy nhất cho ứng dụng, các ứng dụng chọn không sử dụng trình bổ trợ vẫn sẽ yêu cầu RRO trực tiếp nhắm mục tiêu đến chính ứng dụng đó.
Triển khai các API trình bổ trợ
Điểm truy cập chính cho trình bổ trợ là
Lớp com.android.car.ui.plugin.PluginVersionProviderImpl
. Tất cả trình bổ trợ phải
hãy thêm một lớp có tên và tên gói chính xác như vậy. Lớp này phải có một
hàm khởi tạo mặc định và triển khai giao diện PluginVersionProviderOEMV1
.
Các trình bổ trợ CarUi phải hoạt động với các ứng dụng cũ hơn hoặc mới hơn trình bổ trợ. Người nhận
hỗ trợ việc này, tất cả API trình bổ trợ đều được tạo phiên bản bằng V#
ở cuối
tên lớp. Nếu thư viện Giao diện người dùng ô tô ra mắt phiên bản mới kèm theo các tính năng mới,
chúng là một phần của phiên bản V2
của thành phần. Thư viện giao diện người dùng trên ô tô thực hiện
tốt nhất để làm cho các tính năng mới hoạt động trong phạm vi của thành phần trình bổ trợ cũ.
Ví dụ: bằng cách chuyển đổi một loại nút mới trên thanh công cụ thành MenuItems
.
Tuy nhiên, ứng dụng có phiên bản cũ của thư viện Giao diện người dùng ô tô không thể thích ứng với được viết dựa trên các API mới hơn. Để giải quyết vấn đề này, chúng tôi cho phép trình bổ trợ trả về nhiều cách triển khai của chính nó dựa trên phiên bản API OEM được ứng dụng hỗ trợ.
PluginVersionProviderOEMV1
chứa một phương thức:
Object getPluginFactory(int maxVersion, Context context, String packageName);
Phương thức này trả về một đối tượng triển khai phiên bản cao nhất của
PluginFactoryOEMV#
được trình bổ trợ hỗ trợ, mặc dù vẫn thấp hơn hoặc
bằng maxVersion
. Nếu một trình bổ trợ không triển khai
PluginFactory
cũ, giá trị này có thể trả về null
, trong trường hợp đó phương thức tĩnh
được liên kết triển khai các thành phần CarUi.
Để duy trì khả năng tương thích ngược với các ứng dụng được biên dịch dựa trên
phiên bản cũ hơn của thư viện Car UI tĩnh, bạn nên hỗ trợ
maxVersion
là 2, 5 trở lên trong quá trình triển khai trình bổ trợ
lớp PluginVersionProvider
. Phiên bản 1, 3 và 4 không được hỗ trợ. Cho
thông tin khác, xem
PluginVersionProviderImpl
.
PluginFactory
là giao diện tạo tất cả CarUi khác
thành phần. Tệp này cũng xác định phiên bản giao diện sẽ được sử dụng. Nếu
trình bổ trợ không tìm cách triển khai bất kỳ thành phần nào trong số này, nó có thể trả về
null
trong chức năng tạo của mình (ngoại trừ thanh công cụ có
một hàm customizesBaseLayout()
riêng).
pluginFactory
giới hạn những phiên bản thành phần CarUi có thể sử dụng
khi kết hợp cùng nhau. Ví dụ: sẽ không bao giờ có một pluginFactory
có thể tạo
phiên bản 100 của Toolbar
và phiên bản 1 của RecyclerView
, như ở đó
sẽ ít có thể đảm bảo rằng nhiều phiên bản thành phần khác nhau sẽ
làm việc cùng nhau. Để sử dụng thanh công cụ phiên bản 100, nhà phát triển cần phải
cung cấp phương thức triển khai phiên bản pluginFactory
sẽ tạo
thanh công cụ phiên bản 100, sau đó giới hạn các tuỳ chọn trên các phiên bản của
thành phần có thể được tạo. Phiên bản của các thành phần khác có thể không được
bằng nhau, ví dụ: pluginFactoryOEMV100
có thể tạo
ToolbarControllerOEMV100
và RecyclerViewOEMV70
.
Thanh công cụ
Bố cục cơ sở
Thanh công cụ và "bố cục cơ sở" có liên quan rất chặt chẽ, do đó hàm này
để tạo thanh công cụ có tên là installBaseLayoutAround
. Chiến lược phát hành đĩa đơn
bố cục cơ sở
là một khái niệm cho phép thanh công cụ được đặt ở bất cứ đâu xung quanh ứng dụng
nội dung, để cho phép thanh công cụ chạy dọc ở đầu/cuối ứng dụng
dọc theo các cạnh hoặc thậm chí là một thanh công cụ hình tròn bao quanh toàn bộ ứng dụng. Đây là
được thực hiện bằng cách chuyển thành phần hiển thị đến installBaseLayoutAround
cho thanh công cụ/cơ sở
để bao bọc.
Trình bổ trợ sẽ lấy khung hiển thị được cung cấp, tách khỏi khung hiển thị gốc, tăng cường
bố cục riêng của trình bổ trợ trong cùng một chỉ mục của thành phần mẹ và với cùng một
LayoutParams
làm thành phần hiển thị vừa được tách rời, rồi đính kèm lại thành phần hiển thị này
ở đâu đó bên trong bố cục vừa được tăng cường. Bố cục tăng cường sẽ
chứa thanh công cụ nếu ứng dụng yêu cầu.
Ứng dụng có thể yêu cầu bố cục cơ sở mà không cần thanh công cụ. Nếu có,
installBaseLayoutAround
phải trả về giá trị rỗng. Đối với hầu hết các trình bổ trợ, đó là tất cả những gì cần làm
cần xảy ra, nhưng nếu tác giả trình bổ trợ muốn áp dụng, ví dụ: trang trí
xung quanh cạnh ứng dụng, bạn vẫn có thể thực hiện việc đó với bố cục cơ sở. Các
trang trí đặc biệt hữu ích cho các thiết bị có màn hình không phải hình chữ nhật, vì
họ có thể đẩy ứng dụng vào không gian hình chữ nhật và thêm các hiệu ứng chuyển tiếp rõ ràng vào
không gian không phải hình chữ nhật.
installBaseLayoutAround
cũng nhận được một Consumer<InsetsOEMV1>
. Chiến dịch này
đối tượng tiêu dùng có thể được dùng để cho ứng dụng biết rằng trình bổ trợ chỉ sử dụng một phần
che khuất nội dung của ứng dụng (bằng thanh công cụ hoặc cách khác). Ứng dụng này sẽ
thì cần tiếp tục vẽ trong không gian này, nhưng vẫn giữ mọi tính năng tối quan trọng mà người dùng có thể tương tác
thành phần khác. Hiệu ứng này được dùng trong thiết kế tham chiếu để làm cho
thanh công cụ có bán trong suốt và có danh sách cuộn bên dưới. Nếu tính năng này
chưa được triển khai, mục đầu tiên trong danh sách sẽ bị kẹt bên dưới thanh công cụ
và không nhấp vào được. Nếu không cần hiệu ứng này, trình bổ trợ có thể bỏ qua
Người tiêu dùng.
Hình 2. Cuộn nội dung bên dưới thanh công cụ
Từ góc độ của ứng dụng, khi trình bổ trợ gửi các phần lồng ghép mới, nó sẽ nhận
chúng qua mọi hoạt động hoặc mảnh triển khai InsetsChangedListener
. Nếu
một hoạt động hoặc mảnh không triển khai InsetsChangedListener
, Giao diện người dùng ô tô
thư viện sẽ xử lý các phần lồng ghép theo mặc định bằng cách áp dụng các phần lồng ghép làm khoảng đệm cho
Activity
hoặc FragmentActivity
chứa mảnh. Thư viện không
áp dụng phần lồng ghép theo mặc định cho các mảnh. Dưới đây là một đoạn mã mẫu của
phương thức triển khai áp dụng các phần lồng ghép làm khoảng đệm trên RecyclerView
trong
ứng dụng:
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());
}
}
Cuối cùng, trình bổ trợ này sẽ được cung cấp một gợi ý fullscreen
để cho biết liệu
khung hiển thị cần được bao bọc sẽ chiếm toàn bộ ứng dụng hoặc chỉ một phần nhỏ.
Bạn có thể dùng tính năng này để tránh áp dụng một số đồ trang trí dọc theo cạnh
chỉ có ý nghĩa nếu chúng xuất hiện dọc theo cạnh của toàn bộ màn hình. Một mẫu
ứng dụng sử dụng bố cục cơ sở không toàn màn hình là Cài đặt, trong đó mỗi ngăn của
bố cục ngăn kép có thanh công cụ riêng.
Vì theo dự kiến, installBaseLayoutAround
sẽ trả về giá trị rỗng khi
toolbarEnabled
là false
để trình bổ trợ cho biết rằng nó không
muốn tuỳ chỉnh bố cục cơ sở, thì bố cục này phải trả về false
từ
customizesBaseLayout
.
Bố cục cơ sở phải chứa FocusParkingView
và FocusArea
để đầy đủ
hỗ trợ điều khiển xoay. Bạn có thể bỏ qua các chế độ xem này trên các thiết bị
không hỗ trợ xoay. FocusParkingView/FocusAreas
được triển khai trong
thư viện CarUi tĩnh, nên setRotaryFactories
được dùng để cung cấp cho các nhà máy
tạo chế độ xem từ ngữ cảnh.
Ngữ cảnh dùng để tạo Chế độ xem tập trung phải là ngữ cảnh nguồn, chứ không phải là
ngữ cảnh của trình bổ trợ. FocusParkingView
phải gần với thành phần hiển thị đầu tiên nhất
vào cây một cách hợp lý nhất có thể, vì đó là yếu tố được tập trung vào khi cần
người dùng không nhìn thấy tiêu điểm. FocusArea
phải gói thanh công cụ trong
bố cục cơ sở để cho biết đó là vùng nhắc nhở hoạt động xoay. Nếu FocusArea
không phải là
được cung cấp, người dùng không thể điều hướng đến bất kỳ nút nào trên thanh công cụ bằng
núm vặn điều khiển.
Bộ điều khiển thanh công cụ
ToolbarController
thực tế được trả về sẽ đơn giản hơn nhiều để
triển khai so với bố cục cơ sở. Nhiệm vụ của mã này là đưa thông tin đến
phương thức setter và hiển thị phương thức này trong bố cục cơ sở. Xem Javadoc để biết thông tin về
hầu hết các phương pháp. Một số phương pháp phức tạp hơn sẽ được thảo luận dưới đây.
getImeSearchInterface
dùng để hiện kết quả tìm kiếm trong IME (bàn phím)
cửa sổ. Điều này có thể hữu ích khi hiển thị/tạo ảnh động cho kết quả tìm kiếm cùng với
bàn phím, ví dụ: nếu bàn phím chỉ chiếm một nửa màn hình. Hầu hết
chức năng này được triển khai trong thư viện CarUi tĩnh, công cụ tìm kiếm
giao diện trong trình bổ trợ chỉ cung cấp các phương thức cho thư viện tĩnh để lấy
Lệnh gọi lại TextView
và onPrivateIMECommand
. Để hỗ trợ điều này, trình bổ trợ
nên sử dụng một lớp con TextView
ghi đè onPrivateIMECommand
và truyền
lệnh gọi đến trình nghe đã cung cấp làm TextView
của thanh tìm kiếm.
setMenuItems
chỉ hiển thị MenuItems trên màn hình, nhưng sẽ được gọi
thường đến mức bất ngờ. Vì API trình bổ trợ cho MenuItems là không thể thay đổi, bất cứ khi nào một
Mục trình đơn đã thay đổi, một lệnh gọi setMenuItems
hoàn toàn mới sẽ diễn ra. Điều này có thể
xảy ra cho một việc không đáng kể như khi người dùng nhấp vào một mục trình đơn chuyển đổi và điều đó
nhấp vào khiến nút chuyển này chuyển sang trạng thái bật/tắt. Vì cả lý do liên quan đến hiệu suất và ảnh động,
do đó, bạn nên tính toán sự khác biệt giữa quảng cáo cũ và mới
Danh sách MenuItems và chỉ cập nhật các thành phần hiển thị thực sự thay đổi. Mục trong trình đơn
hãy cung cấp trường key
có thể trợ giúp việc này, vì khoá phải giống nhau
trên các lệnh gọi khác nhau đến setMenuItems
cho cùng một Mục trình đơn.
Chế độ xem AppStyled
AppStyledView
là vùng chứa một khung hiển thị không được tuỳ chỉnh. Nó
có thể được dùng để tạo đường viền xung quanh thành phần hiển thị đó giúp thành phần hiển thị đó nổi bật
phần còn lại của ứng dụng và chỉ cho người dùng rằng đây là một loại
. Thành phần hiển thị được gói bằng AppStyledView được cung cấp trong
setContent
. AppStyledView
cũng có thể có nút quay lại hoặc nút đóng như
mà ứng dụng yêu cầu.
AppStyledView
không chèn ngay các khung hiển thị vào hệ phân cấp khung hiển thị
giống như installBaseLayoutAround
, thì phương thức này chỉ trả về khung hiển thị của
thư viện tĩnh thông qua getView
, sau đó thực hiện thao tác chèn. Vị trí và
bạn cũng có thể kiểm soát kích thước của AppStyledView
bằng cách triển khai
getDialogWindowLayoutParam
.
Ngữ cảnh
Trình bổ trợ này phải cẩn thận khi sử dụng Ngữ cảnh, vì có cả trình bổ trợ và
"nguồn" ngữ cảnh. Ngữ cảnh trình bổ trợ được cung cấp dưới dạng một đối số cho
getPluginFactory
và là ngữ cảnh duy nhất có
trình bổ trợ trong đó. Đây có nghĩa là đây là ngữ cảnh duy nhất có thể được sử dụng để
tăng cường bố cục trong trình bổ trợ.
Tuy nhiên, ngữ cảnh trình bổ trợ có thể không được thiết lập đúng cấu hình. Người nhận
có được cấu hình chính xác, chúng ta sẽ cung cấp ngữ cảnh nguồn trong các phương thức tạo
thành phần. Ngữ cảnh nguồn thường là một hoạt động, nhưng trong một số trường hợp có thể
cũng là Dịch vụ hoặc thành phần Android khác. Để sử dụng cấu hình từ
ngữ cảnh nguồn cùng với tài nguyên từ ngữ cảnh trình bổ trợ, thì ngữ cảnh mới phải là
được tạo bằng createConfigurationContext
. Nếu cấu hình không chính xác
thì sẽ vi phạm chế độ nghiêm ngặt của Android và chế độ xem tăng cường có thể
không có phương diện chính xác.
Context layoutInflationContext = pluginContext.createConfigurationContext(
sourceContext.getResources().getConfiguration());
Các thay đổi về chế độ
Một số trình bổ trợ có thể hỗ trợ nhiều chế độ cho các thành phần của chúng, chẳng hạn như chế độ thể thao hoặc chế độ sinh thái trông khác biệt về mặt trực quan. Không có hỗ trợ tích hợp sẵn cho chức năng như vậy trong CarUi, nhưng không có gì dừng lại trình bổ trợ triển khai hoàn toàn nội bộ. Trình bổ trợ có thể giám sát bất kỳ điều kiện nào nó muốn xác định thời điểm chuyển đổi chế độ, chẳng hạn như đang nghe thông báo truyền tin. Trình bổ trợ không thể kích hoạt thay đổi cấu hình thay đổi chế độ, nhưng bạn không nên dựa vào các thay đổi về cấu hình dù sao thì việc cập nhật giao diện của mỗi thành phần theo cách thủ công sẽ mượt mà hơn cho người dùng và cũng cho phép chuyển đổi không thể thực hiện được với các thay đổi về cấu hình.
Jetpack Compose
Có thể triển khai trình bổ trợ bằng Jetpack Compose, nhưng đây là cấp độ alpha và không nên được xem là ổn định.
Các trình bổ trợ có thể sử dụng
ComposeView
để tạo một bề mặt hỗ trợ Compose để kết xuất. ComposeView
này sẽ là
dữ liệu được trả về từ ứng dụng từ phương thức getView
trong các thành phần.
Một vấn đề lớn khi sử dụng ComposeView
là đặt thẻ trong chế độ xem gốc
trong bố cục để lưu trữ các biến toàn cục được chia sẻ qua
ComposeView khác nhau trong hệ phân cấp. Vì mã tài nguyên của trình bổ trợ không phải là
không gian tên tách biệt với ứng dụng, điều này có thể gây ra xung đột khi cả hai
và trình bổ trợ đặt các thẻ trên cùng một chế độ xem. Tùy chỉnh
ComposeViewWithLifecycle
để chuyển các biến toàn cục này xuống
ComposeView
được cung cấp dưới đây. Xin nhắc lại rằng bạn không nên xem thuộc tính này là ổn định.
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)
// }
}