compose--CompositionLocal、列表LazyColumn&LazyRow、约束布局ConstraintLayout

2022-12-11 09:20:01 浏览数 (1)

通过前面内置组件和修饰符Modifier的使用,结合Stat状态,相信对于一般的开发需求已经没有问题了,接下来对CompositionLocal进行学习,以及对列表组件LazyColumn&LazyRow和约束布局的完善ConstraintLayout

一、CompositionLocal

CompositionLocal可以创建以树为作用域的具名对象,简单来说就是可组合函数的作用域内,其所有的内容组件都可以隐式的拿到和修改CompositionLocal中的内容,针对组件的颜色、样式等属性值,他们往往按照一套风格来设计,使用隐式调用更加合适

1.MaterialTheme主题

之前我们在使用一些ShapeColorTextStyle时,用到了MaterialThemeshapescolorstypography获取,这些都是CompositionLocal对象

创建项目时,也会自动帮助我们创建一个主题:

代码语言:javascript复制
private val DarkColorScheme = darkColorScheme(
    primary = Purple80,
    secondary = PurpleGrey80,
    tertiary = Pink80
)

private val LightColorScheme = lightColorScheme(
    primary = Purple40,
    secondary = PurpleGrey40,
    tertiary = Pink40

    /* Other default colors to override
    background = Color(0xFFFFFBFE),
    surface = Color(0xFFFFFBFE),
    onPrimary = Color.White,
    onSecondary = Color.White,
    onTertiary = Color.White,
    onBackground = Color(0xFF1C1B1F),
    onSurface = Color(0xFF1C1B1F),
    */
)

@Composable
fun MyComposeApplicationTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12 
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }
    val view = LocalView.current
    if (!view.isInEditMode) {
        SideEffect {
            (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb()
            ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
        }
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}

官方也推荐使用md风格,也就是使用预设的几种颜色,尺寸等对组件进行样式的选择,并且整体APP遵循md风格进行设计

在项目中,直接使用定义的Theme主题包含compose组件,即可获取md风格的样式,以及深色与浅色主题的切换:

代码语言:javascript复制
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyComposeApplicationTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    MyScaffold()
                }
            }
        }
    }
}

效果:

2.CompositionLocalProvider

CompositionLocalProvider可以临时改变CompositionLocal的属性值,如果你想要对某些组件的样式进行特殊处理,推荐使用CompositionLocalProvider,此改变只争对该作用域内的组件:

代码语言:javascript复制
@Preview
@Composable
fun MyCompositionLocalProvider() {
    MyComposeApplicationTheme { // MaterialTheme sets ContentAlpha.high as default
        Column {
            Text("Uses MaterialTheme's provided alpha")
            CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)) {
                Text("Medium value provided for LocalContentAlpha")
                Text("This Text also uses the medium value")
                CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)) {
                    DescendantExample()
                }
            }
        }
    }
}

@Composable
fun DescendantExample() {
    // CompositionLocalProviders also work across composable functions
    Text("This Text uses the disabled alpha now")
}

效果:

3.自定义CompositionLocal

我们也可以为组件自定义CompositionLocal,方式有两种:

3.1 compositionLocalOf

在重组期间只有读取current的组件才会发生重组:

代码语言:javascript复制
// 定义数据类
data class TextColor(var color: Color)

// 定义CompositionLocal
val LocalColors = compositionLocalOf { TextColor(Color.Black) }

object Colors {
    val Blue: TextColor
        get() = TextColor(color = Color.Blue)
    val Red: TextColor
        get() = TextColor(color = Color.Red)
}

@Composable
fun MyTextColor(
    text: String,
    color: Color = LocalColors.current.color
) {
    Text(text, color = color)
}

@Preview
@Composable
fun MyCompositionLocalProvider2() {
    Row {
        MyTextColor("hi")

        Spacer(modifier = Modifier.width(10.dp))

        CompositionLocalProvider(LocalColors provides Colors.Red) {
            MyTextColor("hi")
        }
    }
}

预览效果:

3.2 staticCompositionLocalOf

staticCompositionLocalOf 为更改值会导致提供 CompositionLocal 的整个 content lambda 被重组,如果提供的值发生更改的可能性微乎其微或永远不会更改,使用 staticCompositionLocalOf 可提高性能

二、列表LazyColumn&LazyRow

LazyColumnLazyRow相当于RecyclerView,内部组件并不会全部一次性加载,而是利用缓存机制,适用于加载大量的数据

1.LazyRow

LazyRow 支持横向滑动:

