Jetpack Compose(二)-Modifier修饰符

在xml文件中通过各种属性来描述View,在Compose中通过Modifier修饰符来定义UI组件的样式。

在Compose中,每个基础UI组件都有一个Modifier参数,通过定义Modifier来修改组件的样式。

一、常用的修饰符

API 说明
align 设置组件在父容器中的对齐方式。
alpha 设置组件的透明度。
aspectRatio 设置组件的宽高比。
background 设置组件的背景样式。
border 设置组件的边框样式。
clickable 设置组件可点击。
clip 设置组件的裁剪效果。
clipToBounds 设置组件是否裁剪超出边界的内容。
fillMaxHeight 将组件的高度设置为最大可用空间。
fillMaxSize 将组件的尺寸设置为最大可用空间。
fillMaxWidth 将组件的宽度设置为最大可用空间。
focusRequester 设置组件的焦点请求器。
height 设置组件的固定高度。
indication 设置组件的触摸反馈效果。
layout 设置组件的自定义布局规则。
offset 设置组件的偏移量。
padding 设置组件的内边距。
pointerInput 设置组件的指针输入处理。
requiredHeight 设置组件的最小高度。
requiredSize 设置组件的最小尺寸。
requiredWidth 设置组件的最小宽度。
rotate 设置组件的旋转角度。
scale 设置组件的缩放比例。
shadow 设置组件的阴影效果。
size 设置组件的尺寸。
swipeable 设置组件可滑动。
testTag 为组件设置测试标签。
weight 设置组件在父容器中的权重。
width/height 设置组件的固定宽高度。
zIndex 设置组件的堆叠顺序。
draggable 获取组件单向的拖拽的偏移量

二、常用修饰符用法示例

1、Modifier.size

Image(

    painter = painterResource(id = R.mipmap.rabit),
    contentDescription = "图片描述",
    modifier = Modifier


        //.size(150.dp)       <--------这个也行,下面是重载方法
        .size(width = 150.dp, height = 150.dp)
        .clip(CircleShape)



)

宽高150dp的圆角图片就实现了

lADPJxf-2uVMYR3NBLDNAhw_540_1200.jpg

如果想要设置宽度为屏幕宽度,高度为300dp,应该如何写?
方式一:

Box(







    modifier = Modifier







        .fillMaxWidth()


        .height(300.dp)

        .background(Color.Blue)
) { }

方式二:

import androidx.compose.ui.platform.LocalConfiguration



//获取屏幕宽度的dp值
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
Box(
    modifier = Modifier
        .size(height = 300.dp, width = screenWidth)
        .background(Color.Blue)
) { }

2、Modifier.background

backgroud修饰符用来为被修饰组件添加背景色。背景色支持设置color的纯色背景,也可以使用brush设置渐变色背景。

Row {
    //Box1
    Box(
        modifier = Modifier
            .size(150.dp)
            .background(color = Color.Blue)             <--------纯色背景
    ) {
        Text(text = "纯色", Modifier.align(Alignment.Center), color = Color.White)
    }
    // 增加水平间距
    Spacer(modifier = Modifier.width(50.dp))
    //Box2
    Box(

        modifier = Modifier


            .size(150.dp)
            .background(              <--------渐变色背景
                brush = Brush.horizontalGradient(    //创建Brush水平方向的线性渐变色
                    listOf(
                        Color.Cyan,
                        Color.Blue,
                        Color.Green
                    )
                )
            )
    ) {
        Text(text = "渐变色", Modifier.align(Alignment.Center), color = Color.White)
    }
}

运行在手机上的效果

lADPJwY7Yc_nrbzNCWDNBDg_1080_2400.jpg

xml中View的background属性可以设置图片格式的背景,Compose的background修饰符只能设置颜色背景,图片背景需要使用其他组件实现。下面是一些示例:

方式一:

Box(







    modifier = Modifier







        .fillMaxWidth()


        .height(300.dp)

        .paint(painterResource(id = R.mipmap.rabit), contentScale = ContentScale.Fit)   <----背景图
) { }

