Khi sử dụng Compose trong ứng dụng, bạn có thể kết hợp Compose với giao diện người dùng dựa trên Khung hiển thị. Dưới đây là danh sách các API, kiến nghị và mẹo để khiến việc chuyển đổi sang Compose dễ dàng hơn.
Compose trong Chế độ xem
Bạn có thể thêm giao diện người dùng của Compose vào ứng dụng hiện có sử dụng thiết kế dựa trên thành phần hiển thị
Để tạo một màn hình mới hoàn toàn dựa trên Compose, hãy gọi phương thức setContent()
và truyền bất kỳ hàm có khả năng kết hợp nào mà bạn muốn.
class ExampleActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // In here, we can call composables! MaterialTheme { Greeting(name = "compose") } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") }
Đoạn mã trên được viết giống hệt những gì bạn sẽ thấy trong bất kỳ ứng dụng thuần Compose nào
Phương thức ViewCompositionStrategy của lớp ComposeView
Theo mặc định, Compose sẽ huỷ bỏ Cấu trúc (Composition) bất cứ khi nào thành phần hiển thị bị tách khỏi cửa sổ. Các loại View
giao diện người dùng của Compose như lớp ComposeView
và lớp AbstractComposeView
sử dụng lớp giao tiếp ViewCompositionStrategy
để thực hiện tác vụ này.
Theo mặc định, Compose sẽ sử dụng lớp giao tiếp DisposeOnDetachedFromWindowOrReleasedFromPool
. Tuy nhiên, giá trị mặc định này có thể gây phiền phức trong một số trường hợp khi sử dụng các loại View
Giao diện người dùng của Compose trong:
Mảnh. Cấu trúc sử dụng phải tuân thủ theo vòng đời của thành phần hiển thị mảnh dành cho
View
Giao diện người dùng của Compose để lưu lại trạng thái.Chuyển đổi. Bất cứ khi nào
View
trên giao diện người dùng Compose được dùng như một phần của quá trình chuyển đổi, nó sẽ bị tách ra khỏi cửa sổ ngay khi quá trình chuyển đổi bắt đầu chứ không phải là khi quá trình chuyển đổi kết thúc, điều này khiến cho thành phần kết hợp bỏ qua trạng thái của nó trong khi vẫn hiện trên màn hình.View
tuỳ chỉnh của riêng bạn do vòng đời quản lý.
Trong một số trường hợp như vậy, ứng dụng cũng có thể dần bị rò rỉ bộ nhớ từ các thực thể Cấu trúc (Composition), trừ phi bạn gọi phương thức AbstractComposeView.disposeComposition
theo cách thủ công.
Để tự động huỷ bỏ Cấu trúc không cần dùng đến, hãy đặt một chiến lược khác hoặc tự tạo chiến lược riêng bằng cách gọi phương thức setViewCompositionStrategy
. Ví dụ: chiến lược DisposeOnLifecycleDestroyed
sẽ huỷ bỏ Các thành phần khi lifecycle
bị huỷ. Chiến lược này phù hợp với các loại View
Giao diện người dùng Compose có chung mối liên kết 1:1 với một lớp giao tiếp LifecycleOwner
đã biết.
Khi LifecycleOwner
là không xác định, DisposeOnViewTreeLifecycleDestroyed
sẽ được dùng.
Xem API này trong thực tế tại ComposeView trong Mảnh (Fragment).
ComposeView trong Mảnh (Fragment)
Nếu bạn muốn kết hợp nội dung từ Giao diện người dùng Compose trong một mảnh (fragment) hoặc bố cục Thành phần hiển thị (View layout) hiện có, hãy sử dụng lớp ComposeView
và gọi phương thức setContent()
. Lớp ComposeView
là một lớp cơ sở View
của Android.
Bạn có thể đặt lớp ComposeView
vào bố cục XML giống như đối với bất kỳ lớp cơ sở View
nào khác:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/hello_world"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello Android!" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Trong mã nguồn Kotlin, hãy tăng cường sử dụng bố cục trong tài nguyên bố cục được định nghĩa trong XML. Tiếp theo, tạo lớp ComposeView
bằng mã XML, đặt chiến lược Bố cục phù hợp nhất với máy chủ View
và gọi phương thức setContent()
để sử dụng tính năng từ Compose.
class ExampleFragment : Fragment() { private var _binding: FragmentExampleBinding? = null // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentExampleBinding.inflate(inflater, container, false) val view = binding.root binding.composeView.apply { // Dispose of the Composition when the view's LifecycleOwner // is destroyed setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { // In Compose world MaterialTheme { Text("Hello Compose!") } } } return view } override fun onDestroyView() { super.onDestroyView() _binding = null } }
Hình 1. Hình trên thể hiện kết quả chạy của đoạn mã trên sau khi thêm các phần tử Compose vào một hệ phân cấp Giao diện người dùng. Dòng chữ "Hello Android!" ("Xin chào Android!") hiển thị nhờ tiện ích TextView
. Dòng chữ "Hello Compose!" ("Xin chào Compose!") hiển thị nhờ phần tử Compose.
Bạn cũng có thể trực tiếp đưa lớp ComposeView
vào một mảnh nếu chế độ toàn màn hình của bạn được tạo bằng Compose. Điều này cho phép bạn tránh hoàn toàn việc sử dụng tệp bố cục XML.
class ExampleFragmentNoXml : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { // Dispose of the Composition when the view's LifecycleOwner // is destroyed setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { MaterialTheme { // In Compose world Text("Hello Compose!") } } } } }
Nhiều ComposeView trong cùng một bố cục
Nếu có nhiều phần tử ComposeView
trong cùng một bố cục, thì mỗi phần tử phải có một mã nhận dạng duy nhất để biến savedInstanceState
hoạt động.
class ExampleFragmentMultipleComposeView : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View = LinearLayout(requireContext()).apply { addView( ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) id = R.id.compose_view_x // ... } ) addView(TextView(requireContext())) addView( ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) id = R.id.compose_view_y // ... } ) } }
Mã nhận dạng ComposeView
được định nghĩa trong tệp res/values/ids.xml
:
<resources>
<item name="compose_view_x" type="id" />
<item name="compose_view_y" type="id" />
</resources>
Khung hiển thị trong Compose
Bạn có thể đưa một hệ phân cấp khung hiển thị Android vào giao diện người dùng Compose. Phương pháp này đặc biệt hữu ích nếu bạn muốn sử dụng các thành phần trên giao diện người dùng chưa có trong Compose, chẳng hạn như AdView
.
Phương pháp này cũng cho phép bạn tái sử dụng các khung hiển thị tuỳ chỉnh mà bạn đã thiết kế.
Để gộp thêm một phần tử hoặc một hệ thành phần hiển thị phân cấp, hãy sử dụng thành phần kết hợp AndroidView
.
AndroidView
sẽ được truyền một hàm lambda để trả về một lớp View
. AndroidView
cũng cung cấp một lệnh gọi lại lệnh update
khi lượt xem tăng cao. AndroidView
sẽ tái cấu trúc lại bất cứ khi nào một biểu thị State
giữa các hàm callback thay đổi. Cũng như nhiều thành phần kết hợp được tích hợp sẵn khác, AndroidView
sẽ sử dụng một tham số Modifier
để đặt vị trí của nó trong thành phần kết hợp cha, chẳng hạn như vậy.
@Composable fun CustomView() { var selectedItem by remember { mutableStateOf(0) } // Adds view to Compose AndroidView( modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree factory = { context -> // Creates view MyView(context).apply { // Sets up listeners for View -> Compose communication setOnClickListener { selectedItem = 1 } } }, update = { view -> // View's been inflated or state read in this block has been updated // Add logic here if necessary // As selectedItem is read here, AndroidView will recompose // whenever the state changes // Example of Compose -> View communication view.selectedItem = selectedItem } ) } @Composable fun ContentExample() { Column(Modifier.fillMaxSize()) { Text("Look at this CustomView!") CustomView() } }
Để nhúng bố cục XML bất kỳ, hãy sử dụng API AndroidViewBinding
do thư viện androidx.compose.ui:ui-viewbinding
cung cấp. Để làm được điều này, dự án của bạn cần bật tính năng liên kết khung hiển thị.
@Composable fun AndroidViewBindingExample() { AndroidViewBinding(ExampleLayoutBinding::inflate) { exampleView.setBackgroundColor(Color.GRAY) } }
Các mảnh trong Compose
Sử dụng thành phần kết hợp AndroidViewBinding
để thêm một Fragment
vào Compose.
AndroidViewBinding
có quy trình xử lý dành riêng cho mảnh, chẳng hạn như xoá mảnh khi thành phần kết hợp rời khỏi cấu trúc.
Hãy làm việc này bằng cách tăng cường tệp XML chứa FragmentContainerView
dưới dạng chủ sở hữu của Fragment
.
Chẳng hạn nếu đã xác định được my_fragment_layout.xml
, bạn có thể sử dụng mã như thế này trong khi thay thế thuộc tính XML android:name
bằng tên lớp Fragment
của bạn:
<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container_view"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:name="com.example.MyFragment" />
Tăng cường mảnh này trong Compose như sau:
@Composable fun FragmentInComposeExample() { AndroidViewBinding(MyFragmentLayoutBinding::inflate) { val myFragment = fragmentContainerView.getFragment<MyFragment>() // ... } }
Nếu cần sử dụng nhiều mảnh trong cùng một bố cục, hãy đảm bảo là bạn đã xác định một mã nhận dạng duy nhất cho mỗi FragmentContainerView
.
Gọi khung Android qua Compose
Tính năng compose hoạt động trong các lớp của khung Android. Ví dụ: tệp được lưu trữ trên các lớp Khung hiển thị Android (như Activity
hoặc Fragment
) và có thể cần tận dụng các lớp khác của khung Android (như Context
), tài nguyên hệ thống, Service
hoặc BroadcastReceiver
.
Để tìm hiểu thêm về tài nguyên hệ thống, hãy xem thêm tài liệu Tài nguyên trong Compose.
Composition Locals
Lớp CompositionLocal
cho phép truyền dữ liệu trực tiếp thông qua các hàm tổng hợp. Những lớp này thường được cấp một giá trị nào đó ở một nút nhất định trong cây Giao diện người dùng. Giá trị con có thể kết hợp của hàm đó có thể sử dụng giá trị đó mà không cần khai báo lớp CompositionLocal
dưới dạng tham số trong hàm có khả năng kết hợp.
Lớp CompositionLocal
được dùng để truyền giá trị cho các loại khung Android trong Compose như Context
, Configuration
hoặc View
mà trong đó mã Compose lưu trữ bởi các biến LocalContext
, LocalConfiguration
, hoặc LocalView
tương ứng.
Lưu ý rằng các lớp CompositionLocal
có tiền tố Local
để chức năng tự động điền trong IDE dễ nhận diện hơn.
Truy cập giá trị hiện tại của CompositionLocal
bằng cách sử dụng thuộc tính current
. Ví dụ: mã dưới đây sẽ hiển thị một thông báo ngắn bằng cách đưa LocalContext.current
vào phương thức Toast.makeToast
.
@Composable fun ToastGreetingButton(greeting: String) { val context = LocalContext.current Button(onClick = { Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show() }) { Text("Greet") } }
Để có ví dụ hoàn chỉnh hơn, hãy xem thêm mục Nghiên cứu điển hình: BroadcastReceivers ở cuối tài liệu này.
Các hoạt động tương tác khác
Nếu không có tiện ích nào được xác định cho các hoạt động tương tác bạn cần thì cách tốt nhất là làm theo hướng dẫn Compose chung, dữ liệu chạy xuống, sự kiện chạy lên (được thảo luận kỹ hơn trong phần Tư duy trong Compose). Ví dụ, hoạt động tổng hợp này sẽ khởi chạy một hoạt động khác:
class OtherInteractionsActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // get data from savedInstanceState setContent { MaterialTheme { ExampleComposable(data, onButtonClick = { startActivity(Intent(this, MyActivity::class.java)) }) } } } } @Composable fun ExampleComposable(data: DataExample, onButtonClick: () -> Unit) { Button(onClick = onButtonClick) { Text(data.title) } }
Nghiên cứu điển hình: BroadcastReceivers
Để có một ví dụ thực tế hơn về các tính năng mà bạn muốn di chuyển hoặc triển khai trong Compose và để hiển thị CompositionLocal
với các hiệu ứng phụ, hãy giả sử BroadcastReceiver
cần được đăng ký qua một hàm có khả năng kết hợp.
Giải pháp này tận dụng LocalContext
để sử dụng trong trường hợp hiện tại, và rememberUpdatedState
cũng như hiệu ứng lề DisposableEffect
.
@Composable fun SystemBroadcastReceiver( systemAction: String, onSystemEvent: (intent: Intent?) -> Unit ) { // Grab the current context in this part of the UI tree val context = LocalContext.current // Safely use the latest onSystemEvent lambda passed to the function val currentOnSystemEvent by rememberUpdatedState(onSystemEvent) // If either context or systemAction changes, unregister and register again DisposableEffect(context, systemAction) { val intentFilter = IntentFilter(systemAction) val broadcast = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { currentOnSystemEvent(intent) } } context.registerReceiver(broadcast, intentFilter) // When the effect leaves the Composition, remove the callback onDispose { context.unregisterReceiver(broadcast) } } } @Composable fun HomeScreen() { SystemBroadcastReceiver(Intent.ACTION_BATTERY_CHANGED) { batteryStatus -> val isCharging = /* Get from batteryStatus ... */ true /* Do something if the device is charging */ } /* Rest of the HomeScreen */ }