Como usar o Compose em visualizações

É possível adicionar uma interface com base no Compose a um app já existente que usa um design com base em visualização.

Para criar uma tela totalmente baseada no Compose, faça sua atividade chamar o método setContent() e transmitir as funções combináveis que você quer usar.

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

Esse código é parecido com o que você encontraria em um app feito inteiramente com o Compose.

ViewCompositionStrategy por ComposeView

ViewCompositionStrategy define quando a composição precisa ser descartada. O padrão, ViewCompositionStrategy.Default, descarta a composição quando o ComposeView subjacente se desanexa da janela, a menos que faça parte de um contêiner de pool, como um RecyclerView. Em um app de atividade única do Compose, esse comportamento padrão é o que você quer. No entanto, se você estiver adicionando o Compose de forma incremental à sua base de código, esse comportamento pode causar perda de estado em alguns cenários.

Para mudar o ViewCompositionStrategy, chame o método setViewCompositionStrategy() e forneça uma estratégia diferente.

A tabela abaixo resume os diferentes cenários em que você pode usar ViewCompositionStrategy:

ViewCompositionStrategy Descrição e cenário de interoperabilidade
DisposeOnDetachedFromWindow A composição será descartada quando a ComposeView for removida da janela. Foi substituído por DisposeOnDetachedFromWindowOrReleasedFromPool.

Cenário de interoperabilidade:

* ComposeView se é o único elemento na hierarquia de visualização ou no contexto de uma tela de visualização/composição mista (não no fragmento).
DisposeOnDetachedFromWindowOrReleasedFromPool (padrão) Semelhante a DisposeOnDetachedFromWindow, quando a composição não está em um contêiner de agrupamento, como um RecyclerView. Se ele estiver em um contêiner de pool, ele será descartado quando o contêiner de pool se desconectar da janela ou quando o item for descartado (ou seja, quando o pool estiver cheio).

Cenário de interoperabilidade:

* ComposeView se ele for o único elemento na hierarquia de visualização ou no contexto de uma tela de visualização/composição mista (não no fragmento).
* ComposeView como um item em um contêiner de agrupamento, como RecyclerView.
DisposeOnLifecycleDestroyed A composição será descartada quando o Lifecycle fornecido for destruído.

Cenário de Interop

* ComposeView na visualização de um fragmento.
DisposeOnViewTreeLifecycleDestroyed A composição será descartada quando a Lifecycle pertencente à LifecycleOwner retornada por ViewTreeLifecycleOwner.get da próxima janela à qual a visualização está anexada for destruída.

Cenário de interoperabilidade:

* ComposeView na visualização de um fragmento.
* ComposeView em uma visualização em que o ciclo de vida ainda não é conhecido.

ComposeView em fragmentos

Se você quiser incorporar o conteúdo da interface do Compose em um fragmento ou um layout de visualização já existente, use ComposeView e chame o método setContent() dele. ComposeView é uma View para Android.

Você pode colocar a ComposeView no seu layout XML como qualquer outra View:

<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/text"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content" />

  <androidx.compose.ui.platform.ComposeView
      android:id="@+id/compose_view"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />
</LinearLayout>

No código-fonte do Kotlin, infle o layout usando o recurso de layout definido no XML. Em seguida, acesse a ComposeView usando o ID do XML, defina uma estratégia de composição que funcione melhor para a View host e chame setContent() para usar o Compose.

class ExampleFragmentXml : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val view = inflater.inflate(R.layout.fragment_example, container, false)
        val composeView = view.findViewById<ComposeView>(R.id.compose_view)
        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
    }
}

Como alternativa, você também pode usar a vinculação de visualizações para acessar referências ao ComposeView referenciando a classe de vinculação gerada para seu arquivo de layout XML:

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

Dois elementos de texto um pouco diferentes, um acima do outro

Figura 1. Isso mostra a saída do código que adiciona elementos do Compose a uma hierarquia de interface de visualização. A mensagem "Hello Android!" é exibida por um widget TextView. A mensagem "Hello Compose!" é exibida por um elemento de texto do Compose.

Também será possível incluir uma ComposeView diretamente em um fragmento se a tela cheia for criada com o Compose, o que permite evitar totalmente o uso de um arquivo de layout 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!")
                }
            }
        }
    }
}

Várias instâncias de ComposeView no mesmo layout

Se houver vários elementos ComposeView no mesmo layout, cada um precisará ter um ID exclusivo para que o savedInstanceState funcione.

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

Os IDs ComposeView são definidos no arquivo res/values/ids.xml:

<resources>
  <item name="compose_view_x" type="id" />
  <item name="compose_view_y" type="id" />
</resources>

Visualizar elementos combináveis no Layout Editor

Também é possível visualizar elementos combináveis no Layout Editor para o layout XML que contém uma ComposeView. Isso permite que você veja como seus elementos combináveis ficam em um layout misto de visualizações e Compose.

Digamos que você queira mostrar o elemento combinável a seguir no Layout Editor. Os elementos combináveis anotados com @Preview são bons candidatos para visualização no Layout Editor.

@Preview
@Composable
fun GreetingPreview() {
    Greeting(name = "Android")
}

Para mostrar esse elemento combinável, use o atributo de ferramentas tools:composableName e defina o valor como o nome totalmente qualificado do elemento combinável para pré-visualizar no layout.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <androidx.compose.ui.platform.ComposeView
      android:id="@+id/my_compose_view"
      tools:composableName="com.example.compose.snippets.interop.InteroperabilityAPIsSnippetsKt.GreetingPreview"
      android:layout_height="match_parent"
      android:layout_width="match_parent"/>

</LinearLayout>

Elemento combinável exibido no Layout Editor

Próximas etapas

Agora que você aprendeu sobre as APIs de interoperabilidade para usar o Compose em visualizações, saiba como usar as visualizações no Compose.