观心静

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

前言

  此篇博客讲解Modifier修饰符的使用,Modifier修饰符的作用是快速的修改组件的显示大小、边距、边框、背景颜色、剪裁、点击、旋转、偏移、滚动、焦点等等,Modifier在Compose的全部组件上都有存在,需要熟练的掌握。另外Modifier是可以被扩展函数扩展的,所以此篇博客只举例通用的Modifier,不举例只在某些组件下才能使用的Modifier修饰符(例如Box(align属性)与constraintlayout(constrainAs方法))。

设置背景

设置背景颜色

代码:

    @Preview
    @Composable
    fun MyText() {
        Text(
            text = "你好",
            color = Color.Gray,
            modifier = Modifier.background(Color.White)
        )
    }

效果图:

设置圆角背景

    @Preview
    @Composable
    fun MyText() {
        Text(
            text = "你好",
            color = Color.Gray,
            modifier = Modifier.background(Color.White, shape = RoundedCornerShape(10.dp))
        )
    }

效果图:

渐变色背景

代码

    @Preview
    @Composable
    fun 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
    @Composable
    fun MyText() {
        Text(
            text = "你好",
            color = Color.White,
            modifier = Modifier
                .width(100.dp)
                .height(100.dp)
        )
    }

效果:

填满宽高

 代码

    @Preview
    @Composable
    fun 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
    @Composable
    fun MyText() {
        Box(
            modifier = Modifier
                .width(IntrinsicSize.Min)
                .height(IntrinsicSize.Min)
                .background(Color.Gray)
        ) {
            Text(
                text = "你好世界",
                modifier = Modifier.width(60.dp)
            )
        }
    }

效果图:

也可以使用wrapContentSize来实现此功能,wrapContentSize会自适应内容大小

Image(painter = painterResource(id = R.mipmap.ic_test_0), contentDescription = null, modifier = Modifier.wrapContentSize())

按比例填充宽高

 代码:

    @Preview
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun MyText() {
        Text(
            text = "你好",
            color = Color.Black,
            modifier = Modifier
                .background(Color.Red)
                .defaultMinSize(minWidth = 50.dp, minHeight = 50.dp)
        )
    }

无视父类容器大小强制设置高度与宽度

requiredHeight与requiredWidth

代码:

Box(modifier = Modifier.fillMaxSize()){
    //父容器只设置100高度
    Row(modifier = Modifier.size(width = 100.dp, height = 100.dp).align(Alignment.Center)) {
        //这边使用requiredHeight并且设置150dp,让下面的子元素突破父容器高度
        Spacer(modifier = Modifier.requiredHeight(150.dp).width(50.dp).background(color = Color.Red))
        //这边同样设置150高度作为对比,实际上这个子元素高度是无法突破父容器的限制
        Spacer(modifier = Modifier.size(width = 50.dp, height = 150.dp).background(color = Color.Blue))
    }
}

效果图:

设置边距

代码

    @Preview
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun 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 *= zoomChange
                        Log.e("zh", "缩放 ${zoomChange}" )
                        Log.e("zh", "坐标 ${panChange}")
                        Log.e("zh", "旋转 ${rotationChange}" )
                    })
            )
        }
    }

效果图:

双指旋转

 代码:

    @Preview
    @Composable
    fun 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
    @Composable
    fun 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.x
                        offsetY.value += panChange.y
                        Log.e("zh", "旋转 ${rotationChange}" )
                    })
            )
        }
    }

效果图:

双指多操作组合

同时拖动、旋转、缩放

代码

@Preview
    @Composable
    fun 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.value
                        scaleY = scale.value
                        translationX = offsetX.value
                        translationY = offsetY.value
                        rotationZ = rotation.value
                    }
                    .transformable(state = TransformableState { zoomChange, panChange, rotationChange ->
                        scale.value *= zoomChange
                        offsetX.value += panChange.x
                        offsetY.value += panChange.y
                        rotation.value += rotationChange
                    })
            )
        }
    }

效果图:

单指拖动

单方向拖动

如果你发现拖动会超出父类布局,但是你希望不超出父类布局,请参考博客下面的 “让子组件不超出当前父组件范围” 内容

    @Preview
    @Composable
    fun 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
    @Composable
    fun 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.x
                            offsetY.value += dragAmount.y
                        }
                    }
            )
        }
    }

效果图:

左右滑动

