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

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

一、CompositionLocal

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

1.MaterialTheme主题

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

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

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风格的样式,以及深色与浅色主题的切换:

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,此改变只争对该作用域内的组件:

@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的组件才会发生重组:

// 定义数据类
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 支持横向滑动:

@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
) 

例子:

@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达到粘性标题:

例子:

@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 版本页面

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

1.创建引用,使用约束

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

例子:

@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")进行约束匹配

例子:

@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种:

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

例子:

@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中方式:

// 以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)

例子:

@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 配置链内各个组件的分布,创建方式有两种:

// 创建水平的链
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:空间会分布在第一个可组合项之前和最后一个可组合项之后,各个可组合项之间没有空间,会挤在一起。

例子:

@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) {})
    }
}

预览效果:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容