最近尝试了Jetpack Compose ,参考Compose 编程思想,我在测试中使用的版本是“androidx.activity:activity-compose:1.3.0-alpha03”
本文中所写的案例95%都经过测试,可以完成一个页面的基本布局,跟kotlin的anko相比,这个更好用,是推荐的。但在生产环境完成页面来说还有距离,对复杂页面我感觉不适合,查代码可太复杂了。
本篇只讲基础操作,动画和手势放在了单独的篇章讲解
1、运行工程遇到的问题
1)下载了示例工程代码,使用as4.0打开始终找不到app,报错:
Extension with name ‘android’ does not exist
咋办呢?下载官方编译器:
Canary build
Get the leading-edge features in a lightly tested build.
DOWNLOAD ARCTIC FOX (2020.3.1) CANARY 9
2)Please remove usages of jcenter()
Maven repository from your build scripts and migrate your build to other Maven repositories.
移除repositories中的`jcenter()
3)Failed to find Build Tools revision 30.0.2
我的问题是根据提示升级了com.android.tools.build:gradle:7.0.0-alpha08’
到09,运行报错,然后发现
android {
compileSdkVersion 30
buildToolsVersion “30.0.2”//这一句消失了,可以把它指定为as中当前有的版本。
之后发现是因为我没有30.0.2这个版本才报错,我就下了一个
然后就运行起来了
然后就运行起来了,当然页面相当丑
关于实时预览,我的电脑是17配的windows中端机,i76700CPU(3.4、3.41GHZ),8G内存,每次预览要延迟个40s-1分钟,大部分保存能触发刷新,但有时候得手动刷新,刷新结果还不一定准确
4)setContent{}
setContent从androidx.compose.ui.platform移到了androidx.activity.compose,需引入implementation “androidx.activity:activity-compose:1.3.0-alpha02”
2、Modifier
这是一个用来声明View属性的对象,所有的View都有这个属性
modifier = Modifier
//尺寸
.fillMaxHeight() / .fillMaxWidth() / .fillMaxSize() //相当于match
.wrapContentHeight(align = Alignment.Top)//Top|Bottom|CenterVertically
.wrapContentWidth(align = Alignment.End)//CenterHorizontally|Start|End
.wrapContentSize(align = Alignment.Center)//Center|CenterStart|CenterEnd|TopCenter|TopStart|TopEnd|BottomCenter|BottomStart|BottomEnd
.height(80.dp).width(70.dp) //设置组件给定的宽高
//间隔
.padding(10.dp,10.dp,0.dp,10.dp)//设置组件padding,注意padding结合background的作用相当于margin;同时padding作用于Size内部,而非外部,因此需要注意背景尺寸变化
.background( )//背景
background( color = Color.LightGray) //默认填充颜色
background( color = Color.LightGray, shape = RoundedCornerShape(20.dp))//带颜色的圆角Shap,需要注意圆角外的空间一开始默认填充为黑色,后来就完全填充看不出形状了,解决办法是再设置shape=RoundedCornerShape(20.dp))
.border()//边框,注意边框在背景内部,因此边框较宽时需要增大组件尺寸
.border(width = 3.dp,color = Color.Black)// 宽度和颜色
.alpha() //透明度,对背景不起作用
总体来说JC的组件分成三类:基础布局、基础组件、嵌套组件
3、基础布局
1)Column 纵向布局 &Row 横向布局:LinearLayout
Column 默认设置会直接将所有子项逐个堆叠起来,中间不留间距,子View默认靠左。
Column(modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.background(Color.LightGray)
,verticalArrangement = Arrangement.Bottom
//verticalArrangement: Arrangement.Vertical = Arrangement.Top 布局子级的 竖直 放置方式,默认从布局顶部往布局底部方向放置
verticalArrangement = Arrangement.spacedBy(4.dp),//官方提供的用法,内部组件的间隔
,horizontalAlignment = Alignment.Start
//horizontalAlignment: Alignment.Horizontal = Alignment.Start 布局子级的 水平直 对其方式,默认从布局开始对齐
) {内容}
2)Box :FrameLayout
Box(modifier = Modifier .height(150.dp) .width(150.dp)
,contentAlignment = Alignment.Center//内容对齐方式
){
//子View
}
3)LazyRow/LazyColumn :RecyclerView & ScrollView
alpha11废弃了滚动视图系列 ScrollableRow、ScrollableColumn,因为超长列表的情况,滚动视图没有缓存机制,列表过长的时候,容易造成内存溢出。
替代实现滚动的组件是LazyRow/LazyColumn,对标RecyclerView,无需Adapter。但下拉刷新,上拉加载等效果待研究;
当然如果内容不多可以用Row(Modifier.horizontalScroll(rememberScrollState()) / Column
()来代替实现滚动效果。不过Row/Column实现的滚动效果都不会处理视图的缓存复用。
需要注意的是,Lazy的布局内部必须是item{},其内部才能增加布局,否则会报错。而且只能实现单行或者单列布局
val listState = rememberLazyListState()//用于监听滚动
LazyRow(
content = {
item {
Text(text = "Head")
}
val listData = (0..5).toList()
items(listData.size) {//循环生成的布局
Text(
text = "Text:${listData.get(it)}",
Modifier.fillMaxHeight().width(60.dp)
.border(width = 1.dp, color = Color.Cyan)
)
}
item { //单独成型的布局
Text(
text = "Footer", modifier = Modifier
.background(color = Color.Green)
.fillMaxHeight(), textAlign = TextAlign.Center
)
}
}, modifier = Modifier.fillMaxWidth().height(80.dp)
.background( Color.LightGray )
,state = listState
)
需要提的是,根据官网发布的消息,LazyColumn 具有固定头stickyHeader ,但其为ExperimentalFoundationApi。而且目前不支持动画
4)LazyVerticalGrid:GirdView
网格布局 1.0.0-beta02版本出现,这个在用的时候一直报错,原因是因为它是个实验性的组件,因此需要添加额外的注释@ExperimentalFoundationApi,并且需要给调用该组件的所有父函数添加。
比如我在函数testApp()中调用了该组件,那么调用testApp()的函数alpha()和调用了alpha()的beta()都需要添加@ExperimentalFoundationApi。
LazyVerticalGrid(
cells = GridCells.Fixed(2)
) {
items(listData) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(24.dp)
.background(
color = when {
it % 2 == 0 -> {
Color.Yellow
}
else -> {
Color.Red
}
}
),
contentAlignment = Alignment.Center,
) {
Text(
text = "列表项:$it"
)
}
}
}
5)ConstraintLayout
这个我在引用的时候只有android下面需要传context的版本,因此我猜测需要额外的引包,或者需要带包名的类全程
ConstraintLayout
约束布局迁移到了新包中,需引入implementation “androidx.constraintlayout:constraintlayout-compose:1.0.0-alpha02”
ConstraintLayout {
// Create references for the composables to constrain
val (button, text) = createRefs()
Button(
onClick = { /* Do something */ },
// Assign reference "button" to the Button composable
// and constrain it to the top of the ConstraintLayout
modifier = Modifier.constrainAs(button) {
top.linkTo(parent.top, margin = 16.dp)
}
) {
Text("Button")
}
// Assign reference "text" to the Text composable
// and constrain it to the bottom of the Button composable
Text("Text", Modifier.constrainAs(text) {
top.linkTo(button.bottom, margin = 16.dp)
})
}
上述为官方案例
5)Scaffold :Material顶层布局
JC提供了一个Material 顶层布局Scaffold,官方介绍的用途是“让您实现具有基本 Material Design 布局结构的界面,使用 Scaffold,很容易确保 TopAppBar、BottomAppBar、FloatingActionButton 和 Drawer这些组件得到适当放置且正确地协同工作”
Scaffold (
drawerContent = { /*...*/ },
topBar = { /*...*/ },
bodyContent = { /*...*/ }
)
//一个完整案例(未测试)
val scaffoldState = rememberScaffoldState()
Scaffold(
scaffoldState = scaffoldState,
drawerContent = {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(text = "抽屉组件中内容")
}
},
//标题栏区域
topBar = {
TopAppBar(
title = { Text(text = "脚手架示例") },
navigationIcon = {
IconButton(
onClick = {
scaffoldState.drawerState.open()
}
) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = null
)
}
}
)
},
//悬浮按钮
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("悬浮按钮") },
onClick = { }
)
},
floatingActionButtonPosition = FabPosition.End,
//屏幕内容区域
content= {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(text = "屏幕内容区域")
}
},
)`
4、基础组件
内部不能嵌套任何东西的原组件,最小分子
1)Text / ClickableText:TextView
二者区别在于能否使用onClick ,Button只能设置背景和点击事件不能显示文字
Text(
text = "YAMA",//要显示的文字
color = Color.White,// 文字颜色,modifuer中alpha的作用对象
fontWeight = FontWeight.ExtraBold,//样式
fontStyle = FontStyle.Italic,//字体
fontFamily = FontFamily.Cursive,//字体
fontWeight
textAlign = TextAlign.Center,//文本在段落行内的对齐方式,测试时发现他调整的是横向的文字位置,不涉及到纵向,因此height巨大时不能View的居中显示。其中Justify、left和start表现是一样的;right和end表现一样。设置他不能做纵向的居中
textDecoration = TextDecoration.LineThrough 给文字加线,三种样式:文字中间、文字底部、没有
style = androidx.compose.ui.text.TextStyle.Default,//样式,推荐使用MaterialTheme.typography.XXX
//尺寸相关
fontSize lineHeight letterSpacing 三个属性都要求 TextUnit参数,默认值为TextUnit.Unspecified。值为XX.sp,注意此处不能使用dp
//其他属性
maxLines = 2 //最大显示行数
overflow = TextOverflow.Ellipsis //超出限制后显示……
softWrap = true//自动折行
)
关于文字位置的设置遇到一些问题:
1)当Text处于Surface中,且Surface尺寸固定时,居中仅设置modifier = Modifier.wrapContentSize()就可以实现;不设置宽高或者设置为fill默认在左上角;
2)当Text没有外容器时,设置fillMaxHeight,背景能填充的范围,但文字通过textAlign 不能设置居中
3)使用第三方字体
需要将字体放入res/font下面,之后需要声明
//声明
val firaSansFamily = FontFamily(
Font(R.font.firasans_light, FontWeight.Light),
Font(R.font.firasans_regular, FontWeight.Normal),
Font(R.font.firasans_italic, FontWeight.Normal, FontStyle.Italic),
Font(R.font.firasans_medium, FontWeight.Medium),
Font(R.font.firasans_bold, FontWeight.Bold)
)
//使用
Text( fontFamily = firaSansFamily, fontWeight = FontWeight.Normal,fontStyle = FontStyle.Italic)
4)文字重复并限制显示
Text("hello ".repeat(50), maxLines = 2)
5)点击事件:给整个Text设置点击事件,需要在Modifier中的clickable()方法设置
2)Image / Icon:ImageView
这个地方有个坑,教程和网上都说用imageResource来获取图片,然而我这版本没有这个函数,最后看的源码发现了内置函数1中的图片获取.而且我是真没想到,为了区分bitmap和vector专门做了两个组件
Image(bitmap = image, contentDescription = "image test",modifier = Modifier
.width(80.dp)
.height(80.dp))
Icon(imageVector =ImageVector.Companion.vectorResource(id=R.drawable.ic_baseline_ac_unit_24) , contentDescription ="icon deta"
tint = Color.Blue )
Image和Icon一样 接受 imageVector: ImageVector, bitmap: ImageBitmap, painter: Painter, 三种图片资源,其中可以转换:
ImageBitmap转Painter remember(bitmap) { BitmapPainter(bitmap) }
imageVector转Painter rememberVectorPainter(imageVector)
注意VectorDrawable和BitmapDrawable严格区分,混用会报错
Icon的tint有两点要说:
1)使纯色图标改色使用 tint = 颜色
2)使彩色图标显色使用 tint = Color.Unspecified(不应用任何色调) 不设置会是黑乎乎的一团,受 AmbientContentColor.current 默认着色影响
3)Switch / CheckBox
整个按钮我就想说他们都需要val checkedState = remember { mutableStateOf(true) }来记录状态,否则选中状态是不会更改的
val switchCheck = remember { mutableStateOf(true) }
val checkCheck = remember { mutableStateOf(true) }
Switch(checked = switchCheck.value, onCheckedChange = {
Log.i("switch", "$it")
switchCheck.value = it
})
Checkbox(checked = checkCheck.value, onCheckedChange = {
Log.i("checkbox", "$it")
checkCheck.value = it
// check(it)//baocuo
})
绝大部分属性和button很像,这俩目前不知道如何更换图片,使用IconButton倒是可以
4)RadioButton
这个组件一般用来多选,单选建议使用checkbox或者iconButton
//RadioButton数量
val tags = arrayListOf("Java", "Kotlin", "XML", "Compose", "JetPack")
//选中的值
val radioCheck = remember { mutableStateOf("Null") }
Row() {
tags.forEach {
Row {
RadioButton(
selected = it == radioCheck.value,
onClick = {
radioCheck.value = it
}
)
Text(text = it)
}
Spacer(modifier = Modifier.width(20.dp))
}
}
5) CircularProgressIndicator /LinearProgressIndicator :ProgressBar
圆形进度条 CircularProgressIndicator
CircularProgressIndicator不声明progress参数的时候是无限转动的,声明之后则需要触发,可以通过rememberProgress.value的增加和减少来改变进度
CircularProgressIndicator() //loading
val rememberProgress = remember { mutableStateOf(0.1f) }//正经进度条
CircularProgressIndicator(
progress = rememberProgress.value
)
TextButton(onClick = {//点击触发
if (rememberProgress.value < 1f) rememberProgress.value += 0.1f }) {
Text(text = "增加进度")
}
长条进度条 LinearProgressIndicator
LinearProgressIndicator( )//loading
val rememberProgress = remember { mutableStateOf(0.1f) }//正经进度条
LinearProgressIndicator( progress = rememberProgress.value)
TextButton(onClick = {//点击触发
if (rememberProgress.value < 1f) rememberProgress.value += 0.1f }) {
Text(text = "增加进度")
}
进度条是可以添加动画的,要点是progress = animatedProgress;关于animatedProgress的写法我所用的版本有争议还在探索
6)Slider :SeekBar可拖动进度条
var sliderValue = remember{ mutableStateOf(0f)}
Slider(value = sliderValue.value
, onValueChange = {
sliderValue.value = it
Log.i("slider","$it") }
,enabled = true
,valueRange = 0f..1f //取值范围
,colors = SliderDefaults.colors(thumbColor = Color.Yellow)
,steps = 3//步骤 分段,划到两段中间会自动靠边
,interactionSource = remember { MutableInteractionSource() }
)
7)Spacer:Space & View
Spacer(modifier = Modifier.height(20.dp).width(100.dp)
.background(Color.Red,shape = CutCornerShape(10.dp))
测试了一下可以当作Space或者仅设置背景色的View用,jc当中没有margin,我看范例中都是用Spacer
5、嵌套组件
1)Surface
这个布局可以看作是一个FrameLayout,内部组件全部在左上角,可以做 Image和Text的组合嵌套
Surface( modifier = Modifier.height(60.dp).width(60.dp),
shape = RoundedCornerShape(20.dp), //形状的颜色由下面的color控制,而非modifier 中的background
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
) { //这里是布局内容 }
注意经过测试发现给Surface设置modifier .background不生效,默认为白色填充;不设置background时默认为透明;border生效可以正常显示
2)Button
没想到吧,我当初用这个的时候一头雾水薪水这个Button怎么不能设置Text呢,结果他是内部嵌套View。Button默认是个横向布局
Button(
onClick = {//点击事件
Log.d("ClickableText", " -th character is clicked.") }
,modifier = Modifier.background(Color.Yellow).padding(10.dp)
//button背景
,border = BorderStroke(1.dp, Color.White)
,colors = ButtonDefaults.buttonColors(contentColor = Color.Yellow)
//阴影
,elevation = ButtonDefaults.elevation(defaultElevation = 3.dp)
,shape = MaterialTheme.shapes.small.copy(
topStart = ZeroCornerSize, topEnd = CornerSize(40.dp)
,bottomEnd = CornerSize(40.dp),bottomStart = ZeroCornerSize
)
//可用性
,enabled = true
//容器和子View之间的距离值
,contentPadding = PaddingValues(40.dp)
) {//Button内部的文字和图标
Text(text = buildAnnotatedString {
append("Button:")
withStyle(style = SpanStyle(color = Color.Blue,
fontWeight = FontWeight.Bold )){
append("style \n");
}
})
Text(text = stringResource(id = R.string.app_name))
}
Button的着色顺序是:modifier -> Button属性 -> 子View
上图中 黄色部分是modifier .background,紫色部分是Button的color+shape作用
需要注意的是colors 和elevation 获取 与12版本的教程不一样,最后翻源码翻出来的。
还有OutlinedButton和TextButton在参数上没有什么区别,更多是概念上的,Button用于主要操作;Outlined 主要用于重要但不主要的操作;TextButton用于不明显的动作,如cards and dialogs中的按钮
IconButton是可单击的图标,用于表示操作。 IconButton的整体最小触摸目标大小为48 x 48dp,以满足辅助功能准则。内容位于IconButton内部。
注意Button系列中的interactionState在新版本中用,interactionSource = MutableInteractionSource()替代了
3)IconToggleButton
该组件必须放置一个icon,否则显示空白
val checkedState = remember { mutableStateOf(true) }//更换状态的关键字
IconToggleButton(checked = checkedState.value
,onCheckedChange = {
Log.e("IconToggleButton", "点击了:$it")
checkedState.value = it
} ) {
Icon( imageVector = Icons.Filled.Favorite,
contentDescription ="icon"
, tint = if (checkedState.value) { Color.Red }
else { Color.Gray }
)
}
}
4)文字选中 DisableSelection / SelectionContainer
这是一种用于text的容器控件,控件内的文字处于“可以被选中”和“不可以被选中”的状态,我顺带测试了一下,具有文字的组件都不能被选中
SelectionContainer {//可选的
Text("This text is selectable")
}
DisableSelection {//不可选的
Text("This text is not selectable")
}
5)Card![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/64c02dcd36a6e724ec28ccf411ccff50.png)
card是典型的嵌套组件,官方案例测试得到
var expanded = remember { mutableStateOf(false) }
var title = "aaaaaaaa"
var body="aaaaa\naaaaaa\naaaaa\naaa\n"
// Describe the card for the current state of expanded.
Card {
Column {
Text(text = title)
// Content of the card depends on the current value of expanded.
if (expanded.value) {
Text(text = body)
// Change expanded in response to click events.
IconButton(onClick = { expanded.value = false }) {
Icon(Icons.Default.ExpandLess, contentDescription = "Collapse")
}
} else {
// Change expanded in response to click events.
IconButton(onClick = { expanded.value = true }) {
Icon(Icons.Default.ExpandMore, contentDescription = "Expand")
}
}
}
}
6、其他组件
1) TextField :TextInputLayout
从使用上说这是一个更优化的TextInputLayout。普通的Text是不能输入并更改更新的。该组件分为TextField(效果与EditText默认效果一致)和OutlinedTextField(效果为带有一个边框用于自定义)
示例代码我运行不起来,测试后发现是源代码中的var text by remember { mutableStateOf(“Hello”) }这句有问题,TextField要求我们自行处理内容更新的问题,然而我找到的资料在我用的版本上用不了,痛哭
var password by rememberSaveable { mutableStateOf("") }//官方提供,我所在的JC版本运行不起来
var text by savedInstanceState { “” } //网友提供,也不好用
最后发现和checkbox的选择一样,使用:val textValue = remember{mutableStateOf(“null”)} 时,可以通过textValue.value 来获取的,万能的mutableStateOf
所以我觉得,犹豫不决的时候都可以用remember{mutableStateOf(Object)}
val textValue = remember{mutableStateOf("null")}//文字缓存
TextField(
//值
value= textValue.value //,包含了输入框编辑状态的信息,这个功能很强大,可以用来更新文本,光标等,然后还可以从其他位置直接观察到这些值的变化。也就是相当于双向绑定的意思
,onValueChange = {Log.i("textChange","内容:$it")
textValue.value = it}//输入服务更新TextFieldValue中的值时触发的回调,更新后的TextFieldValue值作为回调参数
//显示在文本字段内的可选标签 Title
,label = { Text("Enter password") }//显示在文本字段内的可选标签,类似于占位符,但是是输入框没有获得焦点时候的占位符
//hint
,placeholder = {
Text("请输入内容")
}
//输入框的样式,默认样式为AmbientTextStyle.current
,textStyle = MaterialTheme.typography.button// 应用于输入框的样式,目前还没成功
,shape = CutCornerShape(20.dp)
//文字颜色控制
,colors = TextFieldDefaults.textFieldColors(textColor = Color.Yellow)
,interactionSource = MutableInteractionSource()
//图标
,leadingIcon = {//左侧图标
Icon(imageVector = Icons.Filled.Lock, contentDescription = "icon")
}
,trailingIcon = {//右侧图标,一般用来显示delete
Icon(
imageVector = Icons.Filled.Delete,
contentDescription = "icon"
,modifier = Modifier.clickable( onClick = { textValue.value="" } ) ) }
//输入限制
,visualTransformation = androidx.compose.ui.text.input.PasswordVisualTransformation(),//注意这个要写带包名的全称,我用的类名一直报错
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
//capitalization //autoCorrect //keyboardType //imeAction
,keyboardActions = KeyboardActions(onDone = { })
,maxLines = 2 //Int.MAX_VALUE,显示行数
,enabled = true
,readOnly = true //只读模式不可修改值
,singleLine = true //单行显示
,isError = true //是否处于报错状态,有报错样式
)
组件具有左侧的icon,右侧的删除按钮,背景设置和错误提示,算是比较全面的了
2)TabRow / ScrollableTabRow:TabBar
TabRow、ScrollableTabRow的区别是,前者让选项平分充满屏幕宽度;后者让选项完全展开。虽然有了TabBar但目前还缺少ViewPager
var state = remember { mutableStateOf(0) }//存tab号
val titles = listOf("标签1", "标签2", "这是很长的标签3","6789uij")//标签
Column {
//超出范围后可以滚动的TabBar
ScrollableTabRow(
selectedTabIndex = state.value,
modifier = Modifier.wrapContentWidth(),
edgePadding = 16.dp //独有属性,前后padding
) {
titles.forEachIndexed { index, title ->
Tab(
text = { Text(title) },
selected = state.value == index,
onClick = { state.value = index }
) }
}
//完全填充Tabbar
TabRow(selectedTabIndex = state.value//当前选中项
//标签文字颜色
,backgroundColor = Color.Yellow
,contentColor = Color.Cyan
//标签点击的动画效果
,indicator ={tabPositions ->
TabRowDefaults.Indicator( Modifier.tabIndicatorOffset(tabPositions[state.value]) ) }
//位于tab组件上指定位置的一条线,默认是在底部和活动条一个位置,如果高度高于活动条,则活动条会露白
,divider = { TabRowDefaults.Divider(color = Color.Yellow,modifier = Modifier.height(3.dp)) }
) {
titles.forEachIndexed { index, title ->
Tab(
text = { Text(title) },
selected = state.value == index,
onClick = { state.value = index }
)
}
}
Spacer(modifier = Modifier.height(20.dp))
Text(
modifier = Modifier.align(Alignment.CenterHorizontally),
text = "第${state.value + 1}个标签被选中了",
style = MaterialTheme.typography.body1
)
}
上图为divider 示例
3)TopAppBar、BottomAppBar:Toolbar
TopAppBar包含标题,导航图标和其他的更多操作等。
//底部
BottomAppBar(
modifier = Modifier.fillMaxWidth().height(80.dp),
backgroundColor = Color.Green,//背景色
contentColor = Color.Cyan,//文字颜色
cutoutShape = CutCornerShape(20.dp),//看不出来
elevation = 1.dp
) {
//BottomAppBar的内容,实际上是高度自定义的没有任何限制,所以我感觉可以用来替代tabBar
}
我测试了一下,TopAppBar和BottomAppBar所处的位置由布局决定,不会因为用了TopAppBar就自动置顶,BottomAppBar同理。这俩的区别直观来说:
TopAppBar的内容不会居中,呈现一种贴top的样式;而BottomAppBar的内容会垂直居中
最后:两个组件都需要使用 Spacer(Modifier.weight(1f, true))来撑开空隙
遇到的问题:TopAppBar有两个构造函数,使用带参数title的这个函数时会报错,判断指向,指向的是另一个
4)DropdownMenu:popupwindows菜单
这个我在网上看的博客测评的是alpha09版本,和我用的1.3.0-alpha03差别巨大,主要区别是对menu的触发icon的处理,从组件中声明,变成了下面这种全靠mutableStateOf开关的方式,后者我感觉更解耦
它内部使用PopUp实现
var expandedDropdownMenu = remember { mutableStateOf(false) }
IconButton(onClick = { expandedDropdownMenu.value = true }) {
Icon(imageVector = Icons.Default.MoreVert
,contentDescription = "")
}
DropdownMenu(
expanded = expandedDropdownMenu.value,
onDismissRequest = { expandedDropdownMenu.value = false }
,modifier = Modifier.wrapContentSize()
,offset = DpOffset(0.dp,10.dp)
// ,properties = PopupProperties()
) {
DropdownMenuItem(onClick = { }) {
Text("分享")
}
DropdownMenuItem(onClick = { }) {
Text("举报")
}
DropdownMenuItem(onClick = { }) {
Text("关注")
}
}
5)AlertDialog
就是很普通的alertDialog样式
var expandedDropdownMenu = remember { mutableStateOf(false) }
if (expandedDropdownMenu.value) {
AlertDialog(
onDismissRequest = {
expandedDropdownMenu.value = false
},
title = {
Text(text = "这是对话框标题")
},
text = {
Text(
"这是一段描述对话框提示内容的文本, " +
"这个文本有点长,还有点俏皮!"
)
},
confirmButton = {
TextButton(
onClick = {
expandedDropdownMenu.value = false
}
) {
Text("确认")
}
},
dismissButton = {
TextButton(
onClick = {
expandedDropdownMenu.value = false
}
) {
Text("取消")
}
}
)
}
}
7、内置函数
0)主题
方便一键换色,官方指导
MaterialTheme(
colors = …,
typography = …,
shapes = …
) {
// app content
}
这个我感觉尤其像anko
1)Resources系列
位于在ui:ui:1.0.0-beta01@aar下统一package为androidx.compose.ui.res
//资源类
//颜色
colorResource(id: kotlin.Int):androidx.compose.ui.graphics.Color
ResourcesCompat.getColor(@NonNull Resources res, @ColorRes int id, @Nullable Theme theme)
ColorStateList getColorStateList(@NonNull Resources res, @ColorRes int id, @Nullable Theme theme)
//尺寸
dimensionResource(id: kotlin.Int): androidx.compose.ui.unit.Dp { /* compiled code */ }
animatedVectorResource(id: kotlin.Int): androidx.compose.ui.graphics.vector.AnimatedImageVector
fontResource(fontFamily = ):androidx.compose.ui.text.font.Typeface
//数值类
stringResource(id: kotlin.Int): kotlin.String
text = stringResource(R.string.congratulate, "New Year", 2021) //<string name="congratulate">Happy %1$s %2$d</string>
booleanResource(id: kotlin.Int): kotlin.Boolean { /* compiled code */ }
integerArrayResource(id: kotlin.Int): kotlin.IntArray { /* compiled code */ }
stringArrayResource(id: kotlin.Int): kotlin.Array<kotlin.String>
// <plurals name="runtime_format">
// <item quantity="one">%1$d minute</item>
// <item quantity="other">%1$d minutes</item>
// </plurals>
resources.getQuantityString( R.plurals.runtime_format, quantity, quantity )
integerResource(id: kotlin.Int):kotlin.Int
//图片类
ImageVector:
ImageVector.Companion.vectorResource(id: kotlin.Int)
使用 androidx.compose.material.icons 包下的Icons中提供的资源,例如Icons.Outlined.Face
ImageBitmap
ImageBitmap.Companion.imageResource(id = )
androidx.compose.ui.graphics.painter.Painter
painter = painterResource(id = R.drawable.ic_logo)
我用的这个版本一致找不到网上和官网资料上的imageResource(id = R.drawable.xxx),而这让我比较头疼是因为 Image和icon组件可以用imageBitmap和imageVector,稍有不慎就写错了
2)Shape
CutCornerShape(20.dp) 折角矩形
AbsoluteCutCornerShape(20.dp) 折角矩形
AbsoluteRoundedCornerShape() 圆角矩形
CircleShape 如果尺寸为方则为圆形,长方则为圆角矩形
RectangleShape 直角矩形
MaterialTheme.shapes.small | medium 小圆角矩形
MaterialTheme.shapes.large 直角矩形
//小修改(复制系统的修改圆角)
shape = MaterialTheme.shapes.small.copy(
topStart = ZeroCornerSize, topEnd = CornerSize(40.dp)
,bottomEnd = CornerSize(40.dp),bottomStart = ZeroCornerSize)
3)保存值
remember{ mutableStateOf(0f)} 我目前只用这一个,已经能满足需求,可存入 String、bool、int、float完全没压力
remember { MutableInteractionSource() }这个目前还没探索,但是很多组件都有这个interactionSource属性
大量值的保存
官方有个案例值得借鉴,通过HelloViewModel 做数据传递
class HelloViewModel : ViewModel() {
// LiveData holds state which is observed by the UI.
// (state flows down from ViewModel)
private val _name = MutableLiveData("")
val name: LiveData<String> = _name
// onNameChanged is an event we're defining that the UI can invoke.
// (events flow up from UI)
fun onNameChanged(newName: String) {
_name.value = newName
}
}
@Composable
fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {
// By default, viewModel() follows the Lifecycle as the Activity or Fragment
// that calls HelloScreen().
// name is the _current_ value of [helloViewModel.name]
// with an initial value of "".
// observeAsState returns a State<T> that will trigger a recomposition
// of the composables that read the state whenever it changes.
val name: String by helloViewModel.name.observeAsState("")
Column {
Text(text = "Hello, $name")
TextField(
value = name,
onValueChange = { helloViewModel.onNameChanged(it) },
label = { Text("Name") }
)
}
}
4)函数
repeat
这个函数不仅可以让string重复,还能重复组件
repeat(10) {
Text("Item $it", modifier = Modifier.padding(2.dp))
}
5)测试
8、其他
1)绘图
Canvas(modifier = Modifier.fillMaxSize()){
val canvasWidth = size.width
val canvasHeight = size.height
drawLine(
start = Offset(x=canvasWidth, y = 0f),
end = Offset(x = 0f, y = canvasHeight),
color = Color.Blue
)
}
2)动画
手势
9、遇到的问题
1)预览延迟
我是有延迟,在真机上运行后,编译器的预览颜色才发生改变
或者使用快捷键Build(ctrl+Shift+F5)
2)预览
首先说一个函数内的布局能被预览,函数需要被声明为可组合函数,使用@Composable注释
创建可组合函数,只需将 @Composable 注释添加到函数名称中即可。
首先说一下@Preview这个标签不能给class用,使用是有限制的
@Preview
@Preview(“Light Theme”, widthDp = 720, heightDp = 1080) //第一个是名字