通過前面內(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主題
之前我們在使用一些Shape
、Color
、TextStyle
時(shí),用到了MaterialTheme
的shapes
、colors
、typography
獲取,這些都是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
LazyColumn
和LazyRow
相當(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
)
}
}
}
效果:
除了LazyRow
和LazyColumn
外,此外還有LazyVerticalGrid 和 LazyHorizontalGrid 可組合項(xiàng)為在網(wǎng)格中顯示列表項(xiàng)提供支持,用法上是大致相同的
三、約束布局ConstraintLayout
ConstraintLayout
面對一些復(fù)雜布局中,對對齊要求較高時(shí),使用ConstraintLayout
時(shí)一個(gè)很好的選擇,它能夠做到不需要嵌套各種Row
、Box
等布局,只用一個(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è)指示線,指示線可以相較于父組件的top
、bottom
、start
、end
以一個(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ù)覽效果: