Android Compose 组件学习(一)

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.找其他开发者的文档,更详细更效率的学习

  • https://jetpackcompose.cn/docs文档和开源项目
  • https://github.com/Gurupreet/ComposeCookBook开源项目

下载工具

使用 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 = 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

这篇博客只是只是展示了控件的简单使用,快速的了解控件的创建及展示效果。
下一篇我们介绍如何跳转页面。

你可能感兴趣的:(Android Compose 组件学习(一))