Android Compose自推出正式版本后,google 就一直推薦使用Compose來開發。正好疫情期間,作為一個 Android 摸魚達人,就來摸索一下Compose的開發。說實話開發了2天感覺對Android 開發人員來說變化是巨大的,但是作為從業者我們還必須學習和學會,才能不被甩開。
學習使用 Compose 我們需要坐什么呢?
1.使用 Kotlin
2.使用Android Studio 最新版本開發工具
3.去官網查看教程,跟著google 一步一個腳印的學習->官方文檔速轉通道
- Jetpack Compose 使用入門
https://developer.android.google.cn/jetpack/compose/documentation - Compose 教程
https://developer.android.google.cn/courses/pathways/compose - Compose 示例
https://github.com/android/compose-samples - Codelab: 在 Jetpack Compose 中使用狀態
https://developer.android.google.cn/codelabs/jetpack-compose-state
4.找其他開發者的文檔,更詳細更效率的學習
下載工具
使用 Android Studio Bumblebee 大黃蜂??版本,如果不想影響老版本 Android Studio 的運行項目,可以下載 zip 的版本。AS盡可能升級到最新版,最少也要Arctic Fox,老版本無法實現 Compose 的預覽和開發
官方下載地址:https://developer.android.google.cn/studio
快速下載地址:https://www.androiddevtools.cn/
下載后創建 Compose 項目,如果你電腦配置的 java為11,那么就沒問題。如果出現問題如下:
Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8.
You can try some of the following options:
- changing the IDE settings.
- changing the JAVA_HOME environment variable.
- changing
org.gradle.java.home
ingradle.properties
.
Gradle settings
解決方案為:
1.更新 java 到11,完美。雖然你根本用不到11
2.到系統設置中Preferences -> Build Tools ->Gradle , 選擇 Gradle JDK 為11
image.png
Compose項目構成
在項目可以正式運行后,我們可以先打開MainActivity,繼承ComponentActivity()包含了setContent()方法,如果去除setContent內的代碼運行可以看到一個空頁面。
然后我們添加一個文本控件Text,對應原生的TextView。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text("Hello world!")
}
}
}
項目運行起來后我們看下整個項目的代碼是怎么樣的。
Compose 的引入
buildscript {
ext {
compose_version = '1.0.1'
}
}
//compose 核心
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
另外 google 提供了非常多的compose 庫,具體查看可以到https://developer.android.google.cn/jetpack/androidx/explorer查看。
初識組件
Compose 內部的組件非常少,但是合理的搭配可以做到比安卓原生更好的開發體驗。大體如下:
分類 | Compose | 原生 |
---|---|---|
文本 | Text | TextView |
文本 | TextField | Edittext |
圖片 | Icon | ImageView |
圖片 | Image | ImageView |
Button | Button | Button |
Button | IconButton | Button |
Button | IconToggleButton | CheckBox,Switch |
Button | Switch | Switch |
Button | RadioButton | RadioButton |
Button | Checkbox | CheckBox |
布局 | Box | FrameLayout |
布局 | Column | LinearLayout |
布局 | Row | LinearLayout |
布局 | Scaffold | 無 |
布局 | Constraintlayout | Constraintlayout |
列表 | LazyColumn | RecyclerView,ListView |
列表 | LazyRow | RecyclerView,ListView |
間距類 | Spacer | Space(估計很多人都沒用過) |
我們直接在setContent添加個 Text 和 Image 控件,下面代碼就是最簡單的寫法。
Text、Image應該是我們最常用的控件,特別是 Text 可實現的功能特別多,具體可以去看后續文章或者看下其他博客。
setContent {
Text("Hello world!")
Image(
painter = painterResource(R.drawable.share_calc),
contentDescription = null
)
}
上面的展示代碼會使內容重疊,需要使用 Row 或者 Column。
按照下面展示,我們添加了Column垂直布局,控件就往下繪制不重疊了。
Row 、Column、Box
Column 垂直布局 -- 對用 LinearLayout 的android:orientation="vertical";Row 水平布局 -- 對用 LinearLayout 的android:orientation="horizontal";Box接近FrameLayout,具體使用可以看下圖的例子.
Compose 中的三個基本標準布局元素是 Column、Row 和 Box 可組合項。
Column(){
Text("Hello world!")
Image(
painter = painterResource(R.drawable.share_calc),
contentDescription = null
)
}
Row(){
Text(" Row W")
Image(
painter = painterResource(R.drawable.share_calc),
contentDescription = null
)
}
Box(
modifier = Modifier
.background(Color.Cyan)
.size(180.dp)
) {
Box(
modifier = Modifier
.align(Alignment.TopCenter)
.size(60.dp, 60.dp)
.background(Color.Red)
) {
}
Box(
modifier = Modifier
.align(Alignment.BottomEnd)
.size(60.dp, 60.dp)
.background(Color.Blue)
) {
}
}
Scaffold腳手架和Constraintlayout布局
Scaffold腳手架我們先看下源碼,可以看到包含了topBar、bottomBar、floatingActionButton等控件的頁面。這是個固定布局,當我們的首頁符合該頁面固定樣式時,可以直接使用該布局。
Constraintlayout布局和Android 原生一樣
@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
)
TextField文本輸入控件
@Composable
fun InputText() {
val input = remember { mutableStateOf("") }
TextField(value = input.value, onValueChange = { input.value = it })
}
Icon 和 Image圖片類
Row {
Icon(painter = painterResource(R.drawable.database_set), contentDescription = null)
Spacer(modifier = Modifier.width(8.dp))
Image(
painter = painterResource(R.drawable.share_calc),
contentDescription = null
)
}
Button 類
Compose Button多種多樣,具體的大家可以先看 UI 展示,就能明白在具體場景下使用什么 button。估計是參考的H5前端的按鈕樣式
Button 就是帶背景色的按鈕,OutlinedButton是帶邊框繪制的,TextButton是無背景色的按鈕。其他都可以參考原生的樣式
Row() {
//普通按鈕
Button(onClick = { /*TODO*/ }) {
Text(text = "Button")
}
Spacer(modifier = Modifier.width(8.dp))
//帶邊框繪制的 button
OutlinedButton(onClick = { /*TODO*/ }) {
Text(text = "OutlinedButton")
}
Spacer(modifier = Modifier.width(8.dp))
//純文字按鈕
TextButton(onClick = {}) {
Text("TextButton")
}
}
Row() {
//帶Icon的按鈕
//圓形水波紋和 Checkbox,Switch 水波紋效果一致,
//android 可以看下設置android:background="?attr/selectableItemBackgroundBorderless"("?attr/actionBarItemBackground")的效果
IconButton(modifier = Modifier.width(120.dp), onClick = { /*TODO*/ }) {
Row() {
Icon(
painter = painterResource(id = R.drawable.database_set),
contentDescription = null
)
Spacer(modifier = Modifier.width(8.dp))
Text("IconButton")
}
}
//圖標切換 button,類似于switch,但是更接近于CheckBox
//圓形水波紋和Checkbox,Switch 水波紋效果一致,
//android 可以看下設置android:background="?attr/selectableItemBackgroundBorderless"("?attr/actionBarItemBackground")的效果
IconToggleButton(checked = false, onCheckedChange = {}) {
Text(text = "IconToggleButton")
}
//懸浮按鈕
ExtendedFloatingActionButton(text = { Text(text = "FAB") }, onClick = { })
}
RadioButton\Checkbox和原生對應,但是沒 text 屬性,如果需要顯示文本需要和 Text 控件組合使用
RadioDefault()
RadioText()
RadioButtonGroup()
Row() {
CheckboxDefault()
SwitchDefault()
}
@Composable
fun RadioDefault() {
val isSelected = remember { mutableStateOf(false) }
RadioButton(selected = isSelected.value, onClick = {
isSelected.value = !isSelected.value
Log.e(TAG, "默認Checkbox,同RadioButton不帶text文本控件")
})
}
@Composable
fun RadioText() {
val isSelected = remember { mutableStateOf(false) }
// 如果需要 text 需要和 Text 組合使用,click處理在row上
Row(modifier = Modifier.clickable {
isSelected.value = !isSelected.value
}) {
RadioButton(
selected = isSelected.value, onClick = null,
colors = RadioButtonDefaults.colors(
selectedColor = MaterialTheme.colors.primary,
unselectedColor = MaterialTheme.colors.error,
disabledColor = MaterialTheme.colors.secondary
)
)
Text("選項②")
}
}
@Composable
fun RadioButtonGroup() {
val options = listOf("選項③", "選項④", "選項⑤", "選項⑥")
val selectedButton = remember { mutableStateOf(options.first()) }
//RadioButton 不帶 text 文本控件,
Row() {
options.forEach {
val isSelected = it == selectedButton.value
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable {
selectedButton.value = it
}) {
RadioButton(
selected = isSelected, onClick = null,
)
Text(it, textAlign = TextAlign.Justify)
}
}
}
}
@Composable
fun CheckboxDefault() {
val isSelected = remember { mutableStateOf(false) }
Checkbox(checked = isSelected.value, onCheckedChange = {
isSelected.value = it
Log.e(TAG, "默認Checkbox,同RadioButton不帶text文本控件")
})
}
@Composable
fun SwitchDefault() {
val isSelected = remember { mutableStateOf(false) }
Switch(checked = isSelected.value, onCheckedChange = {
isSelected.value = it
Log.e(TAG, "默認Checkbox,同RadioButton不帶text文本控件")
})
}
定義一個帶 Text 的 RadioButton
@Composable
fun RadioText() {
val isSelected = remember { mutableStateOf(false) }
// 如果需要 text 需要和 Text 組合使用,click處理在row上
Row(modifier = Modifier.clickable {
isSelected.value = !isSelected.value
}) {
RadioButton(
selected = isSelected.value, onClick = null,
colors = RadioButtonDefaults.colors(
selectedColor = MaterialTheme.colors.primary,
unselectedColor = MaterialTheme.colors.error,
disabledColor = MaterialTheme.colors.secondary
)
)
Text("選項②")
}
}
Compose 無RadioGroup 組件,如果需要實現RadioGroup也需要組合使用
定義 RadioGroup 組件,在循環中改變狀態
@Composable
fun RadioButtonGroup() {
val options = listOf("選項③", "選項④", "選項⑤", "選項⑥")
val selectedButton = remember { mutableStateOf(options.first()) }
//RadioButton 不帶 text 文本控件,
Row() {
options.forEach {
val isSelected = it == selectedButton.value
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable {
selectedButton.value = it
}) {
RadioButton(
selected = isSelected, onClick = null,
)
Text(it, textAlign = TextAlign.Justify)
}
}
}
}
同理Checkbox和Switch控件就不細說了。對應的參數改成了checked和onCheckedChange,也是沒有 text 屬性,需要組合使用。
@Composable
fun CheckboxDefault() {
val isSelected = remember { mutableStateOf(false) }
Checkbox(checked = isSelected.value, onCheckedChange = {
isSelected.value = it
Log.e(TAG, "默認Checkbox,同RadioButton不帶text文本控件")
})
}
@Composable
fun SwitchDefault() {
val isSelected = remember { mutableStateOf(false) }
Switch(checked = isSelected.value, onCheckedChange = {
isSelected.value = it
Log.e(TAG, "默認Checkbox,同RadioButton不帶text文本控件")
})
}
LazyColumn、LazyRow
LazyColumn 和 LazyRow 相當于 Android View 中的 RecyclerView。它們只會渲染屏幕上可見的內容,從而在渲染大型列表時提升效率。
注意:LazyColumn 不會像 RecyclerView 一樣回收其子級。它會在您滾動它時發出新的可組合項,并保持高效運行,因為與實例化 Android Views 相比,發出可組合項的成本相對較低。
@Composable
fun RecyclerView(names: List<String> = List(1000) { "$it" }) {
LazyColumn() {
items(items = names) { item ->
MessageCard(item)
}
}
}
@Composable
fun MessageCard(msg: String) {
var isExpanded by remember { mutableStateOf(false) }
val surfaceColor: Color by animateColorAsState(
if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface,
)
Row(modifier = Modifier
.fillMaxWidth()
.clickable {
isExpanded = !isExpanded
}
.padding(8.dp)
) {
Image(
painter = painterResource(R.drawable.share_calc),
contentDescription = null,
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colors.secondaryVariant, CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
// We toggle the isExpanded variable when we click on this Column
Column() {
Text("當前索引:")
Spacer(modifier = Modifier.height(4.dp))
Surface(
shape = MaterialTheme.shapes.medium,
elevation = 1.dp,
// surfaceColor color will be changing gradually from primary to surface
color = surfaceColor,
// animateContentSize will change the Surface size gradually
modifier = Modifier
.animateContentSize()
.padding(1.dp)
) {
Text(
text = "索引為--------> $msg ,這是一個可展開和關閉的 Text 控件:" +
"微涼的晨露 沾濕黑禮服\n" +
"石板路有霧 父在低訴\n" +
"無奈的覺悟 只能更殘酷\n" +
"一切都為了通往圣堂的路",
modifier = Modifier.padding(all = 4.dp),
maxLines = if (isExpanded) Int.MAX_VALUE else 1,
style = MaterialTheme.typography.body2
)
}
}
}
}
這邊定義了一個內容為1000行的 List,使用LazyColumn包裹,同時在 MessageCard 中對點擊坐了處理,點擊后放開 Text 的行數限制。具體效果如下
這篇博客只是只是展示了控件的簡單使用,快速的了解控件的創建及展示效果。
下一篇我們介紹如何跳轉頁面。