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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容