代码语言:javascript复制
@Composable
fun LazyRow(
    modifier: Modifier = Modifier,
    state: LazyListState = rememberLazyListState(),// 可以获取当前第一个显示的元素索引的状态
    contentPadding: PaddingValues = PaddingValues(0.dp),// 为内容组件设置的padding
    reverseLayout: Boolean = false,
    horizontalArrangement: Arrangement.Horizontal =
        if (!reverseLayout) Arrangement.Start else Arrangement.End,//可以通过Arrangement.spacedBy(xx.dp)设置元素的间距
    verticalAlignment: Alignment.Vertical = Alignment.Top,// 垂直对齐方式
    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
    userScrollEnabled: Boolean = true,
    content: LazyListScope.() -> Unit
) 

例子:

代码语言:javascript复制
@Preview
@Composable
fun MyLazyRow() {
    LazyRow {
        items(100) { index ->
            Text(
                text = "hi${index}",
                modifier = Modifier
                    .width(50.dp)
                    .padding(top = 10.dp, bottom = 10.dp),
                textAlign = TextAlign.Center
            )
        }
    }
}

效果:

2.LazyColumn

LazyColumn即纵向滑动列表,我们可以配合使用stickyHeader达到粘性标题:

例子:

代码语言:javascript复制
@OptIn(ExperimentalFoundationApi::class)
@Preview
@Composable
fun MyLazyColumn() {
    val lazyListState = rememberLazyListState()

    LazyColumn(
        state = lazyListState,
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(10.dp)
    ) {
        stickyHeader {
            Text(
                text = "index:${lazyListState.firstVisibleItemIndex}",
                modifier = Modifier
                    .background(MaterialTheme.colorScheme.surface)
                    .padding(10.dp),
                color = MaterialTheme.colorScheme.onSurface
            )
        }

        items(100) { index ->
            Text(
                text = "hi${index}",
                color = MaterialTheme.colorScheme.onSurface,
                modifier = Modifier
                    .fillMaxWidth()
                    .background(MaterialTheme.colorScheme.surface)
                    .padding(top = 10.dp, bottom = 10.dp)
                    .animateItemPlacement(),// 显示时的动画效果
                textAlign = TextAlign.Center
            )
        }
    }
}

效果:

除了LazyRowLazyColumn外,此外还有LazyVerticalGridLazyHorizontalGrid 可组合项为在网格中显示列表项提供支持,用法上是大致相同的

三、约束布局ConstraintLayout

ConstraintLayout面对一些复杂布局中,对对齐要求较高时,使用ConstraintLayout时一个很好的选择,它能够做到不需要嵌套各种RowBox等布局,只用一个约束布局实现内部组件的对齐,可以通过官网介绍进行学习使用:ConstraintLayout

ConstraintLayout需要导入依赖,版本可以通过官网查看: ConstraintLayout 版本页面

代码语言:javascript复制
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
1.创建引用,使用约束

ConstraintLayout作用域内,需要通过createRefs()createRefFor()为内容组件创建引用,通过约束条件,如linkTo()对引用的组件进行对齐,约束条件由constrainAs() 修饰符提供

例子:

代码语言:javascript复制
@Preview
@Composable
fun MyConstraintLayout1() {
    ConstraintLayout {
        // 创建两个引用
        val (txt, btn) = createRefs()

        // 对button、text两个组件分别设置引用
        Button(
            onClick = { /*TODO*/ },
            // 为button进行约束
            modifier = Modifier.constrainAs(btn) {
                // 以父组件顶部,进行一个16dp的margin
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("hi")
        }

        Text(
            "小弟",
            // 为text进行约束
            modifier = Modifier.constrainAs(txt) {
                // 位于btn的下面,并且有一个16dp的margin
                top.linkTo(btn.bottom, margin = 16.dp)
            })
    }
}

预览效果:

2.解构

如果你不想在ConstraintLayout作用域内定义引用以及约束规则,那么可以将 ConstraintSet 作为参数传递给 ConstraintLayout,外部通过createRefFor("key")指定一个字符串key创建引用,constrain("key")进行约束条件;内容组件使用修饰符layoutId("key")进行约束匹配

例子:

代码语言:javascript复制
@Preview
@Composable
fun MyConstraintPreview2() {
    BoxWithConstraints {
        val constraints = if (minWidth < 600.dp) {
            decoupledConstraints(margin = 16.dp) // Portrait constraints
        } else {
            decoupledConstraints(margin = 32.dp) // Landscape constraints
        }
        ConstraintLayout(constraintSet = constraints) {
            Button(
                onClick = { /* Do something */ },
                // 找到引用
                modifier = Modifier.layoutId("button")
            ) {
                Text("hi")
            }

            Text("老弟", Modifier.layoutId("text"))
        }
    }
}

private fun decoupledConstraints(margin: Dp): ConstraintSet {
    return ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        constrain(button) {
            top.linkTo(parent.top, margin = margin)
        }
        constrain(text) {
            top.linkTo(button.bottom, margin)
        }
    }
}