方式二:

@Composable


fun ShowImage() {

    // 获取屏幕的参数
    val density = LocalDensity.current.density      //屏幕密度
    val screenWidthDp = LocalConfiguration.current.screenWidthDp.dp   //屏幕宽度的dp值
    val screenHeightDp = LocalConfiguration.current.screenHeightDp.dp  //屏幕高度的dp值
    val screenWidthPx = LocalConfiguration.current.screenWidthDp.dp.value * density  //屏幕宽度的px值
    val screenHeightPx = LocalConfiguration.current.screenHeightDp.dp.value * density //屏幕高度的px值

    //将资源图片转化为ImageBitmap
    val option = BitmapFactory.Options().apply {
        inPreferredConfig = Bitmap.Config.ARGB_8888
    }
    val imageBitmap =
        BitmapFactory.decodeResource(LocalContext.current.resources, R.mipmap.rabit, option)
            .asImageBitmap()

    Box(
        modifier = Modifier
            .background(Color.Green)
            .width(screenWidthDp)
            .height(screenHeightDp) // 设置Box组件宽高为屏幕的宽高
            .drawBehind {
                drawImage(        <-------绘制背景
                    imageBitmap,
                    srcOffset = IntOffset.Zero,
                    srcSize = IntSize(imageBitmap.width, imageBitmap.height),   //绘制的图片大小
                    dstOffset = IntOffset.Zero,
                    dstSize = IntSize(     //图片宽度为屏幕宽度,高度为屏幕高度的一半
                        screenWidthPx.toInt(),             <---------注意这里容易错
                        (screenHeightPx / 2F).toInt()      <---------注意这里容易错
                    )
                )
            }
    ) { }
}

UI上显示的效果,图片被拉伸占满了一半的屏幕

lADPKHe21Mqy8GbNBLDNAhw_540_1200.jpg

Compose中提供了获取dp的方法,也把Dp作为参数传递,所以有些地方dp和px的使用容易混淆。

3、Modifier.fillMaxSize

Modifier.fillMaxSize为占满父布局,占满父布局宽高度还有fillMaxWidthfillMaxHeight

4、Modifier.border、Modifier.padding

border用来为被修饰组件添加边框。边框可以指定颜色、粗细,以及通过Shape指定形状,比如圆角矩形等。padding用来为被修饰组件增加间隙。

@Composable


fun ShowImage() {

    val avatarSize = 200.dp  //头像的尺寸
    Box(
        modifier = Modifier
            .border(
            2.dp,     //边框宽度
            Color.Blue,   //边框颜色
            shape = RoundedCornerShape(avatarSize / 2))  //边框圆角,也可以给50表示50%
    ) {
        Image(
            painter = painterResource(id = R.mipmap.rabit2),
            contentDescription = null,
            modifier = Modifier
                .size(avatarSize)
                .padding(2.dp)     //向外加一个padding的宽度,不遮挡头像
                .clip(CircleShape)
        )
    }
}

UI效果

lADPKHCb14GBnhLNBLDNAhw_540_1200.jpg

注意padding()方法不是覆盖关系,而是叠加关系,看下面的简单示例:

//用手机运行,试试修改下面的数字就能在手机上直接预览,而不用重新运行
Box(
    modifier = Modifier

        .background(Color.Black)
        .padding(50.dp)
        .border(10.dp, Color.Blue, shape = RoundedCornerShape(10.dp))
        .padding(50.dp)
) {}

UI效果

lADPJv8gZJENvXvNCWDNBDg_1080_2400.jpg

同时要注意padding对background的影响。

相对于传统布局有Margin和Padding之分,Compose中只有padding这一种修饰符,概念更加简洁。

5、Modifier.offset

组件偏移

Box(







    modifier = Modifier







        .size(150.dp)
        .background(Color.Black)   //背景黑色
        .offset(15.dp,25.dp)    //默认在原点,这里设置一定的偏移量
        .background(Color.Green)   //背景绿色
) {}

