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

通過前面內(nèi)置組件和修飾符Modifier的使用,結(jié)合Stat狀態(tài),相信對于一般的開發(fā)需求已經(jīng)沒有問題了,接下來對CompositionLocal進(jìn)行學(xué)習(xí),以及對列表組件LazyColumn&LazyRow和約束布局的完善ConstraintLayout

一、CompositionLocal

CompositionLocal可以創(chuàng)建以樹為作用域的具名對象,簡單來說就是可組合函數(shù)的作用域內(nèi),其所有的內(nèi)容組件都可以隱式的拿到和修改CompositionLocal中的內(nèi)容,針對組件的顏色、樣式等屬性值,他們往往按照一套風(fēng)格來設(shè)計(jì),使用隱式調(diào)用更加合適

1.MaterialTheme主題

之前我們在使用一些ShapeColorTextStyle時(shí),用到了MaterialThemeshapescolorstypography獲取,這些都是CompositionLocal對象

創(chuàng)建項(xiàng)目時(shí),也會(huì)自動(dòng)幫助我們創(chuàng)建一個(gè)主題:

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風(fēng)格,也就是使用預(yù)設(shè)的幾種顏色,尺寸等對組件進(jìn)行樣式的選擇,并且整體APP遵循md風(fēng)格進(jìn)行設(shè)計(jì)

在項(xiàng)目中,直接使用定義的Theme主題包含compose組件,即可獲取md風(fēng)格的樣式,以及深色與淺色主題的切換:

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可以臨時(shí)改變CompositionLocal的屬性值,如果你想要對某些組件的樣式進(jìn)行特殊處理,推薦使用CompositionLocalProvider,此改變只爭對該作用域內(nèi)的組件:

@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的組件才會(huì)發(fā)生重組:

// 定義數(shù)據(jù)類
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")
        }
    }
}

預(yù)覽效果:

3.2 staticCompositionLocalOf

staticCompositionLocalOf 為更改值會(huì)導(dǎo)致提供 CompositionLocal 的整個(gè) content lambda 被重組,如果提供的值發(fā)生更改的可能性微乎其微或永遠(yuǎn)不會(huì)更改,使用 staticCompositionLocalOf 可提高性能

二、列表LazyColumn&LazyRow

LazyColumnLazyRow相當(dāng)于RecyclerView,內(nèi)部組件并不會(huì)全部一次性加載,而是利用緩存機(jī)制,適用于加載大量的數(shù)據(jù)

1.LazyRow

LazyRow 支持橫向滑動(dòng):

@Composable
fun LazyRow(
    modifier: Modifier = Modifier,
    state: LazyListState = rememberLazyListState(),// 可以獲取當(dāng)前第一個(gè)顯示的元素索引的狀態(tài)
    contentPadding: PaddingValues = PaddingValues(0.dp),// 為內(nèi)容組件設(shè)置的padding
    reverseLayout: Boolean = false,
    horizontalArrangement: Arrangement.Horizontal =
        if (!reverseLayout) Arrangement.Start else Arrangement.End,//可以通過Arrangement.spacedBy(xx.dp)設(shè)置元素的間距
    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即縱向滑動(dòng)列表,我們可以配合使用stickyHeader達(dá)到粘性標(biāo)題:

例子:

@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(),// 顯示時(shí)的動(dòng)畫效果
                textAlign = TextAlign.Center
            )
        }
    }
}

效果:

除了LazyRowLazyColumn外,此外還有LazyVerticalGridLazyHorizontalGrid 可組合項(xiàng)為在網(wǎng)格中顯示列表項(xiàng)提供支持,用法上是大致相同的

三、約束布局ConstraintLayout

ConstraintLayout面對一些復(fù)雜布局中,對對齊要求較高時(shí),使用ConstraintLayout時(shí)一個(gè)很好的選擇,它能夠做到不需要嵌套各種RowBox等布局,只用一個(gè)約束布局實(shí)現(xiàn)內(nèi)部組件的對齊,可以通過官網(wǎng)介紹進(jìn)行學(xué)習(xí)使用:ConstraintLayout

ConstraintLayout需要導(dǎo)入依賴,版本可以通過官網(wǎng)查看: ConstraintLayout 版本頁面

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

1.創(chuàng)建引用,使用約束

ConstraintLayout作用域內(nèi),需要通過createRefs()createRefFor()為內(nèi)容組件創(chuàng)建引用,通過約束條件,如linkTo()對引用的組件進(jìn)行對齊,約束條件由constrainAs() 修飾符提供

例子:

