Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发.
但是需要熟悉一下新的Compose的相关控件,虽然也是文本,图片,水平,垂直,但不是LinearLayout,ImageView这些了,不过也类似,记住就行了.
Text
:用于呈现文本的控件。
TextField
:可以让用户输入文本的控件。
Button
:用于执行操作的控件。
Image
:用于显示图像的控件。
Row
: 横排布局
Column
: 是竖排布局
Card
:用于呈现卡片式布局的控件。
Scaffold
:可以将应用程序的基本UI元素包装在一起的控件。
FloatingActionButton
: 实现浮动操作按钮
Slider
:实现滑动条
ProgressBar
:实现进度条
Tab
:用于在选项卡之间切换的控件。
BottomNavigation
:实现底部导航栏
Dialog
:用于显示对话框的控件。
Column
:用于将子控件垂直排列
Row
:用于将子控件水平排列
Box
:类似于 FrameLayout,可以用于布局或者装饰
Scaffold
:实现 Material Design 风格的屏幕布局
Surface
:实现 Material Design 风格的表面,具有形状和阴影
Divider
:用于绘制分割线,可以在Column和Row中使用。
TextButton
:实现文本按钮
OutlinedButton
:实现带边框的按钮
Checkbox
:实现多选框
RadioGroup
:实现单选框
TabRow
:实现选项卡,可用于导航
ModalBottomSheet
:实现底部弹出框
AlertDialog
:实现弹出框
BottomSheet
: 底部弹出式窗口。
Menu
: 弹出式菜单。
Tooltip
: 文本提示框。
RadioButton
: 单选框。
Switch
: 开关按钮。
LinearProgressIndicator
: 线性进度指示器
CircularProgressIndicator
:圆形进度指示器,圆形进度条。
Spacer
:用于占据空白区域,并支持自定义大小
AppBar
: 应用栏。
Drawer
: 抽屉式布局。用于显示侧边栏的控件。
Box
:用于在自由布局中控制位置、大小和绘制顺序等。
Snackbar
:用于在屏幕底部显示消息的控件。
Navigation
:用于管理应用程序的导航,提供了一种可以让用户从一个屏幕到另一个屏幕的方式。
ViewPager2
:用于创建可左右滑动的页面。
SwipeRefreshLayout
:可用于实现下拉刷新操作的控件。
ProgressIndicator
:用于显示进度的控件,提供了多种样式,如环形进度条、线性进度条等。
WebView
:用于在应用中加载网页的控件。
SurfaceView
:用于在应用中显示视频的控件,支持播放本地视频和网络视频。
LinearProgressIndicator
:线性进度条
DropdownMenu
:实现下拉菜单
PopupMenu
:弹出菜单
LazyColumn
:垂直滚动列表
LazyRow
:水平滚动列表
LazyVerticalGrid
:垂直滚动网格
LazyHorizontalGrid
:水平滚动网格
Pager
:分页控件
Surface
:用于创建表面,可以用来绘制自定义的UI元素。
SwipeRefresh
:用于创建下拉刷新的控件。
Accompanist
:提供了许多有用的Compose控件,例如各种加载占位符、图片缩放控件、滑动刷新控件等等。
Compose Charts
:提供了各种绘图控件,包括折线图、柱状图、饼状图等等。
Compose Navigator
:提供了一种新的导航方式,通过声明式路由和导航来管理不同屏幕之间的转换。
Compose DataTable
:提供了数据表格控件,用于展示数据的表格。
Compose Countdown Timer
:提供了倒计时控件。
Compose Material Dialogs
:提供了Material Design风格的对话框控件。
Compose Timeline
:提供了时间线控件。
Compose Dropdown Menu
:提供了下拉菜单控件。
BottomAppBar
:用于底部应用程序栏。
DatePicker
:用于选择日期。
BottomAppBar
: 底部应用栏
BottomDrawer
: 底部抽屉
TopAppBar
: 顶部应用栏
ViewPager
: 用于滑动切换多个页面的控件
方法参数 + 结尾block
块来组合的
Modifier
来完成,这个类会有构建各种,包含但不限于(宽度,高度,描边,圆角)等等代码示例如下
Image(
painter = painterResource(id = R.drawable.ic_launcher_background),//通过painterResource函数指定资源图
contentDescription = "头像",//指定描述文案
modifier = Modifier //通过Modifier指定控件大小,裁切类型,描边类型
.size(40.dp)
.clip(CircleShape)
.border(1.dp, Color.Blue, CircleShape)
)
//Activity onCreate中调用setContent方法,这里的initView是一个Compose函数
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java]
initView()
}
}
通过
@Composable
注解函数会被认定为compose相关视图,对应的内部才能调用compose的函数,以及预览等,这一块是有编码校验的
@OptIn(ExperimentalUnitApi::class)
@Preview
@Composable
fun initView() {
Column(
modifier = Modifier
.background(color = colorResource(id = R.color.bg_white))
.fillMaxWidth()
.fillMaxHeight(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally
) {
LogIcon()
VSpacer(30)
Text(text = "Welcome to example", fontWeight = FontWeight.Bold, fontSize = TextUnit(23f, TextUnitType.Sp))
VSpacer(30)
InputEdit(hint = "please input your userName")
VSpacer(5)
InputEdit(hint = "please input your password", true)
VSpacer(30)
Button(onClick = { Toast.makeText(this@LoginActivity, "测试", Toast.LENGTH_SHORT).show() }) {
Text(text = "Login")
}
}
}
以上定义的
initView
函数就是添加@Composable
注解,还有@Preview
,添加了@Preview
注解之后,会在编码框右边看到当前代码的预览图像.如下图所示这样,左边是代码,右边就是预览图,整体还是比较精准的.不过这里没办法选择设备类型,也就是预览图像没有发现哪里可以选择预览的分辨率.能够看个大概
@Composable
fun LogIcon() {
Image(
painter = painterResource(id = R.drawable.ic_launcher_background),
contentDescription = "图标",
modifier = Modifier
.width(50.dp)
.height(50.dp)
.clip(CircleShape)
.border(2.dp, colorResource(id = android.R.color.black), CircleShape)
)
}
LogIcon就是顶部的图标,指定了图标资源,描边,圆形裁切.宽度和高度.
@Composable
fun VSpacer(height: Int) {
Spacer(
modifier = Modifier
.fillMaxWidth()
.height(height.dp)
)
}
因为这里的分割线都是横向的,所以单独抽了一个函数,通过传入的高度决定是多高.不在外面重复编写.
这里就能看出对比xml的优势了.如果是xml中想要复用的话,是很难做到这种程度的,得挨着复制.
Text(text = "Welcome to example", fontWeight = FontWeight.Bold, fontSize = TextUnit(23f, TextUnitType.Sp))
顶部的欢迎文案 所有的控件都通过
Column
来管理,顶部有介绍这个类似于垂直布局的LinearLayout
@Composable
fun InputEdit(hint: String, isPassword: Boolean = false) {
val textState = remember {
mutableStateOf("")
}
if (!isPassword) {
loginViewModel.saveName.observeForever { }
loginViewModel.saveName.observe(this) {
textState.value = it
}
}
TextField(visualTransformation = if (isPassword) PasswordVisualTransformation() else VisualTransformation.None,
value = textState.value,
onValueChange = {
textState.value = it
if (!isPassword) {
loginViewModel.saveName.value = it
}
},
modifier = Modifier
.width(260.dp)
.height(56.dp),
label = { Text(if (isPassword) "Password" else "UserName") },
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Done, keyboardType = KeyboardType.Password
),
placeholder = { Text(text = hint, Modifier.alpha(0.5f)) })
}
以上是对应的用户账号和用户密码的输入框,定义了两个形参hint和isPassword 用来动态的显示提示文案和是否是密码输入框
因为这个控件是复用的.对比xml如果要这样做的话,要不就是需要抽取style公共主题,要不就是通过include的方式,然后代码中动态设置.
但是声明式就是这样,关注你的展示结果,而不关注实现过程.可以一个函数搞定这些配置化.这里东西稍微多一些.分开描述一下
TextField
输入框,不过源码有打上ExperimentalMaterial3Api
注解,后期可能会做出调整
visualTransformation
对应的是输入框内容的显示模式,因为密码的话应该要显示***这种样式需要指定为PasswordVisualTransformation()
value
对应输入框内容,这里比如要有接受参数,否则输入框会无法键入内容.所以上面创建了一个mutableStateOf
对象来接受数据,这个mutableStateOf
是会动态更新的,也就是修改state的值,会自动刷新到ui上面去.onValueChange
内容变更的时候就会触发,这里会简单演示了一下和ViewModle配合使用的场景,把这个数据设置到livedata中去,这样切换白天黑夜就依然会保存值,但是需要在上面obser监听.label
是指定顶部有一个类似于常驻的提示文本.这个不同于hint,是不会因为输入内容而消失的.keyboardOptions
指定键盘的输入类型placeholder
指定在没有内容的时候显示什么,这里就是一个Text显示hint的文案.并且透明度为0.5f Button(onClick = { Toast.makeText(this@LoginActivity, "测试", Toast.LENGTH_SHORT).show() }) {
Text(text = "Login")
}
最后剩下一个登录按钮,通过Button就可以了,传入onClick blcok参数.后面的block体重,传入Text用于显示文本
class LoginViewModel : ViewModel() {
val saveName = MutableLiveData<String>()
}
最后贴一下ViewModel的代码
Activity完整代码如下
@OptIn(ExperimentalMaterial3Api::class)
class LoginActivity : ComponentActivity() {
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java]
initView()
}
}
@OptIn(ExperimentalUnitApi::class)
@Preview
@Composable
fun initView() {
Column(
modifier = Modifier
.background(color = colorResource(id = R.color.bg_white))
.fillMaxWidth()
.fillMaxHeight(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally
) {
LogIcon()
VSpacer(30)
Text(text = "Welcome to example", fontWeight = FontWeight.Bold, fontSize = TextUnit(23f, TextUnitType.Sp))
VSpacer(30)
InputEdit(hint = "please input your userName")
VSpacer(5)
InputEdit(hint = "please input your password", true)
VSpacer(30)
Button(onClick = { Toast.makeText(this@LoginActivity, "测试", Toast.LENGTH_SHORT).show() }) {
Text(text = "Login")
}
}
}
@Composable
fun LogIcon() {
Image(
painter = painterResource(id = R.drawable.ic_launcher_background),
contentDescription = "图标",
modifier = Modifier
.width(50.dp)
.height(50.dp)
.clip(CircleShape)
.border(2.dp, colorResource(id = android.R.color.black), CircleShape)
)
}
@Composable
fun VSpacer(height: Int) {
Spacer(
modifier = Modifier
.fillMaxWidth()
.height(height.dp)
)
}
@Composable
fun InputEdit(hint: String, isPassword: Boolean = false) {
val textState = remember {
mutableStateOf("")
}
if (!isPassword) {
loginViewModel.saveName.observeForever { }
loginViewModel.saveName.observe(this) {
textState.value = it
}
}
TextField(visualTransformation = if (isPassword) PasswordVisualTransformation() else VisualTransformation.None,
value = textState.value,
onValueChange = {
textState.value = it
if (!isPassword) {
loginViewModel.saveName.value = it
}
},
modifier = Modifier
.width(260.dp)
.height(56.dp),
label = { Text(if (isPassword) "Password" else "UserName") },
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Done, keyboardType = KeyboardType.Password
),
placeholder = { Text(text = hint, Modifier.alpha(0.5f)) })
}
}