UI效果

lADPKHtEU3CDOzDNCWDNBDg_1080_2400.jpg

Modifier调用顺序会影响最终UI呈现的效果,先使用background设置背景,再使用offset修饰符偏移,再使用background绘制背景,会发现呈现的结果可能跟我们想象中不一样。

6、Modifier.align

设置组件在父容器中的对齐方式。

Column(

    modifier = Modifier







        .background(Color.Green)
        .fillMaxWidth()

        .height(300.dp),
    verticalArrangement = Arrangement.Center    //父容器指定子组件的对齐方式为垂直方向上居中
) {
    Text(
        text = "Hello Android",
        style = TextStyle(fontSize = 18.sp, color = Color.Black),
        modifier = Modifier.align(Alignment.CenterHorizontally)  //子组件设置在父容器中的对齐方式为水平居中
    )
}

Tips: Compose中没有类似TextView的gravity="center"的属性,如果想让文本居中只能包一层。

Box(







    modifier = Modifier.size(200.dp),
    contentAlignment = Alignment.Center
) {
    Text(text = "Hello Android")
}

7、Modifier.aspectRatio

用于设置组件的宽高比。做图片适配就很简单,比如我们想让图片宽度占满屏幕的宽度,宽高比为1:1,那么就有了如下的代码:

Image(

    painter = painterResource(id = R.mipmap.rabit2),
    contentDescription = null,
    modifier = Modifier


        .fillMaxWidth()    //宽度为屏幕宽度
        .aspectRatio(1 / 1F)   //宽高比1:1
)

UI效果

lADPJxRxXHP5utXNBLDNAhw_540_1200.jpg

8、Modifier.clickable

组件的点击事件

Box(







    modifier = Modifier.clickable {
        //点击事件
    }

) { }

9、Modifier.clip

用于指定对组件进行剪裁的形状。Modifier.clip 接受一个 Shape 参数,表示要剪裁的形状。Compose 提供了多种内置的 Shape 类型,如 RoundedCornerShape(圆角矩形)CutCornerShape(裁剪角矩形)CircleShape(圆形)等。

Modifier
    .clip(RoundedCornerShape(15.dp))   //圆角
    .clip(RoundedCornerShape(50))   //圆形,50为百分百
    .clip(CircleShape)   //上面的快捷变量
    .clip(CutCornerShape(topStart = 5.dp, topEnd = 5.dp, bottomStart = 2.dp, bottomEnd = 2.dp))  //指定每个角切割圆角

10、Modifier.focusRequester

用于在可交互的组件上请求焦点。通常,这个修饰符用于处理键盘焦点和键盘输入的交互。可以通过创建一个 FocusRequester 对象来使用 Modifier.focusRequester,然后将其传递给需要请求焦点的组件上。

下面是一个有点复杂的例子,进入页面TextField(相当于View中的EditText)显示光标并弹出软键盘,点击按钮,去掉光标并隐藏软键盘。代码如下:

@OptIn(ExperimentalComposeUiApi::class)
@Composable

fun Test() {
    //输入框输入的内容
    var text by remember { mutableStateOf("") }
    //切换焦点
    val focusRequester = remember { FocusRequester() }
    //焦点管理
    val focusManager = LocalFocusManager.current
    //键盘控制器
    val keyboardController = LocalSoftwareKeyboardController.current




    Column(modifier = Modifier.fillMaxSize()) {
        //TextField:相当于View中的EditText
        TextField(
            value = text,
            onValueChange = { text = it },
            modifier = Modifier
                .focusRequester(focusRequester) //获取焦点
                .fillMaxWidth(),
            keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
            keyboardActions = KeyboardActions(
                onDone = {
                    //点击键盘上确认按钮时隐藏键盘
                    keyboardController?.hide()
                }
            ),
            label = { Text(text = "进入页面获取焦点并弹出软键盘") }
        )

        //按钮
        Button(
            onClick = {
                //清除光标
                focusManager.clearFocus()
                //隐藏软键盘
                keyboardController?.hide() //键盘上点击确认按钮时隐藏键盘
            }, modifier = Modifier.padding(16.dp)
        ) {
            Text("点击按钮清除焦点并隐藏软键盘")
        }

        //界面重组异步回调
        LaunchedEffect(key1 = Unit) {
            focusRequester.requestFocus() //首次进入和重组页面请求焦点
            keyboardController?.show() //首次进入页面弹出键盘,注意必须先获取焦点才能弹出键盘成功
        }
    }
}