@Preview
@Composable
fun MyConstraintLayout1() {
    ConstraintLayout {
        // 創(chuàng)建兩個(gè)引用
        val (txt, btn) = createRefs()

        // 對button、text兩個(gè)組件分別設(shè)置引用
        Button(
            onClick = { /*TODO*/ },
            // 為button進(jìn)行約束
            modifier = Modifier.constrainAs(btn) {
                // 以父組件頂部,進(jìn)行一個(gè)16dp的margin
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("hi")
        }

        Text(
            "小弟",
            // 為text進(jìn)行約束
            modifier = Modifier.constrainAs(txt) {
                // 位于btn的下面,并且有一個(gè)16dp的margin
                top.linkTo(btn.bottom, margin = 16.dp)
            })
    }
}

預(yù)覽效果:

2.解構(gòu)

如果你不想在ConstraintLayout作用域內(nèi)定義引用以及約束規(guī)則,那么可以將 ConstraintSet 作為參數(shù)傳遞給 ConstraintLayout,外部通過createRefFor("key")指定一個(gè)字符串key創(chuàng)建引用,constrain("key")進(jìn)行約束條件;內(nèi)容組件使用修飾符layoutId("key")進(jìn)行約束匹配

例子:

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

預(yù)覽效果:

3.Guideline

Guideline:可以創(chuàng)建一個(gè)指示線,指示線可以相較于父組件的topbottomstartend以一個(gè)百分比dp進(jìn)行偏移,以便別的組件可以針對指示線進(jìn)行約束,Guideline創(chuàng)建方式有以下4種:

 // 較于父組件左邊10%位置創(chuàng)建
val startGuideline = createGuidelineFromStart(0.1f)
// 較于父組件右邊10%位置創(chuàng)建
val endGuideline = createGuidelineFromEnd(0.1f)
// 較于父組件頂部16dp位置創(chuàng)建
val topGuideline = createGuidelineFromTop(16.dp)
// 較于父組件底部16dp位置創(chuàng)建
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)
        })
    }
}

預(yù)覽效果:

4.Barrier

Barrier可以將多個(gè)內(nèi)容組件引用組合成一個(gè)屏障,其他的組件就可以以屏障Barrier來進(jìn)行約束,創(chuàng)建Barrier有以下4中方式:

// 以btn,txt進(jìn)行組合,創(chuàng)建右邊的barrier
 val barrier = createEndBarrier(btn, txt)
// 以btn,txt進(jìn)行組合,創(chuàng)建左邊的barrier
 val barrier = createStartBarrier(btn, txt)
// 以btn,txt進(jìn)行組合,創(chuàng)建頂部的barrier
 val barrier = createTopBarrier(btn, txt)
// 以btn,txt進(jìn)行組合,創(chuàng)建底部的barrier
 val barrier = createBottomBarrier(btn, txt)

例子:

@Preview
@Composable
fun MyBarrierPreview() {
    ConstraintLayout {
        val (btn, txt) = createRefs()

        // 對button、text兩個(gè)組件分別設(shè)置引用
        Button(
            onClick = { /*TODO*/ },
            // 為button進(jìn)行約束
            modifier = Modifier.constrainAs(btn) {
                // 以父組件頂部,進(jìn)行一個(gè)16dp的margin
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("hi")
        }

        Text(
            "小弟",
            // 為text進(jìn)行約束
            modifier = Modifier.constrainAs(txt) {
                // 位于btn的下面,并且有一個(gè)16dp的margin
                top.linkTo(btn.bottom, margin = 16.dp)
                start.linkTo(btn.end)
            })

        // 創(chuàng)建barrier
        val barrier = createEndBarrier(btn, txt)
        val txt2 = createRef()

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

預(yù)覽效果:


5.Chain

Chain用于將多個(gè)內(nèi)容組件引用組合成以個(gè)鏈,并以不同的 ChainStyles 配置鏈內(nèi)各個(gè)組件的分布,創(chuàng)建方式有兩種:

// 創(chuàng)建水平的鏈
val chain = createHorizontalChain(txt1, txt2, txt3, chainStyle = ChainStyle.SpreadInside)
// 創(chuàng)建垂直的鏈
val chain = createVerticalChain(txt1, txt2, txt3, chainStyle = ChainStyle.SpreadInside)

ChainStyles配置:

  • ChainStyle.Spread:空間會(huì)在所有可組合項(xiàng)之間均勻分布,包括第一個(gè)可組合項(xiàng)之前和最后一個(gè)可組合項(xiàng)之后的可用空間。
  • ChainStyle.SpreadInside:空間會(huì)在所有可組合項(xiàng)之間均勻分布,不包括第一個(gè)可組合項(xiàng)之前或最后一個(gè)可組合項(xiàng)之后的任何可用空間。
  • ChainStyle.Packed:空間會(huì)分布在第一個(gè)可組合項(xiàng)之前和最后一個(gè)可組合項(xiàng)之后,各個(gè)可組合項(xiàng)之間沒有空間,會(huì)擠在一起。

例子:

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

預(yù)覽效果:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容