代码

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun APage() {
    //滑块的宽度
    val slidingBlockWidth = 50.dp
    //滑动的最大宽度
    val bgWidthDp = 300.dp
    //这里是可以滑动的最大距离
    val slidingMaxWidthPx = LocalDensity.current.run { (bgWidthDp - slidingBlockWidth).toPx() }
    //这里创建一个map,map的第一个值是关闭距离位置与对应key,第二个值是开启距离位置与对应key
    val anchors = mapOf(
        0f to "close",
        slidingMaxWidthPx to "open"
    )

    var swipeableState = rememberSwipeableState(initialValue = "close")

    Box(modifier = Modifier
        .width(bgWidthDp)
        .height(slidingBlockWidth)
        .background(Color.Gray)) {

        Box(
            modifier = Modifier
                .offset { //请注意,offset位置一定要在swipeable的上面,否则会出现无法滑动的问题
                    //整体移动
                    IntOffset(swipeableState.offset.value.toInt(), 0)
                }
                .swipeable(
                    state = swipeableState,
                    anchors = anchors,
                    orientation = Orientation.Horizontal, //移动的方向
                    thresholds = { from, to ->
                        //从关闭到开启状态时,滑块移动超过30%距离自动吸附到开启状态
                        if (from == "close") {
                            FractionalThreshold(0.3f)
                        } else {
                            //从开启状态到关闭状态时,滑块移动超过50%才会自动吸附到关闭状态
                            FractionalThreshold(0.5f)
                        }
                    }

                )
                .width(slidingBlockWidth) //这个一定要在swipeable下面
                .height(slidingBlockWidth) //这个一定要在swipeable下面
                .background(Color.Green) //这个一定要在swipeable下面

        )
    }
}

滑动效果图

设置可滚动

横竖两个方向的滚动

代码:

    @Preview
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun 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")
                        }
                    }
            )
        }
    }

点击效果

效果图

代码

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        Row(
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier
                .padding(top = 100.dp)
                .fillMaxSize()
        ) {
            //默认点击效果
            Image(
                painter = painterResource(id = R.mipmap.ic_test_0),
                contentDescription = null,
                modifier = Modifier
                    .clickable {
                        Toast
                            .makeText(this@DemoActivity, "点击0>默认点击效果", Toast.LENGTH_SHORT)
                            .show()
                    })
            
            //涟漪点击效果,关键代码是rememberRipple
            val interactionSource1 = remember {
                MutableInteractionSource()
            }
            Image(
                painter = painterResource(id = R.mipmap.ic_test_1),
                contentDescription = null,
                modifier = Modifier
                    .clickable(onClick = {
                        Toast
                            .makeText(this@DemoActivity, "点击1>涟漪点击效果", Toast.LENGTH_SHORT)
                            .show()
                    }, indication = rememberRipple(), interactionSource = interactionSource1))

            //通过修改背景颜色实现自定义点击效果
            val interactionSource2 = remember {
                MutableInteractionSource()
            }
            val bgColor = if (interactionSource2.collectIsPressedAsState().value) Color.Blue else Color.Transparent
            Image(
                painter = painterResource(id = R.mipmap.ic_test_2),
                contentDescription = null,
                modifier = Modifier
                    .clickable(onClick = {
                        Toast
                            .makeText(
                                this@DemoActivity,
                                "点击2>通过修改背景颜色实现自定义点击效果",
                                Toast.LENGTH_SHORT
                            )
                            .show()
                    }, indication = null, interactionSource = interactionSource2)
                    .background(bgColor))

            //取消点击效果
            val interactionSource3 = remember {
                MutableInteractionSource()
            }
            Image(
                painter = painterResource(id = R.mipmap.ic_test_3),
                contentDescription = null,
                modifier = Modifier
                    .clickable(onClick = {
                        Toast
                            .makeText(this@DemoActivity, "点击3>取消点击效果", Toast.LENGTH_SHORT)
                            .show()
                    }, indication = null, interactionSource = interactionSource3))
        }
    }
}

graphicsLayer 

graphicsLayer可用于对内容应用效果,如缩放、旋转、不透明度、阴影和剪切。当你的层属性由androidx. composition .runtime. state或动画值支持时,最好使用这个版本,因为读取块中的状态只会导致层属性更新,而不会触发重新组合和重新布局。

代码:

    @Preview
    @Composable
    fun 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.Center
                        transformOrigin =  TransformOrigin(0.1f,0.1f)
                        //形状
                        shape = RectangleShape
                        //设置为' true '将内容剪辑到[shape]。默认值为' false '
                        clip = false
                        //渲染效果,比如下面设置的模糊效果
                        renderEffect = BlurEffect(20f, 0.5f, TileMode.Clamp)
                    }
            )
        }
    }

裁剪

clip的裁剪

代码:

    @Preview
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun 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的设备上才能生效

请注意!shadow的使用也是有顺序的,建议放到background前面