UI效果如下

lADPD3Ir4lLRSJXNBLDNAhw_540_1200.jpg

11、Modifier.indication

用于指定组件(Clickable, Focusable, SemanticsPropertyProvider 等)的交互指示器。

交互指示器是一个视觉效果,当用户与组件进行交互(点击、获取焦点等)时,可以显示在组件周围,以提供反馈和视觉指示。

看下面的一个例子:

Box(







    modifier = Modifier







        .fillMaxSize()

        .background(Color.Blue)
        .clickable {  }
        .indication(
            interactionSource = remember { MutableInteractionSource() }, //创建一个交互源,用于跟踪用户与组件的交互
            indication = rememberRipple()   //用于定义组件的交互指示器效果
        )
) {}

rememberRipple()为系统提供的波纹指示器。点一下有水波纹效果,这个大家应该不陌生。

12、Modifier.layout

Modifier.layout 是一个很强大的 Compose API,它可以以更精确的方式控制布局和位置。通过 Modifier.layout,可以指定组件的位置和大小,以及在父容器中的摆放方式。

Modifier.layout 接受一个 lambda 表达式,该表达式包含二个参数,其中measurable包含了组件的测量信息。通过 Measurable,可以获取组件的实际大小并设置新的布局约束。

val screenWidthDp = LocalConfiguration.current.screenWidthDp.dp   //屏幕宽度的dp值
val screenHeightDp = LocalConfiguration.current.screenHeightDp.dp  //屏幕高度的dp值



Box(
    modifier = Modifier
        .size(screenWidthDp / 2, screenHeightDp / 4)
        .background(Color.LightGray)
) {
    Text(
        text = "Hello Android!",
        style = TextStyle(fontSize = 18.sp, color = Color.White),
        modifier = Modifier
            .layout { measurable, constraints ->
                // 获取组件的实际大小
                val placeable = measurable.measure(constraints)
                // 设置新的布局约束
                layout(placeable.width, placeable.height) {
                    //指定组件在父布局中的位置x,y坐标
                    placeable.placeRelative(100, 100)
                }
            }
            .background(Color.Blue)
    )
}

UI效果

lADPD2eDZmxOdHvNCWDNBDg_1080_2400.jpg

13、Modifier.draggable

单方向上的拖动手势。Google官网有个横向拖拽的例子(网址)

var offsetX by remember { mutableStateOf(0f) }
Text(
    modifier = Modifier

        .fillMaxSize()
        .offset { IntOffset(offsetX.roundToInt(), 0) }
        .draggable(
            orientation = Orientation.Horizontal,    //横向
            state = rememberDraggableState { delta ->
                offsetX += delta   //一直重置偏移量的值
            }
        ),
    text = "Drag me!"
)

14、Modifier.pointerInput

用于监听组件的输入性事件,啥叫输入性事件,就是组件监听到的自身单击,双击,长按,拖拽等事件。detectTapGestures监听点击类事件,detectDragGestures监听拖拽类事件。

var tapped by remember { mutableStateOf(false) }



Box(
    modifier = Modifier


        .size(200.dp)
        .background(if (tapped) Color.Blue else Color.Green)
        .pointerInput(Unit) {
            //监测触摸事件
            detectTapGestures(onTap = { offset -> //触摸改变背景色
                tapped = !tapped
            })




            //监测拖拽事件
            detectDragGestures { pointerInputChange, offset ->


            }

        }
)

