Как использовать представления в режиме создания

Вы можете включить иерархию представлений Android в интерфейс Compose. Этот подход особенно полезен, если вы хотите использовать элементы интерфейса, которые ещё недоступны в Compose, например AdView. Кроме того, этот подход позволяет повторно использовать созданные вами представления.

Чтобы добавить элемент или иерархию представления, используйте функцию AndroidView composable. Функции AndroidView передается лямбда-выражение, которое возвращает View. AndroidView также предоставляет обратный вызов update, который вызывается при раздувании представления. AndroidView перекомпоновывается при каждом изменении State, прочитанного в функции обратного вызова. AndroidView, как и многие другие встроенные composable-функции, принимает параметр Modifier, который можно использовать, например, для установки позиции в родительской composable-функции.

@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 с привязкой к представлению

Чтобы встроить макет XML, используйте API AndroidViewBinding, который предоставляется библиотекой androidx.compose.ui:ui-viewbinding. Для этого в проекте необходимо включить привязку представлений.

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

AndroidView в ленивых списках

Если вы используете AndroidView в ленивом списке (LazyColumn, LazyRow, Pager и т. д.), попробуйте использовать перегрузку AndroidView, добавленную в версии 1.4.0-rc01. Эта перегрузка позволяет Compose повторно использовать базовый экземпляр View, когда содержащая композиция используется повторно, как в случае со списками Lazy.

В перегрузке AndroidView добавлены два дополнительных параметра:

  • onReset – обратный вызов, который вызывается, чтобы сообщить, что объект View скоро будет использован повторно. Чтобы включить повторное использование представления, это значение не должно быть нулевым.
  • onRelease (необязательно) – обратный вызов, который сигнализирует о том, что View вышел из композиции и не будет использоваться повторно.

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

Фрагменты в Compose (переходный этап)

Чтобы добавить Fragment в Compose, используйте composable-функцию AndroidFragment. AndroidFragment имеет фрагмент, который удаляется, когда компонуемый объект покидает композицию.

Чтобы добавить фрагмент, используйте composable-функцию AndroidFragment. Вы передаете класс Fragment в AndroidFragment, который затем добавляет экземпляр этого класса непосредственно в композицию. AndroidFragment также предоставляет объект fragmentState для создания AndroidFragment с заданным состоянием, arguments для передачи в новый фрагмент и обратный вызов onUpdate, который предоставляет фрагмент из композиции. Как и многие другие встроенные composable-функции, AndroidFragment принимает параметр Modifier, который можно использовать, например, чтобы задать его положение в родительской composable-функции.

Вы можете вызвать функцию AndroidFragment в режиме создания письма следующим образом:

@Composable
fun FragmentInComposeExample() {
    AndroidFragment<MyFragment>()
}

Ограничение статуса жизненного цикла фрагмента

В Fragment 1.9.0 и более поздних версиях можно использовать параметр maxLifecycle, чтобы ограничить состояние жизненного цикла встроенного Fragment.

Например, в HorizontalPager можно динамически задать для параметра maxLifecycle значение RESUMED, только если соответствующая страница активна, а для страниц, которые не видны на экране, установить ограничение STARTED. Когда пользователь переходит на другую страницу, ограничение фрагмента предыдущей страницы становится равным STARTED. Это приостанавливает или отменяет рабочие нагрузки, относящиеся к области действия RESUMED (например, сопрограммы, выполняющиеся в repeatOnLifecycle(RESUMED)), предотвращая выполнение фоновых задач на невидимых страницах, сохраняя при этом инициализацию интерфейса на уровне STARTED:

HorizontalPager(state = pagerState) { page ->
    // Dynamically cap the lifecycle state based on whether the page is selected
    val maxLifecycle = if (pagerState.settledPage == page) {
        Lifecycle.State.RESUMED
    } else {
        Lifecycle.State.STARTED
    }

    when (page) {
        0 -> AndroidFragment<HomeFragment>(maxLifecycle = maxLifecycle)
        1 -> AndroidFragment<LibraryFragment>(maxLifecycle = maxLifecycle)
        /* Other pages and corresponding fragments */
    }
}

Как вызывать фреймворк Android из Compose

Compose работает в рамках классов Android. Например, он размещается в классах Android View, таких как Activity или Fragment, и может использовать классы фреймворка Android, такие как Context, системные ресурсы, Service или BroadcastReceiver.

Подробнее о системных ресурсах в Compose…

Местные композиции

CompositionLocal классы позволяют неявно передавать данные через composable-функции. Обычно они указываются в определенном узле дерева интерфейса. Это значение могут использовать его composable-потомки, не объявляя CompositionLocal как параметр в composable-функции.

CompositionLocal используется для передачи значений типов фреймворка Android в Compose, например Context, Configuration или View, в котором размещен код Compose, с соответствующими значениями LocalContext, LocalConfiguration или LocalView. Обратите внимание, что классы CompositionLocal имеют префикс Local, чтобы их было проще находить с помощью автозаполнения в IDE.

Чтобы получить текущее значение CompositionLocal, используйте его свойство current. Например, в приведенном ниже коде показано, как вывести сообщение, передав LocalContext.current в метод Toast.makeToast.

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

Широковещательные приемники

Чтобы показать CompositionLocal и побочные эффекты, если BroadcastReceiver нужно зарегистрировать из composable-функции, используйте LocalContext, чтобы использовать текущий контекст, а также побочные эффекты rememberUpdatedState и 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 */
}

Другие взаимодействия

Если для нужного вам взаимодействия не задана утилита, следуйте общему правилу Compose: данные передаются вниз, а события – вверх (подробнее об этом рассказывается в разделе Как мыслить в стиле Compose). Например, эта composable-функция запускает другое действие:

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