Jetpack Compose前瞻
-
就在不久前官方推出的JetpackCompose1.0.0-beta01版本,让广大Android开发都加入了学习的路途,并且Google开办的Android开发挑战赛更是涌入了大量学习JetPack Compose 的开发者大佬们,一度成为了当下火热的UI工具包。
-
Jetpack Compose是作为构建本机AndroidUI的现代工具包,使用更少的代码,强大的工具和直观的KotlinAPI简化并加速了Android上的UI开发。
欢迎各位同学fork以下GitHub仓库,同时也是挑战赛官方提供的模板project
本文demo代码运行环境
- Android studio版本为:Android Studio Arctic Fox >>> 2020.3.1 Canary 9
- JetpackCompose版本为: 1.0.0-beta01
- JDK版本为11.0.9 ps:
- 想要正常运行模板项目,jdk大于11,且Android studio版本在4.3以上,也就是Canary 9 Arctic Fox
- 另外想问下有大佬在解决在现在Android studio beta版本中成功运行模板project的么?
学习指南
以下我大概从Foundation(基础),Layout(布局),Material(材料),Animation(动画)
Foundation
- Image
- 图像用于显示图像。它类似于经典Android View系统中的ImageView,可以使用painterResource加载资源文件
@Composable
fun ImageResource() {
val image: Painter = painterResource(R.drawable.lagotto_romagnolo)
Image(painter = image, contentDescription = null)
}
2.实际效果如下
- Text
- 使用Text可以显示文本。可以使用style参数定义诸如textdecoration或fontfamily之类的东西。
示例代码
@Composable
@Preview
fun TextExample(){
Column {
Text("Jacky Tallow")
Text("Text with cursive font", style = TextStyle(fontFamily = FontFamily.Cursive))
Text(
text = "Text with LineThrough",
style = TextStyle(textDecoration = TextDecoration.LineThrough)
)
Text(
text = "Text with underline",
style = TextStyle(textDecoration = TextDecoration.Underline)
)
Text(
text = "Text with underline, linethrough and bold",
style = TextStyle(
textDecoration = TextDecoration.combine(
listOf(
TextDecoration.Underline,
TextDecoration.LineThrough
)
), fontWeight = FontWeight.Bold
)
)
}
}
- 普通文字
Text("Jacky Tallow")
- 草书文字
Text("Text with cursive font", style = TextStyle(fontFamily = FontFamily.Cursive))
- 带有LineThrough的文字
Text(
text = "Text with LineThrough",
style = TextStyle(textDecoration = TextDecoration.LineThrough)
)
- 带有下划线的文字
Text(
text = "Text with underline",
style = TextStyle(textDecoration = TextDecoration.Underline)
)
- 带有下划线,粗体和直行的文字
Text(
text = "Text with underline, linethrough and bold",
style = TextStyle(
textDecoration = TextDecoration.combine(
listOf(
TextDecoration.Underline,
TextDecoration.LineThrough
)
), fontWeight = FontWeight.Bold
)
)
- BaseTextFiled BaseTextField可用于插入文本。
@Composable
fun BaseTextFieldDemo() {
var textState by remember { mutableStateOf(TextFieldValue("h")) }
Column {
TextField(value = textState, onValueChange = {
textState = it
})
Text("The textfield has this text: " + textState.text)
}
}
- Canvas 不用多说,作为画布可以进行绘制各种各样的图形去美化界面,下面是该代码实例
@Composable
@Preview(showBackground = true)
fun CanvasDemo() {
Canvas(modifier = Modifier.fillMaxSize()) {
//绘制矩形
drawRect(Color.Blue, topLeft = Offset(0f, 0f), size = Size(this.size.width, 55f))
//绘制圆形
drawCircle(Color.Red, center = Offset(50f, 200f), radius = 40f)
//绘制直线
drawLine(
Color.Green, Offset(20f, 0f),
Offset(200f, 200f), strokeWidth = 5f
)
//绘制饼状
drawArc(
Color.Black,
0f,
60f,
useCenter = true,
size = Size(300f, 300f),
topLeft = Offset(60f, 60f)
)
}
}
-
分别可以使用drawRect绘制矩形,drawCircle绘制圆形,drawLine绘制直线,drawArc绘制饼状
-
实际效果如下
- Shape
Shape故名思义,可以用来绘制特定形状的Composable
- 下面使用了个例子绘制了圆形形状,圆角形状,矩形形状,带切角的圆形形状
@Composable
@Preview
fun PreviewShapeDemo() {
Column(modifier = Modifier
.fillMaxWidth()
.wrapContentSize(Alignment.Center)) {
//矩形形状
ExampleBox(shape = RectangleShape)
//圆形形状
ExampleBox(shape = CircleShape)
//带圆角的矩形形状
ExampleBox(shape = RoundedCornerShape(8.dp))
//带有切角的矩形形状
ExampleBox(shape = CutCornerShape(10.dp))
}
}
//形状
@Composable
fun ExampleBox(shape: Shape) {
Box(modifier = Modifier
.size(100.dp)
.clip(shape)
.background(Color.Red))
}
- 那么如何绘制自定义形状呢? 一般情况下, 有通用形状和扩展形状界面
- 通用形状 这里用GenericShape。让我们看看三角形是如何绘制的。
private val TriangleShape = GenericShape { size ->
// 1)
moveTo(size.width / 2f, 0f)
// 2)
lineTo(size.width, size.height)
// 3)
lineTo(0f, size.height)
}
- 在GenericShape内部,您可以绘制自定义形状。您可以访问size object。这是应用形状的Composable的大小。你可以用高度size.height与宽度size.width
- 最初,画家将从父对象composable(0x,0y)的左上方开始。使用moveTo()可以设置绘画者的坐标。在这里,坐标将设置为父布局的一半宽度,并且坐标为0y。
- 这将从在1)中设置的画家坐标绘制一条线到父布局的右下角。然后将画家坐标自动设置到此角。
- 这将在左下角画一条线。GenericShape将隐式执行close() -函数。close()将从最后一个绘制器坐标到第一个定义的坐标绘制一条线。
- 扩展形状界面 看一看一个例子,有这个example来解释一下
/**
* Defines a generic shape.
*/
interface Shape {
/**
/**
* Creates [Outline] of this shape for the given [size].
*
* @param size the size of the shape boundary.
* @param density the current density of the screen.
*
* @return [Outline] of this shape for the given [size].
*/
fun createOutline(size: Size, density: Density): Outline
}
您可以扩展Shape接口以创建自己的Shape实现。在createOutline内部,您可以获取可组合对象的大小(将形状应用到该对象)以及屏幕的密度。您必须返回Outline的实例。Outline是具有以下子类的密封类:
- 矩形(val rect:Rect)
- 四舍五入(val rrect:RRect)
- 通用(val path:Path)
class CustomShape : Shape {
override fun createOutline(size: Size, density: Density): Outline {
val path = Path().apply {
moveTo(size.width / 2f, 0f)
lineTo(size.width, size.height)
lineTo(0f, size.height)
close()
}
return Outline.Generic(path)
}
}
- LazyColumn LazyColumn是作为垂直滚动列表,只有组成,并勾画出当前可见的项目,它类似于经典Android View系统中的Recyclerview
- 下面是例子,简单使用了LazyColumn
@Composable
fun LazyColumnDemo() {
val list = listOf(
"A", "B", "C", "D"
) + ((0..100).map { it.toString() })
LazyColumn(modifier = Modifier.fillMaxHeight()) {
items(items = list, itemContent = { item ->
Log.d("COMPOSE", "This get rendered $item")
when (item) {
"A" -> {
Text(text = item, style = TextStyle(fontSize = 80.sp))
}
"B" -> {
Button(onClick = {}) {
Text(text = item, style = TextStyle(fontSize = 80.sp))
}
}
"C" -> {
//Do Nothing
}
"D" -> {
Text(text = item)
}
else -> {
Text(text = item, style = TextStyle(fontSize = 80.sp))
}
}
})
}
}
- LazyRow 一个LazyRow是一个水平滚动列表,只组成,并勾画出当前可见的项目。它类似于经典Android View系统中的卧式Recyclerview。
- 下面是一个例子,简单使用了LazyRow
@Composable
fun LazyRowDemo() {
val list = listOf(
"A", "B", "C", "D"
) + ((0..100).map { it.toString() })
LazyRow(modifier = Modifier.fillMaxHeight()) {
items(items = list, itemContent = { item ->
Log.d("COMPOSE", "This get rendered $item")
when (item) {
"A" -> {
Text(text = item, style = TextStyle(fontSize = 80.sp))
}
"B" -> {
Button(onClick = {}) {
Text(text = item, style = TextStyle(fontSize = 80.sp))
}
}
"C" -> {
//Do Nothing
}
"D" -> {
Text(text = item)
}
else -> {
Text(text = item, style = TextStyle(fontSize = 80.sp))
}
}
})
}
}
Layout
- Box Box作为盒子布局,将相互堆叠,可以使用align修饰符指定应在何处进行绘制可组合的对象
- 可以看看下面的例子
fun BoxExample() {
Box(Modifier.fillMaxSize()) {
Text("This text is drawn first", modifier = Modifier.align(Alignment.TopCenter))
Box(
Modifier.align(Alignment.TopCenter).fillMaxHeight().width(
50.dp
).background( Color.Blue)
)
Text("This text is drawn last", modifier = Modifier.align(Alignment.Center))
FloatingActionButton(
modifier = Modifier.align(Alignment.BottomEnd).padding(12.dp),
onClick = {}
) {
Text("+")
}
}
}
- Column Column将显示每个孩子在先前的孩子之下,它类似于具有垂直方向的LinearLayout。
- 看看下面的例子
fun ColumnDemo() {
Column {
Text(text = "Hello World")
Text(text = "Hello World2")
}
}
- Row Row将显示每个孩子在前一个孩子旁边,它类似于具有水平方向的LinearLayout。
- 看看下面的例子
@Composable
@Preview
fun RowDemo() {
Row {
Text(text = "Hello World")
Text(text = "Hello World2")
}
}
- ConstraintLayout Compose中的ConstraintLayout与经典的Android View System中的ConstraintLayout相似
- 看看下面的例子
@Composable
fun ConstraintLayoutDemo() {
ConstraintSet {
val text1 = createRefFor("text1")
val text2 = createRefFor("text2")
val text3 = createRefFor("text3")
constrain(text1) {
start.linkTo(text2.end)
}
constrain(text2) {
top.linkTo(text1.bottom)
}
constrain(text3) {
start.linkTo(text2.end)
top.linkTo(text2.bottom)
}
}
ConstraintLayout {
Text("Text1", Modifier.layoutId("text1"))
Text("Text2", Modifier.layoutId("text2"))
Text("This is a very long text", Modifier.layoutId("text3"))
}
}
Meterial
- TextFiled TextField可用于插入文本。这等效于Android View系统中的EditText。
- 简单看看下面的例子
//文本域
@Composable
@Preview
fun TextFieldDemo() {
Column(Modifier.padding(16.dp)) {
val textState = remember { mutableStateOf(TextFieldValue()) }
TextField(
value = textState.value,
onValueChange = { textState.value = it }
)
Text("The textfield has this text: " + textState.value.text)
}
}
- Button 一个按钮有一个的onClick用功能,可以添加Text-Composable或任何其他composables作为Button的子元素。
- 简单看看下面的例子,简单加入了onclick,colors参数
@Composable
fun ButtonExample() {
Button(
onClick = { /*TODO*/ },
colors = ButtonDefaults.textButtonColors(backgroundColor = Color.Red)
) {
Text("Button")
}
}
- Switch 用作compose中的switch开关,下面是简单的一个例子
@Composable
@Preview
fun SwitchDemo() {
//持久化状态
val checkedState = remember { mutableStateOf(true) }
Switch(checked = checkedState.value, onCheckedChange = { checkedState.value = it })
}
- Card Card与Compose中的CardView等效,用作卡片布局
- 简单使用下Card
@Composable
@Preview
fun CardDemo() {
Card(
Modifier
.fillMaxSize()
.padding(8.dp),elevation = 8.dp) {
Text(text = "This is a Card")
}
}
- AlertDialog 实现Compose自带的对话框,和Android View中的AlertDialog警告对话框基本样式一致
- 简单通过例子学习下,设置一个点击按钮,onClick事件为true的时候展示对话框
fun AlertDialogSample() {
MaterialTheme {
Column {
val openDialog = remember { mutableStateOf(false) }
Button(onClick = {
openDialog.value = true
}) {
Text("Click me")
}
if (openDialog.value) {
AlertDialog(
onDismissRequest = {
openDialog.value = false
},
title = {
Text(text = "Dialog Title")
},
text = {
Text("Here is a text ")
},
confirmButton = {
Button(
onClick = {
openDialog.value = false
}) {
Text("This is the Confirm Button")
}
},
dismissButton = {
Button(
onClick = {
openDialog.value = false
}) {
Text("This is the dismiss Button")
}
}
)
}
}
}
}
- CircularProgressIndicator 顾名思义,CircularProgressIndicator可以用于显示圆形进度,大致可以分为以下两种
- 不设置参数,它会一直进行下去
CircularProgressIndicator()
- 设置参数后,当您为progress参数设置一个值时,指示符将与该进度一起显示。例如,进度为0.5f会将其填满一半。
CircularProgressIndicator(progress = 0.5f)
- 下面来看看一个例子来学习
@Composable
@Preview
fun CircularProgressIndicatorSample() {
var progress by remember { mutableStateOf(0.1f) }
val animatedProgress = animateFloatAsState(
targetValue = progress,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
).value
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Spacer(Modifier.height(30.dp))
Text("CircularProgressIndicator with undefined progress")
//无参
CircularProgressIndicator()
Spacer(Modifier.height(30.dp))
Text("CircularProgressIndicator with progress set by buttons")
//带参数
CircularProgressIndicator(progress = animatedProgress)
Spacer(Modifier.height(30.dp))
OutlinedButton(
onClick = {
if (progress < 1f) progress += 0.1f
}
) {
Text("Increase")
}
OutlinedButton(
onClick = {
if (progress > 0f) progress -= 0.1f
}
) {
Text("Decrease")
}
}
}
- LinearProgressIndicator
LinearProgressIndicator可以用于显示直线上的进度,也称为进度条。可以分为两种:
- 不定,使用不带progress参数的LinearProgressIndicator时,它将永远运行。
LinearProgressIndicator()
- 确定,当您为progress参数设置一个值时,指示符将与该进度一起显示。例如,进度为0.5f会将其填满一半。
LinearProgressIndicator(progress = 0.5f)
- 下面可以看看例子来结合学习
@Composable
fun LinearProgressIndicatorSample() {
var progress by remember { mutableStateOf(0.1f) }
val animatedProgress = animateFloatAsState(
targetValue = progress,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
).value
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Spacer(Modifier.height(30.dp))
Text("LinearProgressIndicator with undefined progress")
LinearProgressIndicator()
Spacer(Modifier.height(30.dp))
Text("LinearProgressIndicator with progress set by buttons")
LinearProgressIndicator(progress = animatedProgress)
Spacer(Modifier.height(30.dp))
OutlinedButton(
onClick = {
if (progress < 1f) progress += 0.1f
}
) {
Text("Increase")
}
OutlinedButton(
onClick = {
if (progress > 0f) progress -= 0.1f
}
) {
Text("Decrease")
}
}
}
- DropdownMenu DropdownMenu Composable可用于创建DropdownMenu,也就是下拉菜单
fun DropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
offset: DpOffset = DpOffset(0.dp, 0.dp),
properties: PopupProperties = PopupProperties(focusable = true),
content: @Composable ColumnScope.() -> Unit
)
- 当前如果为true的话,与dropdownContent的弹出菜单将显示
- CheckBox 作为选择框,可以进行是否选择选项操作
- 简单看看下面的例子,一样传入当前状态判断是否选中
@Composable
fun CheckBoxDemo() {
val checkedState = remember { mutableStateOf(true) }
Checkbox(
checked = checkedState.value,
onCheckedChange = { checkedState.value = it }
)
}
- FloatingActionButton 悬浮按钮,和Android View中一样分为FloatActionButton和ExtendedFloatingActionButton(可扩展的)
- FloatActionButton
fun FloatingActionButtonDemo() {
FloatingActionButton(onClick = { /*do something*/}) {
Text("FloatingActionButton")
}
}
- ExtendedFloatingActionButton
fun FloatingActionButtonDemo() {
FloatingActionButton(onClick = { /*do something*/}) {
Text("FloatingActionButton")
}
}
- ModalDrawerLayout 使用ModalDrawerLayout可以创建抽屉
- 看看下面的例子,点击按钮展开抽屉
@Composable
@Preview
fun ModalDrawerSample() {
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
ModalDrawer(
drawerState = drawerState,
drawerContent = {
Column {
Text("Text in Drawer")
Button(onClick = {
scope.launch {
drawerState.close()
}
}) {
Text("Close Drawer")
}
}
}, content = {
Column {
Text(text = "Text in BodyContext")
Button(onClick = {
scope.launch {
drawerState.open()
}
}) {
Text(text = "Click to open")
}
}
})
}
- RadioButton 简单来说,该api可以实现单选按钮进行单选操作
- 可以看看官方的例子
fun RadioButtonSample() {
val radioOptions = listOf("A", "B", "C")
val (selectedOption, onOptionSelected) = remember { mutableStateOf(radioOptions[1]) }
Column {
radioOptions.forEach { text ->
Row(
Modifier
.fillMaxWidth()
.selectable(
selected = (text == selectedOption),
onClick = {
onOptionSelected
(text)
}
)
.padding(horizontal = 16.dp)
) {
RadioButton(
selected = (text == selectedOption),
onClick = { onOptionSelected(text) }
)
Text(
text = text,
style = MaterialTheme.typography.body1.merge(),
modifier = Modifier.padding(start = 16.dp)
)
}
}
}
}
- Sacffold 脚手架是一种实现基本材料设计布局结构的布局。可以添加诸如TopBar,BottomBar,FAB或Drawer之类的东西。
- 可以看看下面的例子
@Composable
@Preview
fun ScaffoldDemo() {
val materialBlue700 = Color(0xFF1976D2)
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Open))
Scaffold(
scaffoldState = scaffoldState,
topBar = {
//topBar
TopAppBar(
title = { Text("TopAppBar") },
backgroundColor = materialBlue700
)
},
//悬浮按钮位置
floatingActionButtonPosition = FabPosition.End,
//悬浮按钮
floatingActionButton = {
FloatingActionButton(onClick = {}) {
Text("X")
}
},
//抽屉内容
drawerContent = { Text(text = "drawerContent") },
//内容
content = { Text("BodyContent") },
//bottomBar
bottomBar = { BottomAppBar(backgroundColor = materialBlue700) { Text("BottomAppBar") } }
)
}
- scaffoldState: 使用scaffoldState可以设置抽屉的打开状态(DrawerState.Opened或DrawerState.Closed)
- floatActionButton: 在这里可以添加FloatingActionButton,可以设置任何Composable,但是已经为该用例设置了FloatingActionButton
- floatActionButtonPosition : 添加FAB后,可以使用此指定它的位置。默认位置在布局的末尾。
- drawerContent: 可以在此处设置抽屉的内容。
- content: 这里是支架的主要内容
- bottombar:可以将布局的一部分设置在屏幕底部,可以设置任何Composable,但是已经为该用例创建了BottomAppBar。
Animation
- CrossFade 淡入淡出可用于使用淡入淡出动画在可组合对象之间切换。
- 下面通过例子来学习以下CrossFade吧,每次单击按钮时,屏幕内容将随着3秒的动画持续时间而变化。
@Composable
@Preview
fun CrossFadeDemo() {
var currentColor by remember { mutableStateOf(MyColor.Red) }
Column {
Row {
MyColor.values().forEach { myColors ->
Button(
onClick = { currentColor = myColors },
Modifier
.weight(1f, true)
.height(48.dp)
.background(myColors.color),
colors = ButtonDefaults.buttonColors(backgroundColor = myColors.color)
) {
Text(text = myColors.name)
}
}
}
Crossfade(targetState = currentColor) {
Crossfade(targetState = currentColor, animationSpec = tween(3000)) { selectedColor ->
Box(modifier = Modifier.fillMaxSize().background(selectedColor.color))
}
}
}
}
enum class MyColor(val color: Color) {
Red(Color.Red),
Green(Color.Green),
Blue(Color.Blue)
}
- 思考,这个淡入淡出是如何工作的呢?
- 该例子包含一个屏幕,该屏幕的顶部有3个按钮。单击按钮后,下面的屏幕将更改为所选颜色。
Crossfade(targetState = currentColor, animationSpec = tween(3000)) { selectedColor ->
Box(modifier = Modifier.fillMaxSize().background(selectedColor.color))
}
-
Crossfade希望某种状态可以检测何时重新组合。在这个例子中,这是currentColor,它是具有选定颜色枚举的状态。此状态将设置为当前参数。
-
使用animationSpec参数,可以设置应使用哪个动画在可组合对象之间进行切换。此处的默认参数是补间动画。您可以在实现AnimationSpec的任何类之间进行选择。此示例中的3000是补间动画将具有的持续时间。
-
最后一个参数是Crossfade Composable的主体。在这里,您必须创建要显示的UI。selectedColor是currentColor的当前值。在此示例中,我使用Box来显示颜色。每次单击三个按钮之一时,currentColor的值将更改,并且Crossfade将使用新旧UI之间的动画进行重构。
demo地址
- --->大家戳此处,可clone该文档项目demo
- 由于上述api较多就不贴图了,同学们可以自己试试相应的效果,可以使用Preview查看预览效果
总结
- 简单来说,Jetpack Compose是新的下一代UI工具包。它使用基于声明性组件的范例来轻松,快速地构建UI,它完全用Kotlin编写,并包含Kotlin语言的风格和人体工程学。
- 通过示例撰写 Jetpack Compose是一个新的声明性UI工具包,旨在满足创建现代用户界面的需求。通过检查我们使用它创建的具体UI,开始学习Compose,并了解构成该工具包的新API和Material组件。本文同学我通过主题,动画,布局等示例,演示如何自定义和组合组件以构建真实的UI,简化自己的开发经验并带来新的可能性。
- Jetpack Compose现在已经正式发布了beta版本,其中的api将不会做很大的改动,正是广大开发者学习交流的时刻,之后可能开始将其添加到现有实际应用程序中
- 这几天的学习Compose中,也让我思考与工具的紧密集成如何使开发经验变得更好,继续学习Compose以及它如何帮助自己构建更好的应用程序吧!