触摸前
lADPJwY7Yewq4lLNCWDNBDg_1080_2400.jpg

触摸后

lADPKIJfUMsiYk7NCWDNBDg_1080_2400.jpg

detectTapGesturesdetectDragGestures的Api如下:

//按压事件
suspend fun PointerInputScope.detectTapGestures(
    onDoubleTap: ((Offset) -> Unit)? = null,    //双击
    onLongPress: ((Offset) -> Unit)? = null,    //长按
    onPress: suspend PressGestureScope.(Offset) -> Unit = NoPressGesture,    //按压
    onTap: ((Offset) -> Unit)? = null     //单击 
) = coroutineScope {...}



//拖拽事件
suspend fun PointerInputScope.detectDragGestures(
    onDragStart: (Offset) -> Unit = { },   //开始拖拽
    onDragEnd: () -> Unit = { },     //结束拖拽
    onDragCancel: () -> Unit = { },    //取消拖拽
    onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit   //正在拖拽
) {...}

Google官网有个跟手拖拽的例子(网址)

Box(modifier = Modifier.fillMaxSize()) {
    var offsetX by remember { mutableStateOf(0f) }
    var offsetY by remember { mutableStateOf(0f) }

    Box(
        Modifier
            .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
            .background(Color.Blue)

            .size(50.dp)
            .pointerInput(Unit) {
                detectDragGestures { change, dragAmount ->
                    change.consume()
                    offsetX += dragAmount.x   //修改偏移的x,y的量
                    offsetY += dragAmount.y
                }
            }

    )
}

Gif效果图

Screen_Recording_20230721_153530_TestCompose.gif

15、Modifier.swiping

可滑动修饰符允许拖动元素,当释放时,这些元素通常会朝着一个方向中定义的两个或多个锚点移动。一个常见的用法是实现一个“滑动到解散”模式。

官网上有一个例子,我在这个例子上修改了一点代码,让它能在手机屏幕的宽度上左右滑动,也是为了进一步理解参数。

@OptIn(ExperimentalMaterialApi::class)
@Composable

fun Greeting() {
    val screenWidthDp = LocalConfiguration.current.screenWidthDp.dp
    val squareSize = 48.dp



    val swipeableState = rememberSwipeableState(0)
    val screenWidthPx = with(LocalDensity.current) { screenWidthDp.toPx() }  //计算像素值
    val squareSizePx = with(LocalDensity.current) { squareSize.toPx() }   //计算像素值
    val anchors = mapOf(0f to 0, screenWidthPx - squareSizePx to 1)   //定义二个锚点,滑块的最左最右的x坐标点


    Box(
        modifier = Modifier
            .fillMaxWidth()
            .swipeable(
                state = swipeableState,
                anchors = anchors,
                thresholds = { _, _ -> FractionalThreshold(0.3f) },   //0.3为阈值,意思是不超过这个阈值就回弹,超过就滑向定义的下一个锚点
                orientation = Orientation.Horizontal
            )
            .background(Color.LightGray)
    ) {
        Box(
            Modifier
                .offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
                .size(squareSize)
                .background(Color.DarkGray)
        )
    }
}

看GIF格式的UI图

Screen_Recording_20230721_153022_TestCompose.gif

16、Modifier.requiredSize、Modifier.requiredHeight、Modifier.requiredWidth

required系列的Api表示必要的尺寸,中国风尺寸是强制性的,如果内容超过会被截断,不会主动适应扩展。

Box(







    modifier = Modifier







        .requiredHeight(38.dp)
        .fillMaxWidth()

        .background(Color.Green)
) {
    Text(text = "测试超出高度".repeat(20))
}

UI效果

lADPJx8Z2G9VxRDNBLDNAhw_540_1200.jpg

17、Modifier.rotate、Modifier.scale

rotate旋转,scale缩放

var rotationAngle by remember { mutableStateOf(0f) }
var scale by remember { mutableStateOf(1f) }



