將 Compose 與現有 UI 整合

如果應用程式使用的是以 View 為基礎的 UI,您可能不想一次重寫整個 UI。這個頁面可協助您在現有的 UI 中增加新的 Compose 元素。

遷移共用的 UI

如要逐步遷移至 Compose,您可能需要在 Compose 和 View 系統中都使用共用的 UI 元素。舉例來說,如果應用程式具有自訂的 CallToActionButton 元件,您可能需要在 Compose 和以 View 為基礎的螢幕中都使用該元件。

在 Compose 中,共用 UI 元素會成為應用程式中能重複使用的可組合項,無論該元素採用 XML 樣式還是自訂檢視模式,都可以重複使用。舉例來說,您可以建立適用於自訂行動號召 Button 元件的 CallToActionButton 可組合元件。

如要在以檢視畫面為基礎的畫面中使用可組合項,您必須建立從 AbstractComposeView 擴充的自訂檢視包裝函式。在其覆寫的 Content 可組合元件中,將您建立的可組合元件納入您的 Compose 主題中,範例如下:

@Composable
fun CallToActionButton(
    text: String,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            containerColor = MaterialTheme.colorScheme.secondary
        ),
        onClick = onClick,
        modifier = modifier,
    ) {
        Text(text)
    }
}

class CallToActionViewButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : AbstractComposeView(context, attrs, defStyle) {

    var text by mutableStateOf("")
    var onClick by mutableStateOf({})

    @Composable
    override fun Content() {
        YourAppTheme {
            CallToActionButton(text, onClick)
        }
    }
}

請注意,可組合參數在自訂檢視中會變為可變動變數。這會使自訂 CallToActionViewButton 檢視變得可擴充且能使用,就如同傳統檢視一般,例如 View Binding。請參考以下範例:

class ViewBindingActivity : ComponentActivity() {

    private lateinit var binding: ActivityExampleBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.callToAction.apply {
            text = getString(R.string.greeting)
            onClick = { /* Do something */ }
        }
    }
}

如果自訂元件包含可變動狀態,請參閱「狀態真實資訊來源」一節。

遷移應用程式主題

Material Design 是為 Android 應用程式設定主題的推薦設計系統。

以 View 為基礎的應用程式有三種 Material 版本:

  • Material Design 1 使用 AppCompat 程式庫 (即 Theme.AppCompat.*)
  • Material Design 2 使用 MDC-Android 程式庫 (即 Theme.MaterialComponents.*)
  • Material Design 3 使用 MDC-Android 程式庫 (即 Theme.Material3.*)

Compose 應用程式有兩種可用的 Material 版本:

  • Material Design 2 使用 Compose Material 程式庫 (即 androidx.compose.material.MaterialTheme)
  • Material Design 3 使用 Compose Material 3 程式庫 (即 androidx.compose.material3.MaterialTheme)

如果應用程式的設計系統允許,建議您使用最新版 Material 3。View 和 Compose 都有適用的遷移指南:

無論使用的 Material Design 版本為何,在 Compose 中建立新畫面時,請務必先套用 MaterialTheme,再讓任何可組合項從 Compose Material 程式庫發出 UI。Material 元件 (ButtonText 等) 需要有 MaterialTheme,如果沒有,這些元件的行為將處於未定義狀態。

所有 Jetpack Compose 範例都會使用以 MaterialTheme 為基礎建構的自訂 Compose 主題。

詳情請參閱 Compose 中的設計系統將 XML 主題遷移至 Compose

WindowInsets 和 IME 動畫

自 Compose 1.2.0 起,您可以使用修飾符處理版面配置中的 WindowInsets,而且系統也支援 IME 動畫。

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
              MyScreen()
            }
        }
    }
}

@Composable
fun MyScreen() {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon( /* ... */)
        }
    }
}

顯示 UI 元素上下捲動來為鍵盤騰出空間的動畫

圖 2. IME 動畫

優先考慮將狀態從展示檔中分離出來

