前言
風起云涌
最近移動端新聞很多啊,都有點看不過來了,Google 官方先是發布了 beta 版本的 Compose,緊接著又發布了 Flutter 2.0 版本,這是要搞事情啊!!!
Google 官方發布了 beta 版本的 Compose 之后舉辦了一個小比賽,分為四場,每周一場,現在為止已經過去了一場,沒有趕上第一場的不要擔心,還有剩下三場,
國內的安卓大佬們都開始摩拳擦掌,扔物線大佬先發了一篇公眾號,里面介紹了這個比賽的規則,又表明他已經參賽了,并靜等禮物的到來;扔物線發表公眾號的第二天,郭霖大佬也宣布參加了這個比賽,,,應該還有好多大佬參加了,但是我沒有進行關注,
參加比賽
這么多大佬參加, 還是 Google 官方舉辦的比賽,所以我也湊了湊熱鬧,雖然寫的也不好看,但也順手提交上去了,
如果好奇我寫的是什么樣子?那下面就給大家看看吧,看之前先給大家看看要求吧:

是不是很簡單,只需要兩個頁面,然后,,,,,

嗯,,,,差不多就是這樣我就直接提交了,不過得不得獎無所謂,開心就好,,,
Compose 和 Flutter
在寫完這個小 Demo 之后,有一個感覺,Compose 和 Flutter 他們兩個,,,,不能說有點相似,只能說完全一樣!
Compose 完全打破了我之前對安卓的看法,,和 Flutter 簡直是一個模子里刻出來的,連命名都有些相似,雖然 Google 官方說不會放棄 Java,但是看看協程和 Compose,真的想說一句:我信你個鬼,你個糟老頭子壞的很!
雖然二者很像,但是發力的方向是完全不同的:
Flutter 大家都知道,是個 跨平臺 的 UI 框架,注意,只是 UI 框架!各種復雜實作全都是安卓和蘋果原生寫的,可以一套代碼多處使用,特別是現在 Flutter 2.0 的發布,更是不得了,支持安卓、蘋果、windows、mac、網頁,不得不說實在是太強了,
那 Compose 為什么會出現?又或者說它有什么用呢?
Compose 為什么會出現
這一小節的內容很明確,寫 Compose 為什么會出現,這個問題其實寫安卓的都比較清楚,有的就算不清楚也可以猜出二三,
以前咱們撰寫安卓程式的時候頁面都寫在 res -> layout 檔案夾下,以 xml 的形式展現,這樣的好處顯而易見,將邏輯代碼和頁面徹底分開,確實分開了,但是使用的時候又需要去 findViewById,xml 布局加載的時候又需要耗費大量時間,加載完成之后還需要通過反射來獲取 View,又是一大耗時操作,,
記得之前有個大神寫過一篇文章,里面通過直接 new 布局和 xml 方式寫布局進行比較,速度甚至相差幾十倍,然后各種各樣的優化就出來了,好像有個團隊甚至自己撰寫了一整套布局,完全沒有使用 xml,也是神人了!
寫到這里應該都知道 Compose 為什么會出現了吧!
Compose 有啥?
Compose 還有一個重要的特點——宣告式,
宣告式?這是什么東西?怎么說呢,就是不以命令方式改變前端視圖的情況下呈現應用界面,從而使撰寫和維護應用界面變得更加容易,
不解釋還好,一解釋更加懵逼,,,簡單來說就是通過對資料的改變而改變布局,不用以前 findViewById 那樣遍歷樹,減少出錯的可能性,而且軟體維護復雜性會隨著需要更新的視圖數量而增長,不行不行,我自己都有點懵了,給大家舉個例子吧!
其實咱們所熟知的 MVVM 其實就是資料驅動布局改變的,為什么這樣說呢?你想一下,你的 ViewModel 中是不是通常會定義一個 LiveData,然后在你的 Activity 或者 Fragment 中進行 observe,然后將你的 UI 操作放到這里,當資料改變的時候相應地去修改你的 UI,這樣說的話是不是好理解一些呢?
正文
準備撰寫玩安卓
這個小標題命名地不太優雅,,,湊合看吧!
這是我寫的最長的一篇前言,而且好像還沒解釋得太明白,大家將就下吧,
其實這是我個人的一個小習慣,學習什么新東西的時候就會寫個 Demo,之前我寫過一個 MVVM 版的玩安卓,而且還為這個專案寫過一個系列的文章,感興趣的可以去我的文章串列看看,
這次寫完官方比賽的小 Demo 之后覺得 Compose 挺好玩,并且好多大佬都說 Compose 是未來的趨勢,于是就想著把那個 MVVM 版的玩安卓改用 Compose 實作一下試試,
好,說干就干!
先來看看成品吧:
![]() | ![]() |
|---|---|
![]() | ![]() |
![]() | ![]() |
看著是不是也還可以?那就開始著手撰寫吧!
準備作業
在撰寫之前還是放一下 Github 地址吧:
Github 地址:github.com/zhujiang521…
由于之前已經撰寫過 MVVM 版本的玩安卓了,所以說很多東西咱們就可以直接進行使用了,比如說一下圖片資源,又比如說資料、網路請求等等都是現成了,咱們要做的只是將以前的 xml 布局改成 Compose 即可,
聽著是不是很簡單?但是寫的時候有點懵,這還是我之前寫過 Flutter 的情況下,如果大家沒有寫過 Flutter 或者 SwiftUI 的話看起來可能會更懵,因為里面好多東西都顛覆了我對安卓的看法,,,
為了區分和之前 MVVM 版本的區別,我把這次的 Compose 的版本分支改為了 main 分支,大家下載代碼的時候切換下分支就可以了,或者直接下載 main 分支的代碼也可以,
引入依賴
一般要使用一個新東西的第一步,來吧!
// `Compose`
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation "androidx.activity:activity-compose:1.3.0-alpha03"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha02"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$compose_version"
implementation "androidx.compose.foundation:foundation:$compose_version"
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
androidTestImplementation "androidx.compose.ui:ui-test:$compose_version"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
// navigation
implementation "androidx.navigation:navigation-compose:1.0.0-alpha08"
what?不是一個 Compose 庫嗎?干嘛引入這么多?我之前也是這么想的,但是在用的時候一個個又加進去的,,,如果不知道每一個包是什么意思的話可以去官方檔案中查看下,不過光看依賴名稱基本就知道是什么意思了,,,
如果你也想像我一樣在以前的專案中使用 Compose,那么下面的這一步千萬別忘了,我就是忘了添加下面這一步找了整整一天的錯,,,
android {
…………
buildFeatures {
`Compose` true
viewBinding true
}
`Compose`Options {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerVersion kotlin_version
}
}
就是上面的,一定別忘了進行配置,不然找錯誤能找死,,,給我提示的是 Kotlin 內部 JVM 錯誤,搞得我我都準備給 Kotlin 提 Bug 了,,,
開始撰寫
我的天,寫了兩千字,終于開始真正的正文了,,,我太難了,
大家可能看到上面的依賴中有 navigation,看名字就知道是專門為 Compose 寫的,這也是 Compose 跳轉的重要工具,也許有更好的,只是我沒有發現吧,,,
上面也不止一次提到 Compose 顛覆了我之前對安卓的看法,之前的我認為安卓就是一堆 Activity 加上 Fragment ,但是寫了 Compose 之后我發現并不是這樣的,好多官方的 Demo 只有一個 Activity,,
看得我有點懵,但是后來想了想就明白了,還是類似 Flutter ,在 Flutter 中不也是一個 Activity 嘛,每一個頁面也都是一個 Widget!跳轉也不是之前的 Intent ,而是路由,,現在的 Compose 也是一樣,只不過 Widget 改為了 Composable,路由改為了 navigation,
真的要開始寫了
好了好了,真的要開始寫了,,,
今天的標題既然是 初探,那么今天就寫一下首頁框架吧,就是一個底部導航欄加上四個頁面,實作點擊進行切換,
新建 Activity
先來新建一個 Activity 吧,咱們這個專案之后就用這一個 Activity!
先來看一下 AndroidManifest 吧:
<activity
android:name=".`Compose`.NewMainActivity"
android:theme="@style/AppTheme.NoActionBars">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
這沒什么說的,就是把之前的首頁改為了新的首頁,其它的 Activity 都已經用不到了,
class NewMainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PlayTheme {
Home()
}
}
}
}
這個 Activity 很簡單,大家發現了沒有,咱們熟悉的 setContentView 方法沒有呼叫,取而代之的是 setContent 方法,不過不重要,這是 Compose 的固定寫法而已,當然也可以將 Compose 寫到 xml 中然后通過 findViewById 來找到再進行操作,還可以直接 new 出一個 Compose 來進行操作,這里咱們就選擇 setContent 這種方法吧,
還是來看下 setContent 這個方法吧:
public fun ComponentActivity.setContent(
parent: CompositionContext? = null,
content: @Composable () -> Unit
) {
val existingComposeView = window.decorView
.findViewById<ViewGroup>(android.R.id.content)
.getChildAt(0) as? ComposeView
if (existingComposeView != null) with(existingComposeView) {
setParentCompositionContext(parent)
setContent(content)
} else ComposeView(this).apply {
// Set content and parent **before** setContentView
// to have ComposeView create the composition on attach
setParentCompositionContext(parent)
setContent(content)
setContentView(this, DefaultActivityContentLayoutParams)
}
}
明白了吧?這是 ComponentActivity 的一個擴展方法,里面其實還會執行 setContentView 方法的,并沒有什么神器的魔法,,
創建 Compose
雖然這里選擇了 setContent 這種方法,但是還是說下別的情況下怎么使用吧:
可以將 ComposeView 放在 XML 布局中,就像放置其他任何 View 一樣:
<?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>
上面的布局很簡單,大家注意看,咱們把 Compose 直接寫在了 xml 中,這樣也是可以進行使用的,怎么使用呢?
class ExampleFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
return inflater.inflate(
R.layout.fragment_example, container, false
).apply {
findViewById<ComposeView>(R.id.compose_view).setContent {
// In Compose world
MaterialTheme {
Text("Hello Compose!")
}
}
}
}
}
是不是很簡單,當然也可以直接 new 出一個 Compose 來進行操作:
class ExampleFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
MaterialTheme {
// In Compose world
Text("Hello Compose!")
}
}
}
}
}
好的,這樣其實已經滿足大部分的需求了,不過還有一種情況:如果同一布局中存在多個 ComposeView 元素,每個元素必須具有唯一的 ID 才能使 savedInstanceState 發揮作用:
class ExampleFragment : Fragment() {
override fun onCreateView(...): View = LinearLayout(...).apply {
addView(ComposeView(...).apply {
id = R.id.compose_view_x
...
})
addView(TextView(...))
addView(ComposeView(...).apply {
id = R.id.compose_view_y
...
})
}
}
}
上面的代碼也不難,里面需要注意一點,ComposeView ID 需要在 res/values/ids.xml 檔案中進行定義:
<resources>
<item name="compose_view_x" type="id" />
<item name="compose_view_y" type="id" />
</resources>
PlayTheme
上面本來想直接寫主題來著,但是想了想還是說清楚一點吧,要不使用不同場景的就不知道該如何使用了,咱們來接著看剛才定義的 Activity,
setContent 中包裹了一層 PlayTheme,顧名思義,這是一個自定義的主題,這塊也是顛覆我的一個地方,在我之前對安卓的認知中,主題一般存放在 values 中的 styles 檔案中,現在 Compose 中已經不再使用 xml 中的主題了,取而代之的是 Compose 自己的一套主題系統,在這里我也吃過一次虧:死活修改不了顏色,,,
看來 Compose 對 xml 是深惡痛絕啊,一個 xml 都不想使用,包括 color,,,,來看下 PlayTheme 的定義吧:
@Composable
fun PlayTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) {
PlayThemeDark
} else {
PlayThemeLight
}
MaterialTheme(
colors = colors,
typography = typography,
content = content
)
}
private val PlayThemeLight = lightColors(
primary = blue,
onPrimary = Color.White,
primaryVariant = blue,
secondary = blue
)
private val PlayThemeDark = darkColors(
primary = blueDark,
onPrimary = Color.White,
secondary = blueDark,
surface = blueDark
)
定義很簡單,但是根據上面描述的內容發現了一些什么沒有,連主題都是 Composable ,真的像 Flutter 中的 Widget ,再來看下里面的顏色值:
val blue = Color(0xFF2772F3)
val blueDark = Color(0xFF0B182E)
val Purple300 = Color(0xFFCD52FC)
val Purple700 = Color(0xFF8100EF)
畫頁面
準備作業做得差不多了,來開始畫頁面吧!
先想想咱們最終需要做的樣子,忘記的可以滑到上面再看看,
其實很簡單,今天咱們只是初探嘛!只需要畫出下面的底部導航欄和上面幾個空頁面就行了!
說干就干!先來創建一個新的 Composable:
@Composable
fun Home() {
}
很簡單,一個方法加上 @Composable 的注解就是一個新的 Composable 了,咱們需要在這里畫咱們的首頁了,
底部導航欄
查了一下官方檔案, Compose 中和 Flutter 一樣有現成底部導航欄,完全夠咱們使用了:
@Composable
fun Home() {
ComposeDemoTheme {
val (selectedTab, setSelectedTab) = remember { mutableStateOf(CourseTabs.HOME_PAGE) }
val tabs = CourseTabs.values()
Scaffold(
backgroundColor = MaterialTheme.colors.primarySurface,
bottomBar = {
BottomNavigation(
Modifier.navigationBarsHeight(additional = 56.dp)
) {
tabs.forEach { tab ->
BottomNavigationItem(
icon = { Icon(painterResource(tab.icon), contentDescription = null) },
label = { Text(stringResource(tab.title).toUpperCase()) },
selected = tab == selectedTab,
onClick = { setSelectedTab(tab) },
alwaysShowLabel = false,
selectedContentColor = MaterialTheme.colors.secondary,
unselectedContentColor = LocalContentColor.current,
modifier = Modifier.navigationBarsPadding()
)
}
}
}
) { innerPadding ->
val modifier = Modifier.padding(innerPadding)
when (selectedTab) {
CourseTabs.HOME_PAGE -> One(modifier)
CourseTabs.PROJECT -> Two(modifier)
CourseTabs.OFFICIAL_ACCOUNT -> Three(modifier)
CourseTabs.MINE -> Four(modifier)
}
}
}
}
上面代碼有點長,但是意思很簡單,稍微給大家說一下吧,Scaffold 在 Flutter 中也有,意思也是差不多的,來看一下 Scaffold 的原始碼吧:
@Composable
fun Scaffold(
modifier: Modifier = Modifier,
scaffoldState: ScaffoldState = rememberScaffoldState(),
topBar: @Composable () -> Unit = {},
bottomBar: @Composable () -> Unit = {},
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End,
isFloatingActionButtonDocked: Boolean = false,
drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
drawerGesturesEnabled: Boolean = true,
drawerShape: Shape = MaterialTheme.shapes.large,
drawerElevation: Dp = DrawerDefaults.Elevation,
drawerBackgroundColor: Color = MaterialTheme.colors.surface,
drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
drawerScrimColor: Color = DrawerDefaults.scrimColor,
backgroundColor: Color = MaterialTheme.colors.background,
contentColor: Color = contentColorFor(backgroundColor),
content: @Composable (PaddingValues) -> Unit
)
它就是一個腳手架,官方是這樣進行描述的:
Material 支持的最高級別的可組合項是
Scaffold,Scaffold可讓您實作具有基本 Material Design 布局結構的界面,Scaffold可以為最常見的頂級 Material 組件(如TopAppBar、BottomAppBar、FloatingActionButton和Drawer)提供插槽,通過使用Scaffold,很容易確保這些組件得到適當放置且正確地協同作業,
意思很明確,如果不是要求自定義度特別高的頁面,使用 Scaffold 就完全能滿足需求了,這里咱們使用的就是,
上面代碼中的 CourseTabs 還沒有寫,它是一個列舉類,用來表示首頁的幾個頁面的:
enum class CourseTabs(
@StringRes val title: Int,
@DrawableRes val icon: Int
) {
HOME_PAGE(R.string.home_page, R.drawable.ic_nav_news_normal),
PROJECT(R.string.project, R.drawable.ic_nav_tweet_normal),
OFFICIAL_ACCOUNT(R.string.official_account, R.drawable.ic_nav_discover_normal),
MINE(R.string.mine, R.drawable.ic_nav_my_normal)
}
這里其實有的地方大家還是看不太懂的,比如上面的 remember 是個什么東西?
管理狀態
上面所說的的 remember 其實是用來管理 Compose 的狀態的,大家就先記著 remember 可以記錄咱們點擊的按鈕資料,從而驅使頁面發生改變吧,
mutableStateOf(CourseTabs.HOME_PAGE) 其實是 MutableState<CourseTabs> ,[mutableStateOf](https://developer.android.google.cn/reference/kotlin/androidx/compose/runtime/package-summary#mutableStateOf(androidx.compose.runtime.mutableStateOf.T, androidx.compose.runtime.SnapshotMutationPolicy)) 會創建可觀察的 MutableState, MutableState是與 Compose 運行時集成的可觀察型別,
這塊一時半會說不清,需要后面的文章慢慢來和大家講,咱們來日方長!
添加頁面
上面一共創建了四個字頁面:one、two、three、four,四個頁面非常簡單:
@Composable
fun One(modifier: Modifier) {
Text(modifier = modifier
.fillMaxSize()
.padding(top = 100.dp), text = "One", color = Teal200)
}
@Composable
fun Two(modifier: Modifier) {
Text(modifier = modifier
.fillMaxSize()
.padding(top = 100.dp), text = "Two", color = Teal200)
}
@Composable
fun Three(modifier: Modifier) {
Text(modifier = modifier
.fillMaxSize()
.padding(top = 100.dp), text = "Three", color = Teal200)
}
@Composable
fun Four(modifier: Modifier) {
Text(modifier = modifier
.fillMaxSize()
.padding(top = 100.dp), text = "Four", color = Teal200)
}
ok,這就差不多了,
首次運行
代碼寫的差不多了,咱們該運行下看下效果了:

效果是不是還不錯,哈哈哈,代碼沒有幾行,但是影片還是很漂亮的,這都歸功于 Compose 為咱們封裝好的控制元件,
精致的結尾
本篇文章就先寫到這里吧,本篇簡單介紹了下 Compose,并撰寫了一個簡單的頁面,之后會將整個應用全部使用 Compose 撰寫完成的,大家別忘了點贊和關注啊,感激不盡!!!
最后再放一下 Github 地址吧,別忘了切換 main 分支:
Github 地址:github.com/zhujiang521…
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/267155.html
標籤:其他
上一篇:Android studio模擬器啟動失敗 The emulator process for AVD Pixel_2_API_29 was killed 之 配置環境變數






