详解Jetpack Compose中的Modifier修饰符
前言
本文将会介绍Jetpack Compose中的Modifier。在谷歌官方文档中它的描述是这么一句话:Modifier元素是一个有序、不可变的集合,它可以往Jetpack Compose UI元素中添加修饰或者各种行为。例如,背景、填充和单击事件监听器装饰或添加行为到文本或按钮。
Modifier修饰符的作用是快速的修改组件的显示大小、边距、边框、背景颜色、剪裁、点击等等,Modifier在Compose的全部组件上都有存在,需要熟练的掌握。另外Modifier是可以被扩展函数扩展的,所以此篇博客只举例通用的Modifier,不举例只在某些组件下才能使用的Modifier修饰符(例如Box(align属性)与constraintlayout(constrainAs方法))。
Modifier 可以通过链式调用的写法来为组件应用一系列的样式,每个基础的 Composable 组件都有一个 modifier 属性。
有序性
官方对修饰符定义的这个特性包含两个层面的意思,一是修饰符的使用是链式的它是有先后排列顺序的,二是这些顺序不仅仅是排列上的先后同时也是属性对UI展示效果起作用的先后顺序。接下来举一个例子来说明修饰符的有序的特性。
Box {val padding = 16.dpval onClick = Unit;Box(Modifier.clickable(onClick = { onClick }).padding(padding).align(Alignment.Center).size(50.dp, 50.dp).background(Color.Green))
}
Box {val padding = 16.dpval onClick = Unit;Box(Modifier.padding(padding).clickable(onClick = { onClick }).align(Alignment.Center).size(50.dp, 50.dp).background(Color.Green))
}
上面两段代码的意思都是在一个绿色背景宽高为50,50的Box控件中设置点击事件和Padding。首先我们可以直观的看到修饰符的使用是链式的符合咱们说的第一个有序性。可以清楚的看到第一张图波纹是作用范围是涵盖了边距的而第二张水波动画的范围仅仅作用在绿色矩形内部。第一段代码是先设置的点击事件而后设置的边距,而第二段代码是先设置边距而后设置了点击事件,就出现了不同的动画表现。我们通过这个例子就可以直观的看到修饰符的第二个有序性,也就是修饰属性对于UI展示作用的有序性,即属性发生作用的先后顺序和设置顺序是一致的。
不可变性
对于修饰符的不可变性笔者认为就是当设置的属性值确定时,被修饰的UI就确定了不会再变了。下面就展示一个位于屏幕中心,背景为绿色,旋转45度,宽度等比例拉伸2倍的正方形。
Box {Box(Modifier.rotate(45f).scale(2f).align(Alignment.Center).size(50.dp, 50.dp).background(Color.Green))
}
如图:
设置背景
设置背景颜色
@Preview@Composablefun MyText() {Text(text = "你好",color = Color.Gray,modifier = Modifier.background(Color.White))}
效果图:
设置圆角背景
@Preview@Composablefun MyText() {Text(text = "你好",color = Color.Gray,modifier = Modifier.background(Color.White, shape = RoundedCornerShape(10.dp)))}
效果图:
渐变色背景
@Preview@Composablefun MyText() {val colors = listOf(Color(0xFF005599),Color(0xFF3FFFED))Text(text = "你好",color = Color.White,modifier = Modifier.background(brush = Brush.linearGradient(colors),//设置线性渐变效果shape = RoundedCornerShape(10.dp),alpha = 1f)//设置透明度)}
效果图
设置宽高
设置指定宽高
@Preview@Composablefun MyText() {Text(text = "你好",color = Color.White,modifier = Modifier.width(100.dp).height(100.dp))}
效果:
填满宽高
@Preview@Composablefun MyText() {Column(modifier = Modifier.width(100.dp).height(100.dp).background(Color.Black)) {Text(text = "你好",color = Color.Black,modifier = Modifier.background(Color.White).fillMaxSize()//填满宽高)}}
效果图:
自适应子组件高宽
IntrinsicSize.Min 这个是关键
@Preview@Composablefun MyText() {Box(modifier = Modifier.width(IntrinsicSize.Min).height(IntrinsicSize.Min).background(Color.Gray)) {Text(text = "你好世界",modifier = Modifier.width(60.dp))}}
效果图:
按比例填充宽高
@Preview@Composablefun MyText() {Column(modifier = Modifier.width(100.dp).height(100.dp).background(Color.Black)) {Text(text = "你好",color = Color.Black,modifier = Modifier.background(Color.White).fillMaxSize(0.5f)//以50%比例填满宽高)}}
效果:
填满宽度或者高度
@Preview@Composablefun MyText() {Column(modifier = Modifier.width(100.dp).height(100.dp).background(Color.Black)) {Text(text = "你好",color = Color.Black,modifier = Modifier.background(Color.Red).fillMaxWidth() //填满宽度)Text(text = "世界",color = Color.Black,modifier = Modifier.background(Color.Yellow).fillMaxHeight() //填满高度)}}
效果:
按比例填充宽度与高度
@Preview@Composablefun MyText() {Column(modifier = Modifier.width(100.dp).height(100.dp).background(Color.Black)) {Text(text = "你好",color = Color.Black,modifier = Modifier.background(Color.Red).fillMaxWidth(0.5f) //填满宽度)Text(text = "世界",color = Color.Black,modifier = Modifier.background(Color.Yellow).fillMaxHeight(0.5f) //填满高度)}}
效果图:
默认最小宽高
@Preview@Composablefun MyText() {Text(text = "你好",color = Color.Black,modifier = Modifier.background(Color.Red).defaultMinSize(minWidth = 50.dp, minHeight = 50.dp))}
设置边距
@Preview@Composablefun MyText() {Column(modifier = Modifier.width(100.dp).height(150.dp).background(Color.Black)) {Text(text = "A",color = Color.Black,modifier = Modifier.background(Color.Red).padding(5.dp) //设置全部边距)Text(text = "B",color = Color.Black,modifier = Modifier.background(Color.Green).padding(horizontal = 20.dp) //设置横向边距)Text(text = "C",color = Color.Black,modifier = Modifier.background(Color.Yellow).padding(vertical = 10.dp) //设置竖向边距)Text(text = "D",color = Color.Black,modifier = Modifier.background(Color.Blue).padding(0.dp,5.dp,50.dp,0.dp) //设置指定方向边距)}}
效果图:
设置边框
一共有三种重载方法
- fun Modifier.border(border: BorderStroke, shape: Shape = RectangleShape) = border(width = border.width, brush = border.brush, shape = shape)
- fun Modifier.border(width: Dp, color: Color, shape: Shape = RectangleShape) = border(width, SolidColor(color), shape)
- fun Modifier.border(width: Dp, brush: Brush, shape: Shape): Modifier
第一种实现渐变色边框
@Preview@Composablefun MyLayout() {val colors = listOf(Color(0xFF005599), Color(0xFF3FFFED))Column(modifier = Modifier.width(25.dp).height(25.dp).background(Color.White).border( //设置渐变边框border = BorderStroke(1.dp, Brush.linearGradient(colors)), //设置边框粗细与边框渐变色shape = RoundedCornerShape(5.dp) //圆角形状)){ }}
效果图:
第二种实现单色边框
@Preview@Composablefun MyLayout() {Column(modifier = Modifier.width(25.dp).height(25.dp).background(Color.White).border(width = 1.dp, //设置边框粗细color = Color(0xFF000000), //边框颜色shape = CutCornerShape (5.dp,5.dp,5.dp,5.dp) //切角矩形形状)){ }}
效果图:
第三种实现渐变色边框
其实与第一种差不多,只是没有了BorderStroke包装
@Preview@Composablefun MyLayout() {val colors = listOf(Color(0xFF005599), Color(0xFF3FFFED))Column(modifier = Modifier.width(25.dp).height(25.dp).background(Color.White).border(width = 1.dp,brush = Brush.linearGradient(colors),shape = RoundedCornerShape(0.dp,0.dp,5.dp,5.dp))){ }}
效果图:
偏移位置
offset这个偏移是偏移自身这个组件
@Preview@Composablefun MyLayout() {Column(modifier = Modifier.width(100.dp).height(100.dp).background(Color.White)){ Text(text = "你好", color = Color.Black,//相对偏移 这个修改会根据布局方向自动调整水平偏移量(应该是为了阿拉伯语设计的)://当布局方向为从左到右时,正x偏移量会将内容向右移动,当布局方向为从右到左时,正x偏移量会将内容向左移动。modifier = Modifier.offset(x = 20.dp,y = 25.dp))Text(text = "世界", color = Color.Black,//绝对偏移 这个修改将不考虑布局方向:正的x偏移总是将内容向右移动modifier = Modifier.absoluteOffset(x = 45.dp,y = 20.dp))}}
效果图:
缩放
@Preview@Composablefun MyScale() {val scale = remember {mutableStateOf(1.5f)}Column(modifier = Modifier.fillMaxSize().background(Color.Gray)) {//默认Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,)//比例缩放Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.padding(top = 30.dp, start = 30.dp).scale(scale.value) //放大1.5倍)//指定宽高缩放Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.padding(top = 70.dp, start = 90.dp).scale(3f,2f) //宽高缩放)}}
效果图:
旋转
@Preview@Composablefun MyRotate() {val rotate = remember { mutableStateOf(180f) }Column(modifier = Modifier.width(300.dp).height(300.dp).background(Color.Gray)) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.padding(100.dp).rotate(rotate.value) //设置旋转)}}
效果图:
旋转与偏移位置的组合
请注意,Modifier一直强调组合的位置不同会出现不同的效果。如果你想实现旋转与拖动功能,那么旋转一定要在前面,否则会出现拖动的时候,实际移动的X轴与Y轴出现相反的问题。
@Composable
fun rotationAndOffset(){val rotation = remember { mutableStateOf(0f) }val offsetX = remember { mutableStateOf(0f) }val offsetY = remember { mutableStateOf(0f) }AsyncImage(model = if (mIsUrl.value) mUrl.value else mFile.value,contentDescription = null,modifier = Modifier.rotate(rotation.value) //rotate一定要在前面.offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) })
}
输入操作(点击、双击、长按、拖动、触控、滚动)
单指操作_单击、按下、双击、长按、触控
@OptIn(ExperimentalFoundationApi::class)@Preview@Composablefun MyClick() {val isEnableClick = remember {mutableStateOf(true)}Column(modifier = Modifier.width(100.dp).height(150.dp).background(Color.Black)) {//方式一 单击监听Text(text = "A",color = Color.Black,modifier = Modifier.background(Color.White).clickable(isEnableClick.value) { //设置点击Log.e("zh", "A单击")})//方式二 组合性点击Text(text = "B",color = Color.Black,modifier = Modifier.background(Color.White).combinedClickable( //注意!此Api是实验性的onClick = {Log.e("zh", "B单击")},onDoubleClick = {Log.e("zh", "B双击")},onLongClick = {Log.e("zh", "B长按")}))//方式三 输入监听Text(text = "C",color = Color.Black,modifier = Modifier.background(Color.White).pointerInput(Unit) {detectTapGestures(onPress = {Log.e("zh", "C按下")},onDoubleTap = {Log.e("zh", "C双击")},onLongPress = {Log.e("zh", "C长按")},onTap = {Log.e("zh", "C触控")})})}}
多指操作_缩放、旋转、拖动
双指缩放
@Preview@Composablefun MyScale() {val scale = remember {mutableStateOf(1f)}Column(modifier = Modifier.width(300.dp).height(300.dp).background(Color.Gray)) {//多点触控Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.padding(100.dp).scale(scale.value).transformable(state = TransformableState { zoomChange, panChange, rotationChange ->scale.value *= zoomChangeLog.e("zh", "缩放 ${zoomChange}" )Log.e("zh", "坐标 ${panChange}")Log.e("zh", "旋转 ${rotationChange}" )}))}}
效果图:
双指旋转
@Preview@Composablefun MyRotate() {val rotate = remember { mutableStateOf(0f) }Box(modifier = Modifier.width(300.dp).height(300.dp).background(Color.Gray),contentAlignment = Alignment.Center) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.fillMaxSize(0.5f).rotate(rotate.value) //设置旋转.transformable(state = TransformableState { zoomChange, panChange, rotationChange ->Log.e("zh", "缩放 ${zoomChange}" )Log.e("zh", "坐标 ${panChange}")rotate.value += rotationChange //旋转变化Log.e("zh", "旋转 ${rotationChange}" )}))}}
效果图:
双指拖动
@Preview@Composablefun MyOffset() {val offsetX = remember { mutableStateOf(0f) }val offsetY = remember { mutableStateOf(0f) }Box(modifier = Modifier.width(300.dp).height(300.dp).background(Color.Gray),contentAlignment = Alignment.Center) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.fillMaxSize(0.5f).offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) } //设置偏移.transformable(state = TransformableState { zoomChange, panChange, rotationChange ->Log.e("zh", "缩放 ${zoomChange}" )Log.e("zh", "坐标 ${panChange}")offsetX.value += panChange.xoffsetY.value += panChange.yLog.e("zh", "旋转 ${rotationChange}" )}))}}
效果图:
双指多操作组合
同时拖动、旋转、缩放
@Preview@Composablefun MyCombination() {val scale = remember { mutableStateOf(1f) }val offsetX = remember { mutableStateOf(0f) }val offsetY = remember { mutableStateOf(0f) }val rotation = remember { mutableStateOf(0f) }Box(modifier = Modifier.width(300.dp).height(300.dp).background(Color.Gray),contentAlignment = Alignment.Center) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.fillMaxSize(0.5f).graphicsLayer{scaleX = scale.valuescaleY = scale.valuetranslationX = offsetX.valuetranslationY = offsetY.valuerotationZ = rotation.value}.transformable(state = TransformableState { zoomChange, panChange, rotationChange ->scale.value *= zoomChangeoffsetX.value += panChange.xoffsetY.value += panChange.yrotation.value += rotationChange}))}}
效果图:
单指拖动
单方向拖动
如果你发现拖动会超出父类布局,但是你希望不超出父类布局,请参考博客下面的 “让子组件不超出当前父组件范围” 内容
@Preview@Composablefun MyImage() {var offset = remember { mutableStateOf(0f) }Column(modifier = Modifier.width(200.dp).height(200.dp).background(Color.Gray)) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,contentScale = ContentScale.None,modifier = Modifier.draggable(state = rememberDraggableState() { //设置拖动offset.value += it}, orientation = Orientation.Vertical) //设置方向 还能设置横向Orientation.Horizontal.offset { IntOffset(0, offset.value.roundToInt()) })}}
效果图:
双方向拖动
@Preview@Composablefun MyImage() {val offsetX = remember { mutableStateOf(0f) }val offsetY = remember { mutableStateOf(0f) }Column(modifier = Modifier.width(200.dp).height(200.dp).background(color = Color.Gray)) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) }.pointerInput(Unit) {//检测拖动手势detectDragGestures { change, dragAmount ->Log.e("zh", "MyImage:检测拖动 当前指针输入数据 = ${change} ")Log.e("zh", "MyImage:检测拖动 每次偏移量 = ${dragAmount} ")offsetX.value += dragAmount.xoffsetY.value += dragAmount.y}})}}
效果图:
设置可滚动
横竖两个方向的滚动
@Preview@Composablefun MyScroll() {Column(modifier = Modifier.fillMaxSize()) {//Row横向排列滚动Row(modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()) //设置横向滚动) {for (i in 1..10) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,)}}//Column竖向排列滚动Column(modifier = Modifier.padding(10.dp).height(250.dp).verticalScroll(rememberScrollState()) //设置竖向滚动) {for (i in 1..10) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,)}}}
效果图:
监听滚动偏移量、滚动位置与滚动位置跳转操作
@Preview@Composablefun MyScroll() {val state = rememberScrollState()val position = remember {derivedStateOf {state.value}}val scope = rememberCoroutineScope() //这个是在Composable调用协程的一种方式Column() {Text(text = "豫章故郡,洪都新府。星分翼轸,地接衡庐。" +"襟三江而带五湖,控蛮荆而引瓯越。" +"物华天宝,龙光射牛斗之墟;" +"人杰地灵,徐孺下陈蕃之榻。" +"雄州雾列,俊采星驰。" +"台隍枕夷夏之交,宾主尽东南之美。" +"都督阎公之雅望,棨戟遥临;" +"宇文新州之懿范,襜帷暂驻。" +"十旬休假,胜友如云;" +"千里逢迎,高朋满座。" +"腾蛟起凤,孟学士之词宗;" +"紫电青霜,王将军之武库。" +"家君作宰,路出名区;" +"童子何知,躬逢胜饯。",modifier = Modifier.padding(10.dp).width(100.dp).height(100.dp)//监听滚动位置.scrollable(state = rememberScrollableState(consumeScrollDelta = {Log.e("zh", "当前滚动偏移量:${it}")it}), orientation = Orientation.Vertical).verticalScroll(state))Text(color = Color.Red,text = "当前滚动位置${position.value}",)Button(onClick = {scope.launch {state.scrollTo(0)}}) {Text(text = "回到起点")}}}
效果图:
指针输入
指针输入监听,与上面各色方法类似
@Preview@Composablefun MyPointerInput() {Box(modifier = Modifier.width(300.dp).height(300.dp).background(Color.Gray)) {Text(text = "你好",color = Color.Black,modifier = Modifier.align(Alignment.Center).fillMaxSize(0.5f).background(color = Color.Green).pointerInput(Unit) {/*** 下面的只能存在一个手势Gestures方法,多了后续的手势方法会不执行,这边是方便观看全部贴出来*/detectDragGestures { change, dragAmount ->Log.e("zh", "检测拖动手势: change = $change \n dragAmount = $dragAmount")}detectDragGesturesAfterLongPress { change, dragAmount ->Log.e("zh", "检测长按后的拖拽手势: change = $change \n dragAmount = $dragAmount")}detectTapGestures {/*** 检测轻按、双击和长按手势,检测到时分别调用onTap、onDoubleTap和onLongPress。当检测到按压和pressgestuscope时调用onPress。* tryAwaitRelease PressGestureScope。awaitRelease可以用来检测指针何时被释放或手势何时被取消。* 第一个向下的指针和最后一个向上的指针被消耗,* 在长按的情况下,检测到长按后的所有更改都被消耗。*/Log.e("zh", "检测触控: Offset = $it \n")}detectTapGestures(onTap = {Log.e("zh", "单击: Offset = $it \n")},onDoubleTap = {Log.e("zh", "双击: Offset = $it \n")},onLongPress = {Log.e("zh", "长按: Offset = $it \n")},onPress = {Log.e("zh", "按下: Offset = $it \n")})detectTransformGestures { centroid, pan, zoom, rotation ->Log.e("zh", "旋转、平移和缩放的手势检测器: centroid = $centroid \n pan = $pan \n zoom = $zoom rotation = $rotation")}detectHorizontalDragGestures { change, dragAmount ->Log.e("zh", "检测水平拖动手势: change = $change \n dragAmount = $dragAmount")}detectVerticalDragGestures { change, dragAmount ->Log.e("zh", "检测垂直拖动手势: change = $change \n dragAmount = $dragAmount")}})}}
graphicsLayer
graphicsLayer可用于对内容应用效果,如缩放、旋转、不透明度、阴影和剪切。当你的层属性由androidx. composition .runtime. state或动画值支持时,最好使用这个版本,因为读取块中的状态只会导致层属性更新,而不会触发重新组合和重新布局。
@Preview@Composablefun MyGraphicsLayer() {Box(modifier = Modifier.width(300.dp).height(300.dp).background(Color.Gray)) {Text(text = "你好",color = Color.Black,modifier = Modifier.align(Alignment.Center).fillMaxSize(0.5f).background(color = Color.Green).graphicsLayer{//绘制区域的水平比例尺。默认值为“1”scaleX = 1f//绘制区域的垂直比例尺。默认值为“1”。scaleY = 1f//绘制区域的alpha。设置为“1”以外的值将导致绘制的内容是半透明的,设置为“0”将使其完全不可见。默认值为' 1 ',取值范围为' 0 ' ~ ' 1 '。alpha = 1f//该层相对于其左边界的水平像素偏移量。默认值为' 0 '。translationX = 0f//该层相对于其上边界的垂直像素偏移量。默认值为' 0 'translationY = 0f//以像素为单位设置阴影的仰角。默认值为“0”,且不能为负值shadowElevation = 0f//范围阴影颜色ambientShadowColor = Color(0xFFFF0000)//点阴影颜色spotShadowColor = Color(0xFFFF0000)//X轴旋转角度rotationX = 0f//Y轴旋转角度rotationY= 0f//Z轴旋转角度rotationZ = 0f//观察相机视角距离cameraDistance = 0f//旋转or缩放的中心点transformOrigin = TransformOrigin.CentertransformOrigin = TransformOrigin(0.1f,0.1f)//形状shape = RectangleShape//设置为' true '将内容剪辑到[shape]。默认值为' false 'clip = false//渲染效果,比如下面设置的模糊效果renderEffect = BlurEffect(20f, 0.5f, TileMode.Clamp)})}}
裁剪
clip的裁剪
@Preview@Composablefun MyImage() {Column(modifier = Modifier.width(100.dp).height(100.dp).background(Color.White)//clip的剪裁是对当前组件的内部内容进行裁剪,这里使用了CircleShape圆形进行裁剪.//也可以使用RectangleShape 或者 RoundedCornerShape() 矩形进行裁剪.clip(shape = CircleShape)) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.fillMaxSize())}}
效果图:
上面是使用常用形状裁剪,这里在举例一个自定义形状的裁剪方式,如下使用path绘制了一个三角形进行裁剪
/*** 裁剪三角形*/@Preview@Composablefun MyImage() {val customShape = object : Shape {override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline {val path = Path()path.moveTo(size.width/2, 0f)path.lineTo(0f,size.height)path.lineTo(size.width,size.height)path.close()val outline = Outline.Generic(path)return outline}}Column(modifier = Modifier.width(100.dp).height(100.dp).background(Color.White).clip(shape = customShape)) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.fillMaxSize())}}
效果图:
让子组件不超出当前父组件范围
clipToBounds 按照指定的边界裁切内容。强制限制当前布局下的子组件不允许超出到外部。另外这个属性与clip属性无关联。
反面例子代码,下面的代码中注释了clipToBounds
@Preview@Composablefun MyImage() {var offset = remember { mutableStateOf(0f) }//必须在套一个布局设置最大范围的父类布局,以展示下面的Image在拖动的时候超出的效果Column(modifier = Modifier.fillMaxSize().padding(200.dp)) {Column(modifier = Modifier.width(200.dp).height(200.dp).background(Color.Gray) //这里设置背景色为灰色,以观察布局的大小
// .clipToBounds()) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,contentScale = ContentScale.None,modifier = Modifier.draggable(state = rememberDraggableState() {offset.value += it}, orientation = Orientation.Horizontal).offset { IntOffset(offset.value.roundToInt(), 0) })}}}
反面例子效果图,图片在拖动的时候可以超出布局范围:
调用clipToBounds 后的效果图(代码就不重复贴了,直接将clipToBounds 注释取消就行):
模糊
注意!blur这个api需要Android版本12 31 API的设备上才能生效
@Preview@Composablefun MyBlur() {Box(modifier = Modifier.width(300.dp).height(300.dp).background(Color.Gray)) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.align(Alignment.Center)//注意blur这个api需要Android 12 31 API 才能生效.blur(radius = 10.dp, edgeTreatment = BlurredEdgeTreatment.Rectangle) //设置模糊)}}
效果图:
BlurredEdgeTreatment.Rectangle 是矩形边缘清晰中间模糊
BlurredEdgeTreatment.Unbounded 无边界模糊的效果图:
透明度
@Preview@Composablefun MyAlpha() {Box(modifier = Modifier.width(300.dp).height(300.dp).background(Color.Gray)) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.align(Alignment.Center).alpha(0.1f) //设置透明度)}}
效果图:
绘制阴影
注意!shadow这个api需要Android版本10的设备上才能生效
@Preview@Composablefun MyShadow() {Box(modifier = Modifier.width(300.dp).height(300.dp).background(Color.White)) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.align(Alignment.Center).shadow( //设置阴影,调用此api需要设备Android版本为10elevation = 50.dp, //隐藏长度shape = RectangleShape, //阴影形状ambientColor = Color.Red, //环境颜色spotColor = Color.Red, //阴影颜色clip = true //是否跟随裁剪形状改变阴影形状))}}
绘制
drawBehind绘制,将增加的绘制内容显示到组件图层的最下层
@Preview@Composablefun MyDrawBehind() {Box(modifier = Modifier.width(300.dp).height(300.dp).background(Color.White)) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.align(Alignment.Center).drawBehind { //绘制到最下面的图层val radius = size.width / 3val centerOffset = Offset(size.width, 0f)drawCircle(color = Color.Red,radius = radius,center = centerOffset,style = Fill)})}}
在下面的效果图里,可以看到红圈在图片的下层:
drawWithContent 人为控制绘制内容的顺序
@Preview@Composablefun MyDrawWithContent() {Box(modifier = Modifier.width(300.dp).height(300.dp).background(Color.White)) {Image(painter = painterResource(id = R.mipmap.ic_logo),contentDescription = null,modifier = Modifier.align(Alignment.Center).drawWithContent {/*drawContent()是理解drawWithContent的关键drawContent这个是这个组件本身需要绘制的内容。控制这个方法的位置你就可以控制绘制的顺序。*/drawContent()val radius = size.width / 3val centerOffset = Offset(size.width, 0f)drawCircle(color = Color.Red,radius = radius,center = centerOffset,style = Stroke(25f))})}}
因为代码中是先绘制了drawContent()然后在绘制圆环,所以下面的效果图圆环在图片的上层:
drawWithCache 提供缓存数据避免重组的绘制方式
@Preview@Composablefun MyDrawWithCache() {val count = remember { mutableStateOf(0) }Box(modifier = Modifier.width(300.dp).height(300.dp).background(Color.White)) {Text(text = "计数",modifier = Modifier.align(Alignment.Center).clickable { count.value++ }/** drawWithCache 需要配合 onDrawWithContent 或者 onDrawBehind 使用*/.drawWithCache {val x = size.width + 20val y = size.height - 10val paint = Paint()paint.textSize = 50fpaint.textAlign = Paint.Align.CENTERLog.e("zh", "触发重组1 在这个代码块里不会发生重组")onDrawWithContent {Log.e("zh", "触发重组2 在这个代码块里发生重组,但是请注意mutableStateOf的数据也要放到这个代码块中")drawIntoCanvas { canvas ->canvas.nativeCanvas.drawText("${count.value}", x, y, paint)}drawContent()}})}}
效果图:
Painter绘制
绘制图形
@Preview@Composablefun MyPaint() {Box(modifier = Modifier.width(100.dp).height(100.dp).background(Color.White)) {Text(text = "你好",modifier = Modifier.align(Alignment.Center).paint(painter = object : Painter() {override val intrinsicSize: Sizeget() = Size(50f, 50f)override fun DrawScope.onDraw() {drawLine(color = Color.Red,Offset(0f, 0f),Offset(size.width, size.height),strokeWidth = 10f)}}))}}
效果图:
绘制图像
有一些人会利用这个来绘制组件的背景,的确可以但是个人认为不太妥当...
@Preview@Composablefun MyPaint() {Box(modifier = Modifier.width(300.dp).height(300.dp).background(Color.White)) {Text(text = "你好",color = Color.White,modifier = Modifier.align(Alignment.Center).paint(painterResource(id = R.mipmap.ic_logo)))}}
效果图:
组合顺序的影响
Modifier的方法组合顺序是会影响到组件的实际效果的,这里使用两段代码演示(可以根据下面2代码看到点击范围发生了改变):
代码1:
@Preview@Composablefun MyText() {Text(text = "你好世界你好世界",color = Color.Black,modifier = Modifier.background(Color.Green).size(150.dp).clickable { //点击在前Log.e("zh", "MyGraphicsLayer: ")}.padding(50.dp) //padding在后)}
效果图:
代码2:
@Preview@Composablefun MyText() {Text(text = "你好世界你好世界",color = Color.Black,modifier = Modifier.background(Color.Green).size(150.dp).padding(50.dp) //padding在前.clickable { //点击在后Log.e("zh", "MyGraphicsLayer: ")})}
效果图:
多个Modifier组合
下面的代码种,分别创建了backgroundModifier 与 borderModifier, 并且使用then将他们组合
@Preview@Composablefun MyText() {val backgroundModifier = Modifier.background(Color.Green).fillMaxSize(0.2f)val borderModifier = Modifier.border(width = 1.dp, //设置边框粗细color = Color(0xFF000000), //边框颜色shape = RoundedCornerShape(5.dp) //切角矩形形状)Column {Text(text = "A",color = Color.Black,textAlign = TextAlign.Center,modifier = Modifier.then(backgroundModifier).then(borderModifier).clickable {Log.e("zh", "点击A")})Text(text = "B",color = Color.Black,textAlign = TextAlign.Center,modifier = Modifier.then(backgroundModifier).then(borderModifier).clickable {Log.e("zh", "点击B")})}}
end.
详解Jetpack Compose中的Modifier修饰符相关推荐
- Jetpack Compose中的Modifier
Modifier的基本使用 Modifier修饰符是Jetpack Compose中用来修饰组件的,提供常用的属性,写布局时几乎所有Composable组件的大部分属性都可以用Modifier 来修饰 ...
- 随输入动态改变ui_深入详解 Jetpack Compose | 优化 UI 构建
人们对于 UI 开发的预期已经不同往昔.现如今,为了满足用户的需求,我们构建的应用必须包含完善的用户界面,其中必然包括动画 (animation) 和动效 (motion),这些诉求在 UI 工具包创 ...
- Java final修饰符的作用,Java中的final修饰符
1.什么时候可以选择final修饰符 如果想让一个类不被其他类继承,不允许在有子类,这时候就要考虑用到final来修饰. 2.用final修饰的类 首先大家要明白,用final修饰的类是不能被继承的, ...
- JetPack Compose之Modifier修饰符
前言 在Compose中,每一个组件都是带有@Compose注解的函数,被称为Composable.Compose已经预置了很多的Compose UI组件,这些组件都是基于Material Desig ...
- vue输入框输入触发事件_详解.vue文件中监听input输入事件(oninput)
详解.vue文件中监听input输入事件(oninput) .vue文件其实是一个组件,关于它的说明我之前也写过一篇文章,地址:.vue文件,今天这篇文章要讲的是.vue文件中监听input的输入值变 ...
- Jetpack Compose 中的架构思想
Jetpack Compose 中的架构总览 如果应用打算使用 Jetpack Compose 来开发,那么就可以跟以前的MVC.MVP.MVVM等乱七八糟的架构全部说拜拜,这些名词也将在Androi ...
- float属性html,详解CSS样式中的float属性
详解CSS样式中的float属性.float是 css 样式的定位属性.我们在印刷排版中,文本可以按照需要围绕图片.一般把这种方式称为"文本环绕".在网页设计中,应用了CSS的fl ...
- java调用项目中的文件_详解eclipse项目中.classpath文件的使用
1 前言 在使用eclipse或者myeclipse进行java项目开发的时候,每个project(工程)下面都会有一个.classpath文件,那么这个文件究竟有什么作用? 2 作用 .classp ...
- Session机制详解及分布式中Session共享解决方案
Session机制详解及分布式中Session共享解决方案 参考文章: (1)Session机制详解及分布式中Session共享解决方案 (2)https://www.cnblogs.com/jing ...
最新文章
- combo:机器学习模型合并工具库
- linux分区合并不损坏系统,一次Linux磁盘损坏导致系统不可用恢复实例
- Tomcat配置虚拟内存
- javaScript学习笔记之比较运算符||逻辑运算符||条件运算符(三目运算符)
- 剑指 Offer 10- I. 斐波那契数列 (从重叠子问题到备忘录到dp数组迭代解法)
- 计算机专业对口升学模拟试题,2010对口升学模拟试题计算机专理论综合
- 看周志华教授的一番话有感
- rk3288对于parameter参数文件的解析处理【转】
- 数组的常用方法 Array;
- 3.OSPF协议及链路状态算法
- Django中FBV和CBV
- 自学web前端课程大纲分享,适合所有人学习
- 计算机颜色更换,如何给证件照换底色;怎么快速更换证件照底色
- 各种SKYPE网页代码,SKYPE在线代码
- win10怎么在注册表中修改图片查看方式
- linux 安装Docker
- 快速入池淘宝猜你喜欢方法技巧
- 说说共享纸巾的那些事
- HTTP状态码1xx到5xx
- Unity学习 渲染管线介绍