预览效果:

3.Guideline

Guideline:可以创建一个指示线,指示线可以相较于父组件的topbottomstartend以一个百分比dp进行偏移,以便别的组件可以针对指示线进行约束,Guideline创建方式有以下4种:

代码语言:javascript复制
 // 较于父组件左边10%位置创建
val startGuideline = createGuidelineFromStart(0.1f)
// 较于父组件右边10%位置创建
val endGuideline = createGuidelineFromEnd(0.1f)
// 较于父组件顶部16dp位置创建
val topGuideline = createGuidelineFromTop(16.dp)
// 较于父组件底部16dp位置创建
val bottomGuideline = createGuidelineFromBottom(16.dp)

例子:

代码语言:javascript复制
@Preview
@Composable
fun MyGuidelinePreview() {
    ConstraintLayout(modifier = Modifier.fillMaxSize()) {
        val txt = createRef()
        val startGuideline = createGuidelineFromStart(0.5f)

        Text(text = "hi", modifier = Modifier.constrainAs(txt) {
            start.linkTo(startGuideline)
        })
    }
}

预览效果:

4.Barrier

Barrier可以将多个内容组件引用组合成一个屏障,其他的组件就可以以屏障Barrier来进行约束,创建Barrier有以下4中方式:

代码语言:javascript复制
// 以btn,txt进行组合,创建右边的barrier
 val barrier = createEndBarrier(btn, txt)
// 以btn,txt进行组合,创建左边的barrier
 val barrier = createStartBarrier(btn, txt)
// 以btn,txt进行组合,创建顶部的barrier
 val barrier = createTopBarrier(btn, txt)
// 以btn,txt进行组合,创建底部的barrier
 val barrier = createBottomBarrier(btn, txt)

例子:

代码语言:javascript复制
@Preview
@Composable
fun MyBarrierPreview() {
    ConstraintLayout {
        val (btn, txt) = createRefs()

        // 对button、text两个组件分别设置引用
        Button(
            onClick = { /*TODO*/ },
            // 为button进行约束
            modifier = Modifier.constrainAs(btn) {
                // 以父组件顶部,进行一个16dp的margin
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("hi")
        }

        Text(
            "小弟",
            // 为text进行约束
            modifier = Modifier.constrainAs(txt) {
                // 位于btn的下面,并且有一个16dp的margin
                top.linkTo(btn.bottom, margin = 16.dp)
                start.linkTo(btn.end)
            })

        // 创建barrier
        val barrier = createEndBarrier(btn, txt)
        val txt2 = createRef()

        Text(
            "老弟",
            modifier = Modifier.constrainAs(txt2) {
                start.linkTo(barrier)
                top.linkTo(btn.top)
            }
        )
    }
}

预览效果:

5.Chain

Chain用于将多个内容组件引用组合成以个链,并以不同的 ChainStyles 配置链内各个组件的分布,创建方式有两种:

代码语言:javascript复制
// 创建水平的链
val chain = createHorizontalChain(txt1, txt2, txt3, chainStyle = ChainStyle.SpreadInside)
// 创建垂直的链
val chain = createVerticalChain(txt1, txt2, txt3, chainStyle = ChainStyle.SpreadInside)

ChainStyles配置:

  • ChainStyle.Spread:空间会在所有可组合项之间均匀分布,包括第一个可组合项之前和最后一个可组合项之后的可用空间。
  • ChainStyle.SpreadInside:空间会在所有可组合项之间均匀分布,不包括第一个可组合项之前或最后一个可组合项之后的任何可用空间。
  • ChainStyle.Packed:空间会分布在第一个可组合项之前和最后一个可组合项之后,各个可组合项之间没有空间,会挤在一起。

例子:

代码语言:javascript复制
@Preview
@Composable
fun MyChainPreview() {
    ConstraintLayout(Modifier.fillMaxSize()) {
        val (txt1, txt2, txt3) = createRefs()
        val chain = createHorizontalChain(txt1, txt2, txt3, chainStyle = ChainStyle.SpreadInside)

        Text("hi1", modifier = Modifier.constrainAs(txt1) {})
        Text("hi2", modifier = Modifier.constrainAs(txt2) {})
        Text("hi3", modifier = Modifier.constrainAs(txt3) {})
    }
}

预览效果:

0 人点赞