1. Giới thiệu
Compose và hệ thống Chế độ xem có thể hoạt động cùng nhau.
Trong lớp học lập trình này, bạn sẽ di chuyển các phần của màn hình thông tin chi tiết về cây trồng của Sunflower sang Compose. Chúng tôi đã tạo một bản sao của dự án để bạn có thể thử di chuyển ứng dụng thực tế sang Compose.
Khi kết thúc lớp học lập trình, bạn có thể tiếp tục di chuyển và chuyển đổi các màn hình còn lại của Sunflower nếu muốn.
Để được hỗ trợ thêm khi bạn tham gia lớp học lập trình này, hãy xem các mã sau:
Kiến thức bạn sẽ học được
Trong lớp học lập trình này, bạn sẽ tìm hiểu:
- Những đường di chuyển khác nhau bạn có thể làm theo
- Cách di chuyển dần ứng dụng sang Compose
- Cách thêm Compose vào màn hình được tạo hiện có bằng chế độ xem Android
- Cách sử dụng Chế độ xem Android từ bên trong Compose
- Cách sử dụng giao diện trên hệ thống Chế độ xem trong Compose
- Cách kiểm tra màn hình bằng hệ thống Chế độ xem và mã Compose
Điều kiện tiên quyết
- Kinh nghiệm về cú pháp Kotlin, bao gồm cả lambda.
- Hiểu rõ những kiến thức cơ bản về Compose
Bạn cần có
2. Lên kế hoạch di chuyển
Việc di chuyển đến Compose tuỳ thuộc vào bạn và nhóm của bạn. Có nhiều cách để tích hợp Jetpack Compose vào một ứng dụng Android hiện có. Hai chiến lược di chuyển phổ biến là:
- Xây dựng một màn hình mới hoàn toàn bằng Compose
- Sử dụng màn hình hiện có rồi di chuyển dần các thành phần trên màn hình đó.
Compose trong màn hình mới
Một phương pháp phổ biến khi cải tiến ứng dụng sang một công nghệ mới là sử dụng công nghệ đó với các tính năng mới bạn xây dựng cho ứng dụng của mình. Trong trường hợp này, màn hình mới sẽ được áp dụng. Nếu bạn cần tạo màn hình giao diện người dùng mới cho ứng dụng, hãy cân nhắc sử dụng Compose cho màn hình và phần còn lại của ứng dụng có thể vẫn còn trong hệ thống Chế độ xem.
Trong trường hợp này, bạn phải thực hiện tương tác Compose ở cạnh của các tính năng được di chuyển đó.
Compose và Chế độ xem hoạt động cùng nhau
Khi đã thêm một màn hình, bạn có thể chuyển một số phần sang Compose và một số phần khác sang hệ thống Chế độ xem. Ví dụ: bạn có thể di chuyển RecyclerView trong khi để lại phần còn lại của màn hình trong hệ thống Chế độ xem.
Hoặc ngược lại, hãy dùng Compose làm bố cục bên ngoài và dùng một số chế độ xem hiện tại có thể không dùng được trong Compose như MapView hoặc AdView.
Hoàn tất việc di chuyển
Di chuyển lần lượt toàn bộ mảnh hoặc màn hình sang Compose Đơn giản nhất, nhưng rất chi tiết.
Và trong lớp học lập trình này?
Trong lớp học lập trình này, bạn sẽ thực hiện việc di chuyển dần sang màn hình thông tin chi tiết về cây trồng của Sunflower có mục Compose và Chế độ xem hoạt động cùng nhau. Sau đó, bạn sẽ nắm rõ kiến thức đủ để tiếp tục việc di chuyển nếu muốn.
3. Thiết lập
Lấy mã
Lấy mã cho lớp học lập trình từ GitHub:
$ git clone https://github.com/googlecodelabs/android-compose-codelabs
Ngoài ra, bạn có thể tải kho lưu trữ xuống dưới dạng tệp Zip:
Mở Android Studio
Lớp học lập trình này yêu cầu bạn có Android Studio Bumblebee.
Chạy ứng dụng mẫu
Mã bạn vừa tải xuống có chứa mã dành cho tất cả lớp học lập trình Compose hiện có. Để hoàn tất lớp học lập trình này, hãy mở dự án MigrationCodelab
trong Android Studio.
Trong lớp học lập trình này, bạn sẽ di chuyển màn hình thông tin chi tiết về cây trồng của Sunflower sang Compose. Có thể mở màn hình chi tiết về cây bằng cách nhấn vào một trong các cây có trong màn hình danh sách cây.
Thiết lập dự án
Dự án được xây dựng trong nhiều nhánh git.
main
là nhánh bạn đã coi qua hoặc tải xuống. Đây là điểm bắt đầu của lớp học lập trình.end
có chứa giải pháp cho lớp học lập trình này
Nên bắt đầu bằng mã trong nhánh main
và làm theo hướng dẫn từng bước của lớp học lập trình theo tốc độ của bạn.
Xuyên suốt lớp học lập trình, bạn sẽ thấy các đoạn mã bạn cần thêm vào dự án. Có lúc, bạn cũng sẽ cần phải xoá mã được đề cập rõ ràng trong các nhận xét trên đoạn mã.
Để nhận nhánh end
sử dụng git, hãy dùng lệnh sau:
$ git clone -b end https://github.com/googlecodelabs/android-compose-codelabs
Hoặc tải mã giải pháp từ đây:
Câu hỏi thường gặp
4. Compose trong Sunflower
Compose đã được thêm vào mã bạn tải xuống từ nhánh main
. Tuy nhiên, hãy xem những yêu cầu để nó có thể hoạt động.
Nếu mở tệp app/build.gradle
(hoặc build.gradle (Module: compose-migration.app)
), hãy xem cách tệp này nhập phần phụ thuộc Compose và cho phép Android Studio hoạt động với Compose bằng cách sử dụng cờ buildFeatures { compose true }
.
app/build.gradle
android {
...
kotlinOptions {
jvmTarget = '1.8'
useIR = true
}
buildFeatures {
...
compose true
}
composeOptions {
kotlinCompilerExtensionVersion rootProject.composeVersion
}
}
dependencies {
...
// Compose
implementation "androidx.compose.runtime:runtime:$rootProject.composeVersion"
implementation "androidx.compose.ui:ui:$rootProject.composeVersion"
implementation "androidx.compose.foundation:foundation:$rootProject.composeVersion"
implementation "androidx.compose.foundation:foundation-layout:$rootProject.composeVersion"
implementation "androidx.compose.material:material:$rootProject.composeVersion"
implementation "androidx.compose.runtime:runtime-livedata:$rootProject.composeVersion"
implementation "androidx.compose.ui:ui-tooling:$rootProject.composeVersion"
implementation "com.google.android.material:compose-theme-adapter:$rootProject.composeVersion"
...
}
Phiên bản của các phần phụ thuộc này được xác định trong tệp build.gradle
gốc.
5. Xin chào Compose!
Trong màn hình thông tin chi tiết về cây, chúng ta sẽ di chuyển nội dung mô tả về cây sang Compose, đồng thời giữ nguyên cấu trúc tổng thể của màn hình. Tại đây, bạn sẽ theo dõi chiến lược di chuyển Compose và Chế độ xem hoạt động cùng nhau được đề cập trong phần Lên kế hoạch di chuyển.
Compose cần có Hoạt động lưu trữ hoặc Mảnh để hiển thị giao diện người dùng. Trong Sunflower, vì tất cả màn hình đều sử dụng các mảnh nên bạn sẽ dùng ComposeView
: một Chế độ xem Android có thể lưu trữ nội dung trên giao diện người dùng của Compose bằng phương thức setContent
.
Xoá mã XML
Hãy bắt đầu di chuyển! Mở fragment_plant_detail.xml
và làm như sau:
- Chuyển sang Chế độ xem mã
- Xoá mã
ConstraintLayout
và cácTextView
được lồng bên trongNestedScrollView
(lớp học lập trình sẽ so sánh và tham chiếu đến mã XML khi di chuyển từng mục riêng lẻ, vì vậy, bạn sẽ thấy mã có nhận xét sẽ hữu ích) - Hãy thêm một
ComposeView
lưu trữ mã Compose thay vì vớicompose_view
để làm id chế độ xem
fragment_plant_detail.xml
<androidx.core.widget.NestedScrollView
android:id="@+id/plant_detail_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="@dimen/fab_bottom_padding"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
// Step 2) Comment out ConstraintLayout and its children
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/margin_normal">
<TextView
android:id="@+id/plant_detail_name"
...
</androidx.constraintlayout.widget.ConstraintLayout>
// End Step 2) Comment out until here
// Step 3) Add a ComposeView to host Compose code
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.core.widget.NestedScrollView>
Thêm mã Compose
Lúc này, bạn đã sẵn sàng để bắt đầu di chuyển màn hình thông tin chi tiết về cây trồng đến Compose!
Trong suốt lớp học lập trình, bạn phải thêm mã Compose vào tệp PlantDetailDescription.kt
trong thư mục plantdetail
. Mở tệp này và xem cách chúng tôi đã có một phần giữ chỗ cho văn bản "Hello Compose!"
trong dự án.
plantdetail/PlantDetailDescription.kt
@Composable
fun PlantDetailDescription() {
Text("Hello Compose")
}
Hãy hiển thị thông tin này trên màn hình bằng cách gọi thành phần kết hợp từ ComposeView
đã thêm ở bước trước. Mở plantdetail/PlantDetailFragment.kt
.
Vì màn hình đang sử dụng tính năng liên kết dữ liệu, nên bạn có thể truy cập trực tiếp vào composeView
và gọi setContent
để hiển thị mã Compose trên màn hình. Gọi PlantDetailDescription
thành phần kết hợp bên trong MaterialTheme
vì Sunflower sử dụng Material Design.
plantdetail/PlantDetailFragment.kt
class PlantDetailFragment : Fragment() {
...
override fun onCreateView(...): View? {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
...
composeView.setContent {
// You're in Compose world!
MaterialTheme {
PlantDetailDescription()
}
}
}
...
}
}
Nếu chạy ứng dụng, bạn có thể thấy "Hello Compose!
" hiển thị trên màn hình.
6. Tạo một Thành phần kết hợp từ XML
Hãy bắt đầu bằng cách di chuyển tên của cây. Chính xác hơn là TextView
với mã @+id/plant_detail_name
đã xoá trong fragment_plant_detail.xml
. Sau đây là mã XML:
<TextView
android:id="@+id/plant_detail_name"
...
android:layout_marginStart="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
android:text="@{viewModel.plant.name}"
android:textAppearance="?attr/textAppearanceHeadline5"
... />
Hãy xem cách phần này có kiểu textAppearanceHeadline5
, có lề ngang là 8.dp
và nằm ở giữa màn hình theo chiều ngang. Tuy nhiên, tiêu đề được hiển thị lại quan sát được từ LiveData
do PlantDetailViewModel
hiển thị (bắt nguồn từ lớp kho lưu trữ).
Khi quan sát thấy LiveData
được đề cập sau đó, giả sử chúng ta có sẵn tên và được chuyển dưới dạng thông số vào thành phần kết hợp mới PlantName
tạo trong tệp PlantDetailDescription.kt
. Thành phần kết hợp này sẽ được gọi từ thành phần kết hợp PlantDetailDescription
sau.
PlantDetailDescription.kt
@Composable
private fun PlantName(name: String) {
Text(
text = name,
style = MaterialTheme.typography.h5,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.margin_small))
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
@Preview
@Composable
private fun PlantNamePreview() {
MaterialTheme {
PlantName("Apple")
}
}
Bản xem trước:
Trong trường hợp:
- Kiểu của
Text
làMaterialTheme.typography.h5
liên kết tớitextAppearanceHeadline5
từ mã XML. - Công cụ sửa đổi trang trí Văn bản để điều chỉnh sao cho giống như phiên bản XML:
- Công cụ sửa đổi
fillMaxWidth
tương ứng vớiandroid:layout_width="match_parent"
trong mã XML. padding
ngangmargin_small
là một giá trị từ hệ thống Chế độ xem bằng hàm trợ giúpdimensionResource
.wrapContentWidth
để căn chỉnhText
theo chiều ngang.
7. ViewModel và LiveData
Bây giờ, hãy kết nối tiêu đề với màn hình. Để làm việc đó, bạn cần tải dữ liệu bằng cách sử dụng PlantDetailViewModel
. Do đó, Compose tích hợp với ViewModel và LiveData.
ViewModel
Vì một bản sao của PlantDetailViewModel
được dùng trong Mảnh, nên chúng ta có thể chuyển nó đó dưới dạng thông số cho PlantDetailDescription
.
Mở tệp PlantDetailDescription.kt
và thêm thông số PlantDetailViewModel
vào PlantDetailDescription
:
PlantDetailDescription.kt
@Composable
fun PlantDetailDescription(plantDetailViewModel: PlantDetailViewModel) {
...
}
Bây giờ, hãy chuyển bản sao của ViewModel khi gọi thành phần kết hợp này từ mảnh:
PlantDetailFragment.kt
class PlantDetailFragment : Fragment() {
...
override fun onCreateView(...): View? {
...
composeView.setContent {
MaterialTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
LiveData
Bằng cách này, bạn đã có quyền truy cập vào trường LiveData<Plant>
của PlantDetailViewModel
để lấy tên của cây.
Để quan sát LiveData từ một thành phần kết hợp, hãy dùng hàm LiveData.observeAsState()
.
Vì các giá trị do LiveData phát hành có thể có giá trị rỗng, nên cần kết hợp việc sử dụng nó ở mức rỗng. Do đó, vì thế và vì mục đích sử dụng lại, bạn nên chia nhỏ mức tiêu thụ LiveData và nghe trong các thành phần kết hợp khác nhau. Do đó, hãy tạo một thành phần kết hợp mới có tên là PlantDetailContent
để hiển thị thông tin về Plant
.
Như đề cập ở trên, tệp PlantDetailDescription.kt
sẽ giống như sau khi thêm chế độ quan sát LiveData.
PlantDetailDescription.kt
@Composable
fun PlantDetailDescription(plantDetailViewModel: PlantDetailViewModel) {
// Observes values coming from the VM's LiveData<Plant> field
val plant by plantDetailViewModel.plant.observeAsState()
// If plant is not null, display the content
plant?.let {
PlantDetailContent(it)
}
}
@Composable
fun PlantDetailContent(plant: Plant) {
PlantName(plant.name)
}
@Preview
@Composable
private fun PlantDetailContentPreview() {
val plant = Plant("id", "Apple", "description", 3, 30, "")
MaterialTheme {
PlantDetailContent(plant)
}
}
Có cùng bản xem trước như PlantNamePreview
vì PlantDetailContent
hiện chỉ gọi PlantName
.
Bây giờ, bạn đã kết nối toàn bộ ViewModel để hiển thị tên cây trong Compose. Trong phần tiếp theo, bạn sẽ tạo các phần còn lại của thành phần kết hợp và kết nối chúng với ViewModel theo cách tương tự.
8. Di chuyển mã XML khác
Giờ đây, bạn đã có thể dễ dàng hoàn thành những việc còn thiếu trong giao diện người dùng: thông tin tưới cây và nội dung mô tả cây cối. Làm theo cách tương tự như cách tiếp cận mã XML đã làm trước đây, bạn có thể di chuyển phần còn lại của màn hình.
Mã XML thông tin tưới cây bạn đã xoá trước đó khỏi fragment_plant_detail.xml
bao gồm hai chế độ TextView có mã plant_watering_header
và plant_watering
.
<TextView
android:id="@+id/plant_watering_header"
...
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_normal"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
android:text="@string/watering_needs_prefix"
android:textColor="?attr/colorAccent"
android:textStyle="bold"
... />
<TextView
android:id="@+id/plant_watering"
...
android:layout_marginStart="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
app:wateringText="@{viewModel.plant.wateringInterval}"
.../>
Tương tự như cách bạn đã làm trước đây, hãy tạo một thành phần kết hợp mới có tên là PlantWatering
và thêm Text
để hiển thị thông tin tưới cây trên màn hình:
PlantDetailDescription.kt
@Composable
private fun PlantWatering(wateringInterval: Int) {
Column(Modifier.fillMaxWidth()) {
// Same modifier used by both Texts
val centerWithPaddingModifier = Modifier
.padding(horizontal = dimensionResource(R.dimen.margin_small))
.align(Alignment.CenterHorizontally)
val normalPadding = dimensionResource(R.dimen.margin_normal)
Text(
text = stringResource(R.string.watering_needs_prefix),
color = MaterialTheme.colors.primaryVariant,
fontWeight = FontWeight.Bold,
modifier = centerWithPaddingModifier.padding(top = normalPadding)
)
val wateringIntervalText = LocalContext.current.resources.getQuantityString(
R.plurals.watering_needs_suffix, wateringInterval, wateringInterval
)
Text(
text = wateringIntervalText,
modifier = centerWithPaddingModifier.padding(bottom = normalPadding)
)
}
}
@Preview
@Composable
private fun PlantWateringPreview() {
MaterialTheme {
PlantWatering(7)
}
}
Bản xem trước:
Một số điều cần lưu ý:
- Vì khoảng đệm ngang và trang trí căn chỉnh do các thành phần được chia sẻ bởi
Text
, nên bạn có thể sử dụng lại Công cụ sửa đổi này bằng cách chỉ định công cụ này cho một biến cục bộ (tức làcenterWithPaddingModifier
). Vì công cụ sửa đổi là đối tượng Kotlin thông thường, nên bạn có thể thực hiện việc này. MaterialTheme
của Compose không có kết quả khớp chính xác vớicolorAccent
được sử dụng trongplant_watering_header
. Bây giờ, hãy dùngMaterialTheme.colors.primaryVariant
bạn sẽ cải thiện trong phần giao diện.
Hãy kết nối tất cả các phần với nhau và cũng gọi PlantWatering
từ PlantDetailContent
. Mã ConstraintLayout XML chúng ta xoá ở phần đầu có lề 16.dp
chúng ta cần đưa vào mã Compose.
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/margin_normal">
Trong PlantDetailContent
, hãy tạo một Column
để hiển thị cả tên và thông tin tưới nước, đồng thời đặt tên đó ở dạng khoảng đệm. Ngoài ra, để màu nền và màu văn bản sử dụng đều phù hợp, hãy thêm một Surface
để xử lý điều đó.
PlantDetailDescription.kt
@Composable
fun PlantDetailContent(plant: Plant) {
Surface {
Column(Modifier.padding(dimensionResource(R.dimen.margin_normal))) {
PlantName(plant.name)
PlantWatering(plant.wateringInterval)
}
}
}
Nếu làm mới bản xem trước, bạn sẽ thấy:
9. Chế độ xem trong mã Compose
Bây giờ, hãy cùng di chuyển phần mô tả cây. Mã trong fragment_plant_detail.xml
có một TextView
với app:renderHtml="@{viewModel.plant.description}"
để cho XML biết văn bản nào cần hiển thị trên màn hình. renderHtml
là bộ chuyển đổi liên kết có thể tìm thấy trong tệp PlantDetailBindingAdapters.kt
. Cách triển khai sử dụng HtmlCompat.fromHtml
để đặt văn bản trên TextView
!
Tuy nhiên, tính năng Compose hiện không hỗ trợ cho các lớp Spanned
cũng như không hiển thị văn bản dưới định dạng HTML. Do đó, chúng ta cần phải sử dụng TextView
từ hệ thống Chế độ xem trong mã Compose để bỏ qua giới hạn chế.
Vì Compose chưa thể hiển thị mã HTML, nên bạn sẽ tạo TextView
theo phương thức lập trình để thực hiện chính xác việc đó thông qua API AndroidView
.
AndroidView
lấy View
làm thông số và cung cấp cho bạn lệnh gọi lại khi Chế độ xem đã được tăng cường.
Hãy thực hiện việc này bằng cách tạo một thành phần kết hợp PlantDescription
mới. Thành phần kết hợp này có thể gọi AndroidView
bằng TextView
chúng ta vừa nhớ trong một hàm lambda. Trong lệnh gọi lại factory
, hãy khởi tạo một TextView
nhằm phản ứng với các lượt tương tác HTML bằng cách sử dụng Context
. Và trong lệnh gọi lại update
, hãy đặt văn bản bằng phần mô tả đã định dạng HTML được ghi nhớ.
PlantDetailDescription.kt
@Composable
private fun PlantDescription(description: String) {
// Remembers the HTML formatted description. Re-executes on a new description
val htmlDescription = remember(description) {
HtmlCompat.fromHtml(description, HtmlCompat.FROM_HTML_MODE_COMPACT)
}
// Displays the TextView on the screen and updates with the HTML description when inflated
// Updates to htmlDescription will make AndroidView recompose and update the text
AndroidView(
factory = { context ->
TextView(context).apply {
movementMethod = LinkMovementMethod.getInstance()
}
},
update = {
it.text = htmlDescription
}
)
}
@Preview
@Composable
private fun PlantDescriptionPreview() {
MaterialTheme {
PlantDescription("HTML<br><br>description")
}
}
Xem trước:
Lưu ý htmlDescription
ghi nhớ mô tả HTML cho description
nhất định được chuyển dưới dạng thông số. Nếu thông số description
thay đổi, mã htmlDescription
bên trong remember
sẽ thực thi lại.
Tương tự, lệnh gọi lại cập nhật AndroidView
sẽ khởi tạo lại nếu htmlDescription
thay đổi. Bất kỳ trạng thái nào được đọc bên trong lệnh gọi lại đều gây ra việc kết hợp lại.
Hãy thêm PlantDescription
vào mã tổng hợp PlantDetailContent
và thay đổi mã xem trước để hiển thị nội dung mô tả HTML:
PlantDetailDescription.kt
@Composable
fun PlantDetailContent(plant: Plant) {
Surface {
Column(Modifier.padding(dimensionResource(R.dimen.margin_normal))) {
PlantName(plant.name)
PlantWatering(plant.wateringInterval)
PlantDescription(plant.description)
}
}
}
@Preview
@Composable
private fun PlantDetailContentPreview() {
val plant = Plant("id", "Apple", "HTML<br><br>description", 3, 30, "")
MaterialTheme {
PlantDetailContent(plant)
}
}
Bản xem trước:
Lúc này, bạn đã di chuyển tất cả nội dung bên trong ConstraintLayout
gốc sang Compose. Bạn có thể chạy ứng dụng để kiểm tra xem ứng dụng có hoạt động như mong đợi không.
10. Phương thức ViewCompositionStrategy
Theo mặc định, Compose sẽ xử lý Bản sáng tác bất cứ khi nào ComposeView
tách khỏi cửa sổ. Điều này là ngoài mong muốn khi ComposeView
được sử dụng trong các mảnh vì nhiều lý do:
- Bản sáng tác phải tuân theo vòng đời xem của mảnh đối với loại
View
Giao diện người dùng Compose để lưu trạng thái, và - để giữ cho các thành phần trên giao diện người dùng Compose trên màn hình khi quá trình chuyển đổi hoặc chuyển đổi cửa sổ xảy ra. Trong quá trình chuyển đổi,
ComposeView
vẫn hiển thị ngay cả khi đã tách khỏi cửa sổ.
Bạn có thể gọi theo cách thủ công AbstractComposeView.disposeComposition
để vứt bỏ Bản sáng tác theo cách thủ công. Ngoài ra, để tự động xử lý Bản sáng tác khi chúng không cần nữa, hãy đặt một cách khác hoặc tạo chiến lược của riêng bạn bằng cách gọi phương thức setViewCompositionStrategy
.
Sử dụng chiến lược DisposeOnViewTreeLifecycleDestroyed
để loại bỏ Bản sáng tác khi LifecycleOwner
của mảnh bị huỷ bỏ.
Dưới dạngPlantDetailFragment
có chuyển đổi nhập và thoát (kiểm tranav_garden.xml
để biết thêm thông tin), chúng tôi sẽ sử dụng loại View
trong Compose sau, chúng ta cần đảm bảoComposeView
sử dụngDisposeOnViewTreeLifecycleDestroyed
chiến lược. Tuy nhiên, phương thức hay là luôn đặt chiến lược này khi sử dụng ComposeView
trong các mảnh.
plantdetail/PlantDetailFragment.kt
import androidx.compose.ui.platform.ViewCompositionStrategy
...
class PlantDetailFragment : Fragment() {
...
override fun onCreateView(...): View? {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
...
composeView.apply {
// Dispose the Composition when the view's LifecycleOwner
// is destroyed
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
setContent {
MaterialTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
...
}
}
11. Giao diện tương tác
Chúng ta đã di chuyển nội dung văn bản của thông tin chi tiết về cây sang Compose. Tuy nhiên, bạn có thể nhận thấy Compose không sử dụng màu giao diện phù hợp. Nó sử dụng màu tím cho tên cây trong khi cần phải sử dụng màu xanh lục.
Ở giai đoạn đầu của di chuyển này, bạn có thể muốn hướng Compose theo giao diện có sẵn trong hệ thống Chế độ xem thay vì viết lại giao diện Material của riêng bạn trong Compose từ lúc ban đầu. Các giao diện Material hoạt động hoàn hảo với tất cả các thành phần Material Design đi kèm với Compose.
Để sử dụng lại giao diện Thành phần Material Design (MDC) của hệ thống Chế độ xem trong Compose, bạn có thể sử dụng compose-theme-adapter. Hàm MdcTheme
sẽ tự động đọc giao diện MDC của ngữ cảnh máy chủ và chuyển các giao diện này sang MaterialTheme
cho cả giao diện sáng lẫn tối. Mặc dù bạn chỉ cần màu sắc giao diện cho lớp học lập trình này, thư viện cũng có thể đọc các hình dạng và kiểu chữ của hệ thống Chế độ xem.
Thư viện đã được đưa vào tệp app/build.gradle
như sau:
...
dependencies {
...
implementation "com.google.android.material:compose-theme-adapter:$rootProject.composeVersion"
...
}
Để sử dụng thuộc tính này, hãy thay thế MaterialTheme
bằng MdcTheme
. Ví dụ: trong PlantDetailFragment
:
PlantDetailFragment.kt
class PlantDetailFragment : Fragment() {
...
composeView.apply {
...
setContent {
MdcTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
Và tất cả thành phần kết hợp xem trước trong tệp PlantDetailDescription.kt
:
PlantDetailDescription.kt
@Preview
@Composable
private fun PlantDetailContentPreview() {
val plant = Plant("id", "Apple", "HTML<br><br>description", 3, 30, "")
MdcTheme {
PlantDetailContent(plant)
}
}
@Preview
@Composable
private fun PlantNamePreview() {
MdcTheme {
PlantName("Apple")
}
}
@Preview
@Composable
private fun PlantWateringPreview() {
MdcTheme {
PlantWatering(7)
}
}
@Preview
@Composable
private fun PlantDescriptionPreview() {
MdcTheme {
PlantDescription("HTML<br><br>description")
}
}
Như bạn có thể thấy trong bản xem trước, MdcTheme
đang chọn màu từ giao diện trong tệp styles.xml
.
Bạn cũng có thể xem trước giao diện người dùng trong giao diện tối bằng cách tạo một hàm mới và chuyển Configuration.UI_MODE_NIGHT_YES
đến uiMode
của bản xem trước:
import android.content.res.Configuration
...
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PlantDetailContentDarkPreview() {
val plant = Plant("id", "Apple", "HTML<br><br>description", 3, 30, "")
MdcTheme {
PlantDetailContent(plant)
}
}
Bản xem trước:
Nếu bạn chạy ứng dụng, nó hoạt động giống hệt như trước khi di chuyển ở cả giao diện sáng lẫn tối:
12. Thử nghiệm
Sau khi di chuyển các phần của màn hình thông tin chi tiết về cây sang Compose, việc kiểm tra rất quan trọng để đảm bảo bạn không làm hỏng bất kỳ nội dung nào.
Trong Sunflower, PlantDetailFragmentTest
nằm trong thư mục androidTest
sẽ kiểm tra một số chức năng của ứng dụng. Hãy mở tệp và xem mã hiện tại:
testPlantName
kiểm tra tên của cây trên màn hìnhtestShareTextIntent
kiểm tra để đảm bảo ý định phù hợp được kích hoạt sau khi nhấn vào nút chia sẻ
Khi một hoạt động hoặc mảnh sử dụng Compose, thay vì sử dụng ActivityScenarioRule
, bạn cần phải sử dụng createAndroidComposeRule
tích hợp ActivityScenarioRule
với ComposeTestRule
để có thể kiểm tra mã Compose.
Trong PlantDetailFragmentTest
, hãy thay thế việc sử dụng ActivityScenarioRule
bằng createAndroidComposeRule
. Khi cần quy tắc hoạt động để định cấu hình thử nghiệm, hãy dùng thuộc tính activityRule
từ createAndroidComposeRule
như sau:
@RunWith(AndroidJUnit4::class)
class PlantDetailFragmentTest {
@Rule
@JvmField
val composeTestRule = createAndroidComposeRule<GardenActivity>()
...
@Before
fun jumpToPlantDetailFragment() {
populateDatabase()
composeTestRule.activityRule.scenario.onActivity { gardenActivity ->
activity = gardenActivity
val bundle = Bundle().apply { putString("plantId", "malus-pumila") }
findNavController(activity, R.id.nav_host).navigate(R.id.plant_detail_fragment, bundle)
}
}
...
}
Nếu bạn chạy thử nghiệm, testPlantName
sẽ không thành công! testPlantName
kiểm tra để tìm một TextView hiển thị trên màn hình. Tuy nhiên, bạn đã di chuyển phần đó của giao diện người dùng sang Compose. Do đó, bạn cần phải sử dụng tính năng Xác nhận Compose:
@Test
fun testPlantName() {
composeTestRule.onNodeWithText("Apple").assertIsDisplayed()
}
Nếu chạy thử, bạn sẽ thấy tất cả các thử nghiệm này đạt.
13. Xin chúc mừng
Xin chúc mừng, bạn đã hoàn tất thành công lớp học lập trình này!
nhánh compose
của dự án Sunflower github gốc sẽ di chuyển hoàn toàn màn hình thông tin chi tiết về cây sang Compose. Ngoài những việc đã hoàn thành trong lớp học lập trình này, bạn còn cần mô phỏng hành vi của CollapsingThanhLayout. Việc này bao gồm:
- Tải hình ảnh bằng Compose
- Ảnh động
- Xử lý phương diện tốt hơn
- Và nhiều kiến thức khác!
Tiếp theo là gì?
Hãy tham khảo các lớp học lập trình khác trên Lộ trình học Compose.
Tài liệu đọc thêm
- Di chuyển với Jetpack Compose cùng một mã
- Hướng dẫn về Compose trong ứng dụng hiện tại
- Cần cẩu, ứng dụng mẫu, nhúng MapView trong Compose