一般來說,View 是有狀態的。View 負責管理說明顯示 內容 的欄位,以及顯示 方式。將 View 轉換為 Compose 時,請考慮將轉譯的資料分隔開來,以達成單向資料流,如 狀態提升 中進一步說明的那樣。

舉例來說,View 具有 visibility 屬性,用於說明該屬性是可見的、隱藏的或是消失了。這是 View 的固有屬性。雖然其他程式碼可能會變更 View 的瀏覽權限,但只有 View 本身才知道目前的瀏覽權限是哪些。確保 View 可見的邏輯可能出錯,且通常與 View 本身有關。

相較之下,在使用 Kotlin 中有條件的邏輯時,Compose 能輕鬆顯示完全不同的可組合元件:

if (showCautionIcon) {
    CautionIcon(/* ... */)
}

在設計上,CautionIcon 沒有必要瞭解或在意其顯示的原因,也沒有 visibility 的概念:它如果不在 Composition 中,便是不在。

只要將狀態管理和呈現邏輯明確區隔,即可輕鬆將顯示內容方式變更為 UI 狀態的轉換項目。在需要時能夠提升狀態也讓 Composable 變得更容易使用,因為狀態擁有權更為靈活。

升級經過封裝的和可重複使用的元件

View 元素通常對於自己所在位置有一定瞭解:例如在 ActivityDialogFragment 中或在另一個 View 階層中的某個位置。這些類型通常是從靜態版面配置檔案中加載而來,因此 View 的整體結構往往相當嚴謹。這種做法可以建立更緊密的耦合,還會讓 View 更加難以變更或重複使用。

舉例來說,自訂 View 可能會假設其包含特定類型的子項檢視畫面,當中含有特定 ID,然後直接變更其屬性以回應某些動作。這會將這些 View 元素緊密耦合起來:如果找不到子項,自訂 View 可能會停止運作或損毀,而且沒有 View 父項,也可能無法重新使用子項。

在 Compose 中,由於有重複使用的可組合元件,這個問題就不那麼嚴重了。父項可以輕鬆指定狀態和回呼,因此您可以編寫可重複使用的可組合項,不必瞭解這些可組合項的確切使用位置。

var isEnabled by rememberSaveable { mutableStateOf(false) }

Column {
    ImageWithEnabledOverlay(isEnabled)
    ControlPanelWithToggle(
        isEnabled = isEnabled,
        onEnabledChanged = { isEnabled = it }
    )
}

在上例中,三個部分的狀態都是封裝更嚴密,耦合性更少:

  • ImageWithEnabledOverlay 只需要知道 isEnabled 的目前狀態,不需要知道 ControlPanelWithToggle 的存在或控制方式。

  • ControlPanelWithToggle 不知道 ImageWithEnabledOverlay 的存在。可能無法顯示 isEnabled,也可能有一個或多個方法將其顯示出來,而 ControlPanelWithToggle 則無需變更。

  • 對父項而言,巢狀 ImageWithEnabledOverlayControlPanelWithToggle 的深度並不重要。這些子項可以是動畫改變、調換內容,或是將內容傳遞給其他子項。

此模式被稱為 反向控制,詳情請參閱 CompositionLocal 說明文件

處理螢幕大小變更

建立回應式 View 版面配置的主要方式之一就是為不同大小的視窗提供不同的資源。雖然符合條件的資源仍然適用於確定螢幕級別的版面配置,但使用 Compose 時,在程式碼中使用一般條件邏輯即可更加輕鬆地完全變更版面配置。詳情請參閱支援不同的螢幕大小

此外,請參閱建構自動調整式版面配置,瞭解 Compose 提供的建構自動調整式 UI 的相關技巧。

View 巢狀結構捲動

如果想進一步瞭解如何啟用可在捲動式檢視畫面與捲動式可組合函式之間同時使用的巢狀捲動互通性,讓兩個方向皆使用巢狀結構,請閱讀「巢狀捲動互通性」一節。

RecyclerView 中的 Compose

RecyclerView 中的可組合項自從 RecyclerView 1.3.0-alpha02 以來皆可保持高效能。請務必使用 RecyclerView 1.3.0-alpha02 以上版本,以便享有這些好處。