代码

    @Preview
    @Composable
    fun 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版本为10
                        elevation = 50.dp, //隐藏长度
                        shape = RectangleShape, //阴影形状
                        ambientColor = Color.Red, //环境颜色
                        spotColor = Color.Red, //阴影颜色
                        clip = true //是否跟随裁剪形状改变阴影形状
                    )
            )
        }
    }

效果图:

绘制

drawBehind绘制,将增加的绘制内容显示到组件图层的最下层

代码:

    @Preview
    @Composable
    fun 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 / 3
                        val centerOffset = Offset(size.width, 0f)
                        drawCircle(
                            color = Color.Red,
                            radius = radius,
                            center = centerOffset,
                            style = Fill
                        )
                    }
            )
        }
    }

在下面的效果图里,可以看到红圈在图片的下层:

 

drawWithContent 人为控制绘制内容的顺序

    @Preview
    @Composable
    fun 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 / 3
                        val centerOffset = Offset(size.width, 0f)
                        drawCircle(
                            color = Color.Red,
                            radius = radius,
                            center = centerOffset,
                            style = Stroke(25f)
                        )
                    }
            )
        }
    }

因为代码中是先绘制了drawContent()然后在绘制圆环,所以下面的效果图圆环在图片的上层:

 

 

drawWithCache 提供缓存数据避免重组的绘制方式

代码

    @Preview
    @Composable
    fun 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 + 20
                        val y = size.height - 10
                        val paint = Paint()
                        paint.textSize = 50f
                        paint.textAlign = Paint.Align.CENTER
                        Log.e("zh", "触发重组1 在这个代码块里不会发生重组")
                        onDrawWithContent {
                            Log.e("zh", "触发重组2 在这个代码块里发生重组,但是请注意mutableStateOf的数据也要放到这个代码块中")
                            drawIntoCanvas { canvas ->
                                canvas.nativeCanvas.drawText("${count.value}", x, y, paint)
                            }
                            drawContent()
                        }
                    }
            )
        }
    }

效果图:

Painter绘制

绘制图形

代码:

    @Preview
    @Composable
    fun 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: Size
                            get() = Size(50f, 50f)

                        override fun DrawScope.onDraw() {
                            drawLine(
                                color = Color.Red,
                                Offset(0f, 0f),
                                Offset(size.width, size.height),
                                strokeWidth = 10f
                            )
                        }
                    })
            )
        }
    }

效果图:

 

绘制图像

有一些人会利用这个来绘制组件的背景,的确可以但是个人认为不太妥当...

代码:

    @Preview
    @Composable
    fun 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))
            )
        }
    }

效果图:

焦点监听

下面使用TextField输入框来监听焦点的获取

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        Box(modifier = Modifier.fillMaxSize()){
            TextField(value = "输入框", onValueChange = {}, modifier = Modifier.onFocusChanged {
                val text = if (it.isFocused) "获得焦点" else "失去焦点"
                Log.e("zh", "${text}")
            }.align(Alignment.Center))
        }
    }
}

焦点的获取与清除

效果图

代码

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        val hasFocus = remember { mutableStateOf(false) }
        val focusRequester = remember { FocusRequester() }
        val focusManager = LocalFocusManager.current
        Box(modifier = Modifier.fillMaxSize()){
            TextField(value = "输入框", onValueChange = {}, modifier = Modifier
                .focusRequester(focusRequester)
                .onFocusChanged {
                    hasFocus.value = it.isFocused
                    val text = if (it.isFocused) "获得焦点" else "失去焦点"
                    Log.e("zh", "${text}")
                }
                .align(Alignment.Center))
            //这边创建按键来控制上面输入框的焦点获取
            Button(
                modifier = Modifier.padding(bottom = 120.dp).align(Alignment.Center),
                onClick = {
                    if (!hasFocus.value) {
                        focusRequester.requestFocus() //获取焦点
                    } else{
                        focusManager.clearFocus() //清空焦点
                    }
                }
            ) {
                Text(if (!hasFocus.value) "请求焦点" else "取消焦点")
            }
        }
    }
}

自定义焦点选中效果