Column(
    horizontalAlignment = Alignment.CenterHorizontally,
    verticalArrangement = Arrangement.Center,
    modifier = Modifier
        .rotate(rotationAngle)   //旋转
        .scale(scale)           //缩放
        .clickable {       //点击按钮旋转45度,缩小1倍
            rotationAngle += 45f
            scale /= 2
        }
) {
    Image(
        painter = painterResource(id = R.mipmap.rabit2),
        contentDescription = null,
        modifier = Modifier
            .fillMaxWidth()
            .aspectRatio(1 / 1F)
    )
}

点击一次后的UI效果

lADPJxuMWcrhYH7NBLDNAhw_540_1200.jpg

18、Modifier.shadow

用于为 Compose 中的组件添加阴影效果。

Column(

    modifier = Modifier







        .fillMaxSize()

        .padding(20.dp)
) {

    Box(

        modifier = Modifier

            .size(200.dp)
            .shadow(5.dp, shape = RoundedCornerShape(16.dp))   //阴影效果
            .background(Color.White),    //需要添加背景,不添加背景阴影效果会有问题
        contentAlignment = Alignment.Center
    ) {
        Text(
            "Hello Android!",
            style = TextStyle(fontSize = 16.sp, color = Color.Black)
        )
    }
}

添加背景的UI效果(图一)和不添加背景的UI效果(图二)

lADPKHtEU4PPQz3NCWDNBDg_1080_2400.jpg

lADPJxDj3dy4Q0fNCWDNBDg_1080_2400.jpg

19、Modifier.weight

子组件在父组件的权重

Row(
    modifier = Modifier







        .fillMaxWidth()


        .height(100.dp)
) {

    Box(

        modifier = Modifier

            .background(Color.Blue)

            .fillMaxHeight()
            .weight(2f)
    ) {}




    Box(

        modifier = Modifier


            .background(Color.Green)
            .fillMaxHeight()
            .weight(1f)
    ) {}
}

UI效果图

lADPD26eY7k4qG7NCWDNBDg_1080_2400.jpg

20、Modifier.zIndex

设置组件的层级顺序,较大值的组件会绘制在较小值的组件之上。下面的代码如果没有设置zIndexButton2应该绘制在Button1之上,但是因为设置了zIndexButton1绘制在了Button2之上。

Box(







    modifier = Modifier.fillMaxWidth()
) {
    Button(
        onClick = { },
        modifier = Modifier
            .zIndex(2f)
    ) {
        Text("Button1")
    }


    Button(
        onClick = { },
        modifier = Modifier


            .zIndex(1f)
    ) {
        Text("Button2")
    }
}

UI效果

lADPKG0OWPIGcCXNCWDNBDg_1080_2400.jpg

三、作用域限定Modifier修饰符

Compose充分发挥了Kotlin的语法特性,让某些Modifier修饰符只能在特定作用域中使用,有利于类型安全地调用它们。所谓的“作用域”,在Kotlin中就是一个带有Receiver的代码块。例如Box组件参数中的conent就是一个Reciever类型为BoxScope的代码块,因此其子组件都处于BoxScope作用域中。

@Composable


inline fun Box(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    propagateMinConstraints: Boolean = false,
    content: @Composable BoxScope.() -> Unit   //BoxScope作用域
) {...}

需要注意Reciever类型默认可以跨层级访问。例如下面的例子中,bScope{...}处于aScope{...}内部,可以在bScope{...}中访问到属于aScope{...}的方法methodFromAScope()

aScope{
    bScope{
        methodFromAScope()   //aScope作用域的方法
    }

}

ComposeDSL中,一般只需要调用当前作用域的方法,像上面这样的Receiver跨级访问会成为写代码时的“噪声”,加大出错的概率。Compose考虑到了这个问题,可以通过@LayoutScopeMarker注解来规避Receiver的跨级访问。常用组件Receivier作用域类型均已使用@LayoutScopeMarker注解进行了声明。

