Korzystanie z widoków w sekcji Utwórz

W interfejsie Compose możesz umieścić hierarchię widoków Androida. To podejście jest szczególnie przydatne, jeśli chcesz używać elementów interfejsu, które nie są jeszcze dostępne w Compose, np. AdView. Dzięki temu możesz też ponownie wykorzystywać zaprojektowane przez siebie widoki niestandardowe.

Aby uwzględnić element widoku lub hierarchię, użyj funkcji kompozycyjnej AndroidView . AndroidView otrzymuje wyrażenie lambda, które zwraca wartość View. AndroidView udostępnia też updatewywołanie zwrotne, które jest wywoływane po rozwinięciu widoku. Funkcja AndroidView jest ponownie komponowana, gdy zmieni się wartość State odczytana w wywołaniu zwrotnym. AndroidView, podobnie jak wiele innych wbudowanych funkcji kompozycyjnych, przyjmuje parametr Modifier, którego można użyć np. do ustawienia jego pozycji w funkcji kompozycyjnej nadrzędnej.

@Composable
fun CustomView() {
    var selectedItem by remember { mutableIntStateOf(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()
    }
}

AndroidView z powiązaniem widoku

Aby osadzić układ XML, użyj interfejsu API AndroidViewBinding, który jest udostępniany przez bibliotekę androidx.compose.ui:ui-viewbinding. Aby to zrobić, musisz włączyć w projekcie powiązanie widoku.

@Composable
fun AndroidViewBindingExample() {
    AndroidViewBinding(ExampleLayoutBinding::inflate) {
        exampleView.setBackgroundColor(Color.GRAY)
    }
}

AndroidView na listach leniwych

Jeśli używasz AndroidView na liście Lazy (LazyColumn, LazyRow, Pager itp.), rozważ użycie przeciążenia AndroidView wprowadzonego w wersji 1.4.0-rc01. Ten overload umożliwia Compose ponowne użycie bazowego wystąpienia View, gdy zawierająca kompozycja jest ponownie używana w takiej samej postaci, jak w przypadku list leniwych.

Ten przeciążony operator AndroidView dodaje 2 dodatkowe parametry:

  • onReset – wywołanie zwrotne, które sygnalizuje, że obiekt View ma zostać ponownie użyty. Aby włączyć ponowne użycie widoku, ta wartość musi być różna od null.
  • onRelease (opcjonalnie) – wywołanie zwrotne, które jest wywoływane, aby zasygnalizować, że View opuścił kompozycję i nie będzie już ponownie używany.

@Composable
fun AndroidViewInLazyList() {
    LazyColumn {
        items(100) { index ->
            AndroidView(
                modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
                factory = { context ->
                    MyView(context)
                },
                update = { view ->
                    view.selectedItem = index
                },
                onReset = { view ->
                    view.clear()
                }
            )
        }
    }
}

Fragmenty w Compose

Aby dodać Fragment w Compose, użyj komponentu AndroidViewBinding. AndroidViewBinding ma obsługę specyficzną dla fragmentu, np. usuwanie fragmentu, gdy funkcja kompozycyjna opuszcza kompozycję.

Aby to zrobić, rozwiń plik XML zawierający element FragmentContainerView jako kontener dla elementu Fragment.

Jeśli na przykład masz zdefiniowany element my_fragment_layout.xml, możesz użyć takiego kodu, zastępując atrybut XML android:name nazwą klasy elementu Fragment:

<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="com.example.compose.snippets.interop.MyFragment" />

Rozwiń ten fragment w Compose w ten sposób:

@Composable
fun FragmentInComposeExample() {
    AndroidViewBinding(MyFragmentLayoutBinding::inflate) {
        val myFragment = fragmentContainerView.getFragment<MyFragment>()
        // ...
    }
}

Jeśli w tym samym układzie musisz użyć kilku fragmentów, upewnij się, że każdy z nich ma unikalny identyfikator FragmentContainerView.

Wywoływanie platformy Android z Compose

Compose działa w ramach klas platformy Android. Jest ona na przykład hostowana w klasach widoku Androida, takich jak Activity lub Fragment, i może korzystać z klas platformy Androida, takich jak Context, zasoby systemowe Service lub BroadcastReceiver.

Więcej informacji o zasobach systemowych znajdziesz w artykule Zasoby w Compose.

Composition Locals

CompositionLocal klasy umożliwiają niejawne przekazywanie danych za pomocą funkcji, które można łączyć. Zwykle mają one wartość w określonym węźle drzewa interfejsu. Jej wartość może być używana przez elementy kompozycyjne pochodne bez deklarowania CompositionLocal jako parametru w funkcji kompozycyjnej.

Symbol CompositionLocal służy do propagowania wartości typów platformy Android w Compose, takich jak Context, Configuration lub View, w którym jest hostowany kod Compose, z odpowiednimi wartościami LocalContext, LocalConfiguration lub LocalView. Pamiętaj, że klasy CompositionLocal mają przedrostek Local, co ułatwia ich wyszukiwanie za pomocą autouzupełniania w IDE.

Aby uzyskać dostęp do bieżącej wartości CompositionLocal, użyj właściwości current. Na przykład poniższy kod wyświetla komunikat w formie toastu, przekazując LocalContext.current do metody Toast.makeToast.

@Composable
fun ToastGreetingButton(greeting: String) {
    val context = LocalContext.current
    Button(onClick = {
        Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show()
    }) {
        Text("Greet")
    }
}

Bardziej szczegółowy przykład znajdziesz w sekcji Studium przypadku: BroadcastReceivers na końcu tego dokumentu.

Inne interakcje

Jeśli dla interakcji, której potrzebujesz, nie ma zdefiniowanego narzędzia, najlepszym rozwiązaniem jest przestrzeganie ogólnych wytycznych dotyczących Compose: dane przepływają w dół, a zdarzenia w górę (więcej informacji znajdziesz w artykule Myślenie w Compose). Na przykład ten komponent kompozycyjny uruchamia inną aktywność:

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

Studium przypadku: odbiorniki

Aby przedstawić bardziej realistyczny przykład funkcji, które możesz chcieć przenieść lub zaimplementować w Compose, oraz zaprezentować CompositionLocalefekty uboczne, załóżmy, że z funkcji kompozycyjnej trzeba zarejestrować BroadcastReceiver.

Rozwiązanie korzysta z funkcji LocalContext, aby używać bieżącego kontekstu, oraz z efektów ubocznych rememberUpdatedStateDisposableEffect.

@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 */
}

Dalsze kroki

Znasz już interfejsy API interoperacyjności podczas korzystania z Compose w widokach i odwrotnie. Aby dowiedzieć się więcej, zapoznaj się ze stroną Inne kwestie.