Android Compose 組件學習(一)

Android Compose自推出正式版本后,google 就一直推薦使用Compose來開發。正好疫情期間,作為一個 Android 摸魚達人,就來摸索一下Compose的開發。說實話開發了2天感覺對Android 開發人員來說變化是巨大的,但是作為從業者我們還必須學習和學會,才能不被甩開。
學習使用 Compose 我們需要坐什么呢?
1.使用 Kotlin
2.使用Android Studio 最新版本開發工具
3.去官網查看教程,跟著google 一步一個腳印的學習->官方文檔速轉通道

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 in gradle.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查看。

image.png

初識組件

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 可組合項。


image.png
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)
            ) {

            }
        }
image.png

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
            )    
        }
image.png

Button 類

Compose Button多種多樣,具體的大家可以先看 UI 展示,就能明白在具體場景下使用什么 button。估計是參考的H5前端的按鈕樣式
Button 就是帶背景色的按鈕,OutlinedButton是帶邊框繪制的,TextButton是無背景色的按鈕。其他都可以參考原生的樣式


image.png
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 的行數限制。具體效果如下


image.png

7

這篇博客只是只是展示了控件的簡單使用,快速的了解控件的創建及展示效果。
下一篇我們介紹如何跳轉頁面。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容