//例如Column的作用域ColumnScope
Column() {...}



@LayoutScopeMarker     <------注解
@Immutable
@JvmDefaultWithCompatibility
interface ColumnScope {...}

四、Modifier实现原理浅析

Modifier会由于调用顺序不同而产生出不同的Modifier链,Compose会按照Modifier链来顺序完成页面测量布局与渲染。那么Modifier链是如何被构建并解析的呢?

从源码中我们发现Modifier实际是一个接口。它有三个具体实现,分别是一个Modifier伴生对象,Modifier. Element以及CombinedModifier

@Suppress("ModifierFactoryExtensionFunction")
@Stable
@JvmDefaultWithCompatibility
interface Modifier {
    fun <R> foldIn(initial: R, operation: (R, Element) -> R): R



    fun <R> foldOut(initial: R, operation: (Element, R) -> R): R



    fun any(predicate: (Element) -> Boolean): Boolean

    fun all(predicate: (Element) -> Boolean): Boolean




    infix fun then(other: Modifier): Modifier =
        if (other === Modifier) this else CombinedModifier(this, other)


    /**
     * A single element contained within a [Modifier] chain.
     */
    @JvmDefaultWithCompatibility
    interface Element : Modifier {...}    //Modifier. Element
    
    ...略...

    // The companion object implements `Modifier` so that it may be used as the start of a
    // modifier extension factory expression.   //Modifier伴生对象,调用的起点
    companion object : Modifier {
        override fun <R> foldIn(initial: R, operation: (R, Element) -> R): R = initial
        override fun <R> foldOut(initial: R, operation: (Element, R) -> R): R = initial
        override fun any(predicate: (Element) -> Boolean): Boolean = false
        override fun all(predicate: (Element) -> Boolean): Boolean = true
        override infix fun then(other: Modifier): Modifier = other
        override fun toString() = "Modifier"
    }
}

class CombinedModifier(      //CombinedModifier
    internal val outer: Modifier,
    internal val inner: Modifier
) : Modifier {...}

Modifier伴生对象是我们对Modifier修饰符进行链式调用的起点,即Modifier.xxx()中开头的那个ModifierCombinedModifier用于连接Modifier链中的每个Modifier对象。Modifier. Element代表具体的修饰符。当我们使用Modifier.xxx()时,其内部实际上会创建一个Modifier实例。以size为例,其内部会创建SizeModifier实例,并使用then进行连接。

@Stable
fun Modifier.size(size: Dp) = this.then(
    //创建SizeModifier对象
    SizeModifier(...)
)



//连接不同的Modifier
infix fun then(other: Modifier): Modifier =
    if (other === Modifier) this else CombinedModifier(this, other)

我们创建的各种Modifier本质上都是一个Modifier. Element。像LayoutModifier这类直接继承自Modifier. Element的接口。

@JvmDefaultWithCompatibility
interface LayoutModifier : Modifier.Element {...}

其他的还有DrawModifierFocusEventModifierPointerInputModifier等等,正是通过它们的组合形成了我们的UI界面要素。

Compose在绘制UI时,会遍历Modifier链获取配置信息。Compose使用foldOut()foldIn()遍历Modifier链,链上的所有节点被“折叠”成一个结果后,传入视图树用于渲染。

fun <R> foldIn(initial: R, operation: (R, Element) -> R): R



fun <R> foldOut(initial: R, operation: (Element, R) -> R): R

foldInfoldOut的方法相同:initial是折叠计算的初始值,operation是具体计算方法。Element参数表示当前遍历到的Modifier,返回值也是R类型,表示本轮计算的结果,会作为下一轮R类型参数传入。folInfoldOut的遍历顺序有所不同,foldIn()代表从正向遍历,而foldOut是反向遍历。

学习笔记

作为初学者,难免有疏漏或错误,欢迎批评指正。文中部分内容参考了以下资料:
Jetpack Compose博物馆

实体书 Jetpack Compose从入门到实战

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY1GQHot' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片