代码

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        val interactionSource = remember { MutableInteractionSource() }
        val bg1Color = remember { mutableStateOf(Color.Transparent) }
        //collectIsFocusedAsState为焦点状态
        if (interactionSource.collectIsFocusedAsState().value){
            bg1Color.value = Color(0xFF6FFFFF)
        } else {
            bg1Color.value = Color.Transparent
        }

        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier
                .padding(top = 100.dp)
                .fillMaxSize()
        ) {
            Text(
                text = "测试1",
                fontSize = 20.sp,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .size(100.dp, 100.dp)
                    .background(color = bg1Color.value, shape = RoundedCornerShape(40.dp))
                    //需要在clickable上设置interactionSource与indication
                    .clickable(
                        interactionSource = interactionSource,
                        indication = null,
                        onClick = {

                        })
            )
            //默认的自带焦点,作为对比
            Text(
                text = "测试2",
                fontSize = 20.sp,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .size(100.dp, 100.dp)
                    .clickable(onClick = {

                    })
            )
        }
    }
}

效果图

禁用焦点功能

代码

setContent {
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier
            .padding(top = 100.dp)
            .fillMaxSize()
    ) {
        Text(text = "测试2", textAlign = TextAlign.Center,modifier = Modifier.size(100.dp, 100.dp).focusProperties{
            //canFocus设置为false可以禁用焦点
            canFocus = false
        }.clickable {
        })
    }
}

组合顺序的影响

 Modifier的方法组合顺序是会影响到组件的实际效果的,这里使用两段代码演示(可以根据下面2代码看到点击范围发生了改变):

代码1:

    @Preview
    @Composable
    fun 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
    @Composable
    fun 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
    @Composable
    fun 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")
                    }
            )

        }
    }

扩展Modifier函数

kotlin是支持扩展函数的,所以Modifier也能进行函数扩展,使其可以进行一些风格与功能封装,减少重复代码。

组件风格扩展的例子(一个简单的例子)

假设,现在有一个文本风格有统一的背景与边框,现在希望减少重复代码,我们就可以以下面的方式实现Modifier扩展函数,达到随处使用的目的。

扩展函数代码

then是接续函数,代表myStyle这个函数还可以拼接其他的Modifier的函数

fun Modifier.myStyle() = this.then(
    this.padding(10.dp)
    .background(color = Color.DarkGray, shape = RoundedCornerShape(5.dp))
    .border(5.dp, Color.Red)
)

使用函数代码

Column(
    horizontalAlignment = Alignment.CenterHorizontally,
    modifier = Modifier
        .padding(top = 100.dp)
        .fillMaxSize()
) {
    Text(
        text = "测试1",
        fontSize = 20.sp,
        textAlign = TextAlign.Center,
        modifier = Modifier
            .size(100.dp, 100.dp)
            .myStyle()
    )
    Text(
        text = "测试2",
        fontSize = 20.sp,
        textAlign = TextAlign.Center,
        modifier = Modifier
            .size(100.dp, 100.dp)
            .myStyle()
    )
}

效果图

扩展支持compose的例子

上面的例子非常简单,只是封装了常规的Modifier函数。 但是实际开发中,我们有需求封装各色状态,逻辑等等情况,以达到扩展实现动画效果、焦点选中、按下效果的功能。

实现按下、焦点、鼠标悬浮组件缩放的扩展例子

扩展函数代码

下面关键的是composed函数,这个函数可以让以在compose函数里一样调用,remember状态管理、附带效应等等代码

/**
 * 点击和焦点、鼠标悬停的缩放效果
 */
fun Modifier.clickAndFocusHoveredScale(focusedAndHoveredScale: Float, pressedScale: Float, onClick: () -> Unit) =
    this.composed(
        factory = {
            //创建一个缩放效果
            val scale = remember {
                mutableStateOf(1f)
            }
            val interactionSource = remember {
                MutableInteractionSource()
            }
            if (interactionSource.collectIsFocusedAsState().value || interactionSource.collectIsHoveredAsState().value) {
                //当正在焦点状态与鼠标悬停状态时,这里改变scale的数值
                scale.value = focusedAndHoveredScale
            } else if (interactionSource.collectIsPressedAsState().value) {
                //当按下状态时,这里改变scale的数值
                scale.value = pressedScale
            } else {
                //没有状态默认恢复原来的大小
                scale.value = 1.0f
            }
            return@composed Modifier
                .scale(scale.value)
                .clickable(interactionSource, indication = null, onClick = onClick)
        },
    )

使用函数代码

Column(
    horizontalAlignment = Alignment.CenterHorizontally,
    modifier = Modifier
        .padding(top = 100.dp)
        .fillMaxSize()
) {
    (0..5).forEach {
        Text("${it}", color = Color.White, modifier = Modifier.padding(10.dp)
            .size(50.dp)
            .clickAndFocusHoveredScale(1.3f, 0.7f) {
                    //点击

            }
            .background(color = Color.DarkGray)
        )
    }
}

 效果图

 

End

posted on 2022-10-19 19:29  观心静  阅读(714)  评论(0编辑  收藏  举报