B站动脑学院jetpackCompose

什么是jetpack

Compose 函数与浏览

package org.malred.jetpackcomposebasic

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import org.malred.jetpackcomposebasic.ui.theme.JetpackComposeBasicTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(name = "My Android")
        }
    }

    @Composable // compose 控件
    fun MessageCard(name: String) {
        // compose: 文本控件
        Text(text = "Hello $name!")
    }

    @Preview // 预览
    @Composable
    // 预览函数不能带参数
    fun PreviewMessageCard() {
        MessageCard(name = "Android")
    }
}

布局

column & row


column -> linear layout vertical
row -> linear layout horizontal
box -> fragment layout
column|row

package org.malred.jetpackcomposebasic

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import org.malred.jetpackcomposebasic.ui.theme.JetpackComposeBasicTheme

class MainActivity03 : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(Message("androdid", "jetpack compose"))
        }
    }

    @Composable // compose 控件
    fun MessageCard(msg: Message) {
        Row {
            Image(
                painter = painterResource(id = R.drawable.test),
                // 无障碍模式下, 会语言读出这个描述文本
                contentDescription = ""
            )
            // column: vertical垂直排列
            Column {
                // compose: 文本控件
                Text(text = msg.author)
                Text(text = msg.body)
            }
        }
    }
}

// 数据类
data class Message(val author: String, val body: String)

配置布局

package org.malred.jetpackcomposebasic

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.malred.jetpackcomposebasic.ui.theme.JetpackComposeBasicTheme

class MainActivity04 : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(Message("androdid", "jetpack compose"))
        }
    }

    @Composable // compose 控件
    fun MessageCard(msg: Message) {
        Row(modifier = Modifier.padding(all = 8.dp)) {
            Image(
                painter = painterResource(id = R.drawable.test),
                // 无障碍模式下, 会语言读出这个描述文本
                contentDescription = null,
                // 图片描述
                modifier= Modifier
                    .size(40.dp)
                    // 裁剪
                    .clip(CircleShape)
            )
            // column: vertical垂直排列
            Column {
                // compose: 文本控件
                Text(text = msg.author)
                // 间距
                Spacer(modifier = Modifier.height(4.dp))
                Text(text = msg.body)
            }
        }
    }
}

Material Design

// org/malred/jetpackcomposebasic/ui/theme/Theme.kt
package org.malred.jetpackcomposebasic.ui.theme

import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat

private val DarkColorScheme = darkColorScheme(
    // ...
    background = Color.Gray
)

private val LightColorScheme = lightColorScheme(
    // ...
    background = Color.White 
)

@Composable
fun JetpackComposeBasicTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colors = if (!darkTheme) {
        LightColorScheme
    } else {
        DarkColorScheme
    }

    // ...

    MaterialTheme(
        colorScheme = colors,
        typography = Typography,
        content = content,
    )
}
package org.malred.jetpackcomposebasic

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.malred.jetpackcomposebasic.ui.theme.JetpackComposeBasicTheme

class MainActivity04 : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // 基本主题, 可以使用它的深色主题
            JetpackComposeBasicTheme {
                MessageCard(Message("androdid", "jetpack compose"))
            }
        }
    }

    @Composable // compose 控件
    fun MessageCard(msg: Message) {
        Row(
            modifier = Modifier
                .padding(all = 8.dp)
//                .background(Color.Green),
                .background(MaterialTheme.colorScheme.background),
        ) {
            // ...
        }
    }
}

列表和动画

remember保存控件的变量, 当控件重绘后可以继续使用

package org.malred.jetpackcomposebasic

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.malred.jetpackcomposebasic.ui.theme.JetpackComposeBasicTheme
import java.text.SimpleDateFormat

class MainActivity05 : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // 基本主题, 可以使用它的深色主题
            JetpackComposeBasicTheme {
//                MessageCard(Message("androdid", "jetpack compose"))
                Conversation(SimpleData.conversationSample)
            }
        }
    }

    @Composable // compose 控件
    fun MessageCard(msg: Message) {
        Row(
            modifier = Modifier
                .padding(all = 8.dp)
//                .background(Color.Green),
                .background(MaterialTheme.colorScheme.background),
        ) {
            Image(
                painter = painterResource(id = R.drawable.test),
                // 无障碍模式下, 会语言读出这个描述文本
                contentDescription = null,
                // 图片描述
                modifier = Modifier
                    .size(40.dp)
                    // 裁剪
                    .clip(CircleShape)
            )
            Spacer(modifier = Modifier.width(8.dp))
            // 给compose存变量 isExpanded
            var isExpanded by remember { // by -> 委托
                // 可变状态对象
                mutableStateOf(false)
            }
            // column: vertical垂直排列
            Column(
                // 点击column后改变isExpanded的值
                modifier = Modifier.clickable { isExpanded = !isExpanded }
            ) {
                // compose: 文本控件
                Text(
                    text = msg.author,
                    color = MaterialTheme.colorScheme.secondary
                )
                // 间距
                Spacer(modifier = Modifier.height(4.dp))
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    style = MaterialTheme.typography.bodyMedium,
                    // 如果isExpanded为true, 就展开body
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1 // 最大显示函数
                )
            }
        }
    }

    @Composable
    fun Conversation(message: List<Message>) {
        // 带缓冲 list view
        LazyColumn {
            // 从list取出数据, 放入message card, 然后返回
            items(message) { message ->
                MessageCard(message)
            }
        }
    }
}
package org.malred.jetpackcomposebasic

// object -> 静态单例对象
object SimpleData {
    // sample conversation data
    // list集合
    val conversationSample = listOf(
        Message(
            "Colleague",
            "Test...Test...Test"
        ),
        Message(
            "Colleague",
            "List of Android versions:\n" +
                    "Android KitKat (API 19)\n" +
                    "Android Lollipop (API 21)\n" +
                    "Android Marshmallow (API 23)\n" +
                    "Android Nougat (API 24)\n" +
                    "Android Oreo (API 26)\n" +
                    "Android Pie (API 28)\n" +
                    "Android 10 (API 29)\n" +
                    "Android 11 (API 30)\n" +
                    "Android 12 (API 31)\n"
        ),
        Message(
            "Colleague",
            "Lorem ipsum dolor sit amet consectetur adipisicing elit." +
                    " Perspiciatis a voluptatum quaerat accusantium fuga" +
                    " nostrum numquam quis enim est expedita modi," +
                    " maxime dignissimos consequuntur possimus velit! Tempore ipsam sunt praesentium!"
        ),
        Message(
            "Colleague",
            "Lorem ipsum dolor sit amet consectetur adipisicing elit." +
                    " Perspiciatis a voluptatum quaerat accusantium fuga" +
                    " nostrum numquam quis enim est expedita modi," +
                    " maxime dignissimos consequuntur possimus velit! Tempore ipsam sunt praesentium!"
        ),
        Message(
            "Colleague",
            "Lorem ipsum dolor sit amet consectetur adipisicing elit." +
                    " Perspiciatis a voluptatum quaerat accusantium fuga" +
                    " nostrum numquam quis enim est expedita modi," +
                    " maxime dignissimos consequuntur possimus velit! Tempore ipsam sunt praesentium!"
        ),
        Message(
            "Colleague",
            "Lorem ipsum dolor sit amet consectetur adipisicing elit." +
                    " Perspiciatis a voluptatum quaerat accusantium fuga" +
                    " nostrum numquam quis enim est expedita modi," +
                    " maxime dignissimos consequuntur possimus velit! Tempore ipsam sunt praesentium!"
        ),
        Message(
            "Colleague",
            "Lorem ipsum dolor sit amet consectetur adipisicing elit." +
                    " Perspiciatis a voluptatum quaerat accusantium fuga" +
                    " nostrum numquam quis enim est expedita modi," +
                    " maxime dignissimos consequuntur possimus velit! Tempore ipsam sunt praesentium!"
        ),
    )
}

动画

            // 颜色 动画状态
            val surfaceColor: Color by animateColorAsState(
                // 从primary颜色转换到surface 或 反向切换
                if (isExpanded) MaterialTheme.colorScheme.primary
                else MaterialTheme.colorScheme.surface
            )
            // column: vertical垂直排列
            Column(
                // 点击column后改变isExpanded的值
                modifier = Modifier.clickable { isExpanded = !isExpanded }
            ) {
                // compose: 文本控件
                Text(
                    text = msg.author,
                    color = MaterialTheme.colorScheme.secondary
                )
                // 间距
                Spacer(modifier = Modifier.height(4.dp))
                // 使用动画
                Surface(
                    // 圆角
                    shape = MaterialTheme.shapes.medium,
                    // 阴影
                    shadowElevation = 1.dp,
                    color = surfaceColor,
                    modifier = Modifier
                        .animateContentSize()
                        .padding(1.dp)
                ) {
                    Text(
                        text = msg.body,
                        modifier = Modifier.padding(all = 4.dp),
                        style = MaterialTheme.typography.bodyMedium,
                        // 如果isExpanded为true, 就展开body
                        maxLines = if (isExpanded) Int.MAX_VALUE else 1 // 最大显示函数
                    )
                }
            }

Compose所解决的问题


声明式UI

组合 vs 继承


重组

回顾

布局

标准布局组件

隐式传参, A给B传了参数, 但是没有声明出来显示传参, 可以看到有参数传入

package org.malred.jetpackcomposelayout

import android.view.animation.AlphaAnimation
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.DefaultAlpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.ContentAlpha
import androidx.wear.compose.material.LocalContentAlpha
import org.malred.composelayout.R

@Composable
fun PhotographerCard(modifier: Modifier = Modifier) {
    Row(
        modifier = modifier
            .clip(RoundedCornerShape(4.dp))
            .background(color = MaterialTheme.colorScheme.surface)
            .clickable(onClick = { })
            .padding(16.dp)
    ) {
        // Surface是Jetpack Compose的基本构建块之一。它是一个提供可视化空间以及处理高程、形状和边界的组件。
        // Surface可以让开发人员控制阴影、边框、形状和背景色等元素的视觉效果。
        Surface(
            modifier = Modifier.size(50.dp),
            // 形状
            shape = CircleShape,
            color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f),
        ) {
            Image(
                painter = painterResource(id = R.drawable.cover),
                contentDescription = null
            )
        }
        Column {
            Text(text = "Alfred Sisley", fontWeight = FontWeight.Bold)
            CompositionLocalProvider(
                // 等价于 LocalContentAlpha.provides(ContentAlpha.medium)
                // 将透明度隐式传参给{}里的组件
                LocalContentAlpha provides ContentAlpha.medium
            ) {
                Text(text = "3 minutes ago", style = MaterialTheme.typography.bodyMedium)
            }
        }
    }
}
package org.malred.composelayout

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import org.malred.composelayout.ui.theme.ComposeLayoutTheme
import org.malred.jetpackcomposelayout.PhotographerCard

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeLayoutTheme {
                PhotographerCard()
            }
        }
    }
}

slots api

!!!!!!! 不要用 androidx.wear.compose.material 的库, 这个是穿戴设备的控件, 手机上不会显示

package org.malred.composelayout

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LayoutStudy() {
    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Text(text = "LayoutStudy")
                },
                actions = {
                    IconButton(onClick = {}) {
                        Icon(imageVector = Icons.Filled.Favorite, contentDescription = null)
                    }
                }
            )
        }
    ) { contentPadding ->
        BodyContent(Modifier.padding(contentPadding))
    }
}

@Composable
fun BodyContent(modifier: Modifier = Modifier) {
    Column(modifier = modifier.padding(8.dp)) {
        Text(text = "Hi there!")
        Text(text = "Thanks for going through the layoutStudy!")
    }
}

使用列表

package org.malred.composelayout

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import coil.compose.rememberImagePainter
import kotlinx.coroutines.launch

@Composable
fun Lists() {
    Column {
        // 循环
        repeat(100) {
            Text(text = "item #$it", style = MaterialTheme.typography.titleMedium)
        }
    }
}

@Composable
fun SimpleList() {
    // 状态更新触发重绘
    val scrollsStats = rememberScrollState()
    Column(Modifier.verticalScroll(scrollsStats)) {
        // 循环
        repeat(100) {
            Text(text = "item #$it", style = MaterialTheme.typography.titleMedium)
        }
    }
}

@Composable
fun LazyList() {
    val scrollsStats = rememberLazyListState()
    LazyColumn(state = scrollsStats) {
        items(100) {
            Text(text = "item #$it", style = MaterialTheme.typography.titleMedium)
        }
    }
}

@Composable
fun ScrollingList() {
    val listSize = 100
    val scrollsStats = rememberLazyListState()
    // 拿到协程作用域
    val coroutineScope = rememberCoroutineScope()

    Column {
        Row {
            Button(
                modifier = Modifier.weight(1f),
                onClick = {
                    // 启动协程
                    coroutineScope.launch {
                        // 滚动到指定位置
                        // animateScrollToItem是挂起函数,必须在另一个协程或挂起函数中使用
                        scrollsStats.animateScrollToItem(0)
                    }
                }
            ) {
                Text(text = "Scroll to the top")
            }
            Button(
                modifier = Modifier.weight(1f),
                onClick = {
                    // 启动协程
                    coroutineScope.launch {
                        // 滚动到指定位置
                        // animateScrollToItem是挂起函数,必须在另一个协程或挂起函数中使用
                        scrollsStats.animateScrollToItem(listSize - 1)
                    }
                }
            ) {
                Text(text = "Scroll to the end")
            }
        }

        LazyColumn(state = scrollsStats) {
            items(100) {
//                Text(text = "item #$it", style = MaterialTheme.typography.titleMedium)
                ImageListItem(index = it)
            }
        }
    }
}

@Composable
fun ImageListItem(index: Int) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(
            // io.coil-kt:coil-compose:1.3.0
            // 从网络加载图片, 记得给权限
            painter = rememberImagePainter(
                data = "https://ts1.cn.mm.bing.net/th/id/R-C.d07816d313cad5d2b53b30192443c4c5?rik=SUozltQgAs8%2bNQ&riu=http%3a%2f%2fn.sinaimg.cn%2fsinacn10119%2f600%2fw1920h1080%2f20190325%2f6449-hutwezf3366892.jpg&ehk=DH2hT8Ey9e3gfn%2fUBKrQFjRb3LPXUs9sEIOq4LRDZfQ%3d&risl=&pid=ImgRaw&r=0"
            ),
            contentDescription = null,
            modifier = Modifier.size(50.dp)
        )
        Spacer(modifier = Modifier.size(10.dp))
        Text(text = "item #$index", style = MaterialTheme.typography.titleMedium)
    }
}

自定义布局

使用修饰符

package org.malred.composelayout

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.layout.layout
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import org.malred.composelayout.ui.theme.ComposeLayoutTheme

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = this.then(
    layout { measurable, constraints ->
        // 测量元素
        val placeable = measurable.measure(constraints)
        // 测量之后, 获取元素的基线值
        val firstBaseline = placeable[FirstBaseline]
        // 元素新的y坐标 = 新基线值 - 旧基线值
        val placeableY = firstBaselineToTop.roundToPx() - firstBaseline; // roundToPx 转为px
        // 计算元素高度
        val height = placeable.height + placeableY

        layout(placeable.width, height) {
            // 设置元素的位置
            placeable.placeRelative(0, placeableY)
        }
    }
)

@Composable
fun TextWithPaddingToBaseline() {
    ComposeLayoutTheme {
        Text(
            text = "hi there!",
            Modifier
                .firstBaselineToTop(24.dp)
                .background(Color.Red)
        )
    }
}

仿Column组件

package org.malred.composelayout

import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.dp
import org.malred.composelayout.ui.theme.ComposeLayoutTheme

@Composable
fun MyOwnColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(modifier = modifier, content = content)
    { measurables, constraints ->
        // 测量距离
        val placeables = measurables.map { measurable ->
            // constraints 约束
            measurable.measure(constraints)
        }
        var yPosition = 0
        // 布局的大小 maxWidth -> 父容器最大宽
        layout(constraints.maxWidth, constraints.maxHeight) {
            placeables.forEach { placeable ->
                // 设置元素的位置
                placeable.placeRelative(x = 0, y = yPosition)
                // 每个元素的y相对于前面的元素的y相加
                yPosition += placeable.height
            }
        }
    }
}

@Composable
fun MyOwnColumnSample() {
    ComposeLayoutTheme {
        MyOwnColumn(Modifier.padding(8.dp)) {
            Text("test1")
            Text("test2")
            Text("test3")
            Text("test4")
        }
    }
}

staggeredGrid

package org.malred.composelayout

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.max
import kotlin.random.Random

@Composable
fun StaggeredGrid(
    modifier: Modifier = Modifier,
    rows: Int = 3,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // 保存每行的宽度
        val rowWidths = IntArray(rows) { 0 }
        // 保存每行的高度
        val rowHeights = IntArray(rows) { 0 }

        val placeables = measurables.mapIndexed { index, measurable ->
            // 测量每一个元素
            val placeable = measurable.measure(constraints)
            val row = index % rows
            // 一行的宽度 = 这一行所有元素宽度之和
            rowWidths[row] += placeable.width
            // 一行的高度,应该是这一行中高度最高的那个元素的高度
            rowHeights[row] = max(rowHeights[row], placeable.height)
            placeable
        }

        // 计算表格的宽高
        // 表格的宽度 应该是所有行当中最宽的哪一行的宽度
        // 如果没有值,就给最小值
        val width = rowWidths.maxOrNull() ?: constraints.minWidth
        // 表格的高度,应该是所有行高度之和
        val height = rowHeights.sumOf { it }

        // 设置每一行的y坐标
        val rowY = IntArray(rows) { 0 }
        for (i in 1 until rows) {
//            rowY[1] = rowY[0] + rowHeights[0]
            rowY[i] = rowY[i - 1] + rowHeights[i - 1]
        }

        layout(width, height) {
            val rowX = IntArray(rows) { 0 }
            // 设置每一个元素的坐标
            placeables.forEachIndexed { index, placeable ->
                // index: 0~10
                // rows: 3, ro: 0~2
                val row = index % rows
                placeable.placeRelative(
                    x = rowX[row],
                    y = rowY[row]
                )
                // 第一列, x坐标全都为0,下一列的x坐标要累加上前面元素的宽度
                // 设置下一列的x坐标
                rowX[row] += placeable.width
            }
        }
    }
}

@Composable
fun Chip(
    modifier: Modifier = Modifier,
    text: String
) {
    // 一个卡片,圆角,里面包含一个Row,第一列是Box,第二列是文本
    Card(
        modifier = modifier,
        border = BorderStroke(
            color = Color.Black,
            width = Dp.Hairline
        ),
        shape = RoundedCornerShape(8.dp)
    ) {
        Row(
            modifier = Modifier.padding(start = 8.dp, top = 4.dp, end = 8.dp, bottom = 4.dp),
            verticalAlignment = Alignment.CenterVertically // 文字垂直居中
        ) {
            Box(
                modifier = Modifier
                    .size(16.dp, 16.dp)
                    .background(color = MaterialTheme.colorScheme.secondary)
            )
            Spacer(modifier = Modifier.width(4.dp))
            Text(text)
        }
    }
}

@Composable
fun StaggeredGridBodyContent(modifier: Modifier = Modifier) {
    Row(
        modifier = modifier
            .background(color = Color.LightGray)
            .padding(16.dp)
            .horizontalScroll(rememberScrollState()),
        content = {
            StaggeredGrid(modifier = Modifier) {
//                for (topic in topics) {
                for (i in 0 until 15) {
                    val rand = Random(i)
                    Chip(modifier = Modifier.padding(8.dp), text = "test $rand")
                }
            }
        }
    )
}

约束布局

package org.malred.composelayout

import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension

@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout {
        // 通过createRefs创建引用,ConstraintLayout中的每个元素都需要关联一个
        val (button, text) = createRefs()
        Button(
            onClick = {},
            // 使用modifier.constrainAs提供约束,引用作为它的第一个参数
            // 在lambda里指定约束规则
            modifier = Modifier.constrainAs(button) {
                // 使用linkTo指定约束,parent是constraintLayout的引用
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text(text = "button")
        }

        Text(text = "text", Modifier.constrainAs(text) {
            // 文本的底部就是按钮的底部
            top.linkTo(button.bottom, margin = 16.dp)
            // 在约束布局中水平居中
            centerHorizontallyTo(parent)
        })
    }
}

@Composable
fun ConstraintLayoutContent2() {
    ConstraintLayout {
        val (button1, button2, text) = createRefs()
        Button(
            onClick = {},
            modifier = Modifier.constrainAs(button1) {
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text(text = "button1")
        }

        Text(text = "text", Modifier.constrainAs(text) {
            top.linkTo(button1.bottom, margin = 16.dp)
            // 在button1.end中间
            centerAround(button1.end)
        })

        // 将button1和text组合起来,建立一个屏障(barrier
        val barrier = createEndBarrier(button1, text)

        Button(
            onClick = {},
            modifier = Modifier.constrainAs(button2) {
                top.linkTo(parent.top, margin = 16.dp)
                start.linkTo(barrier)
            }
        ) {
            Text(text = "button2")
        }
    }
}

@Composable
fun LargeLayoutCContent() {
    ConstraintLayout {
        val text = createRef()
        val guideline = createGuidelineFromStart(fraction = 0.5f)
        Text(
            text = "vary long long long long long long long long long long long long text",
            Modifier.constrainAs(text) {
                linkTo(start = guideline, end = parent.end)
                width = Dimension.preferredWrapContent // 自动换行
            }
        )
    }
}

解耦api

@Composable
fun DecoupledContent() {
    val margin = 16.dp // val 不可变
    ConstraintLayout {
        val (button, text) = createRefs()

        Button(
            onClick = {},
            modifier = Modifier.constrainAs(button) {
                top.linkTo(parent.top, margin = margin)
            }
        ) {
            Text(text = "button")
        }

        Text(text = "text", Modifier.constrainAs(text) {
            top.linkTo(button.bottom, margin = margin)
        })
    }
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun DecoupledContent2() {
    BoxWithConstraints {
        val constrains = if (maxWidth < maxHeight) {
            decoupledConstraints(16.dp) // 竖屏
        } else {
            decoupledConstraints(160.dp) // 横屏
        }

        ConstraintLayout(constrains) { 
            Button(
                onClick = {},
                modifier = Modifier.layoutId("button")
            ) {
                Text(text = "button")
            }

            Text(text = "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)
        }
        constrain(text) {
            top.linkTo(button.bottom, margin)
        }
    }
}

intrinsics

package org.malred.composelayout

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material3.Divider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

@Composable
fun TwoTexts(modifier: Modifier = Modifier) {
    Row(
        // 高度设为内容的最小值
        modifier = modifier.height(IntrinsicSize.Min)
    ) {
        Text(
            text = "Hi",
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start)
        )

        Divider(
            color = Color.Black,
            modifier = Modifier
                .fillMaxHeight()
                .width(1.dp)
        )

        Text(
            text = "there",
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.End)// 在父容器的结束位置
        )
    }
}

compose 状态

无状态组件

    implementation ("androidx.appcompat:appcompat:1.6.1")
    implementation ("androidx.compose.material:material")
    implementation ("androidx.compose.material:material-icons-extended")
    implementation ("androidx.compose.runtime:runtime-livedata")
    implementation ("androidx.compose.runtime:runtime")

string.xml


<resources>
    <string name="app_name">JetpackComposeState</string>
    <string name="cd_expand">Expand</string>
    <string name="cd_collapse">Collapse</string>
    <string name="cd_crop_square">Crop</string>
    <string name="cd_done">Done</string>
    <string name="cd_event">Event</string>
    <string name="cd_privacy">Privacy</string>
    <string name="cd_restore">Restore</string>
</resources>
package org.malred.jetpackcomposestate.todo.one

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import org.malred.jetpackcomposestate.R
import org.malred.jetpackcomposestate.todo.TodoIcon
import org.malred.jetpackcomposestate.todo.TodoItem
import org.malred.jetpackcomposestate.ui.theme.JetpackComposeStateTheme

class TodoActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            JetpackComposeStateTheme {
                TodoActivityScreen()
            }
        }
    }

    @Composable
    private fun TodoActivityScreen() {
        val items = listOf(
            TodoItem("Learn compose", TodoIcon.Event),
            TodoItem("Take the codelab"),
            TodoItem("Apply state", TodoIcon.Done),
            TodoItem("Build dynamic UIs", TodoIcon.Square)
        )
        TodoScreen(items)
    }
}
package org.malred.jetpackcomposestate.todo

import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CropSquare
import androidx.compose.material.icons.filled.Done
import androidx.compose.material.icons.filled.Event
import androidx.compose.material.icons.filled.PrivacyTip
import androidx.compose.material.icons.filled.RestoreFromTrash
import androidx.compose.ui.graphics.vector.ImageVector
import org.malred.jetpackcomposestate.R
import java.util.UUID

data class TodoItem(
    val task: String,
    val icon: TodoIcon = TodoIcon.Default,
    val id: UUID = UUID.randomUUID()
)

enum class TodoIcon(
    val imageVector: ImageVector,
    @StringRes val contentDescription: Int
) {
    //使用了Material Design的图标
    Square(Icons.Default.CropSquare, R.string.cd_expand),
    Done(Icons.Default.Done, R.string.cd_done),
    Event(Icons.Default.Event, R.string.cd_event),
    Privacy(Icons.Default.PrivacyTip, R.string.cd_privacy),
    Trash(Icons.Default.RestoreFromTrash, R.string.cd_restore);

    companion object {
        val Default = Square
    }
}
package org.malred.jetpackcomposestate.todo.one

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.focusModifier
import androidx.compose.ui.modifier.modifierLocalConsumer
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import org.malred.jetpackcomposestate.todo.TodoItem

@Composable
fun TodoScreen(
    items: List<TodoItem>
) {
    Column() {
        // 多行
        LazyColumn(
            modifier = Modifier.weight(1f), // 优先布局该column
            contentPadding = PaddingValues(top = 8.dp)
        ) {
            items(items) {
                TodoRow(
                    todo = it,
                    modifier = Modifier.fillParentMaxWidth()
                )
            }
        }

        Button(
            onClick = {},
            modifier = Modifier
                .padding(16.dp)
                .fillMaxWidth()
        ) {
            Text(text = "Add random item")
        }
    }
}

@Composable
fun TodoRow(
    todo: TodoItem,
    modifier: Modifier = Modifier
) {
    Row(
        modifier = modifier
            .padding(horizontal = 16.dp, vertical = 8.dp),
        // 子元素水平均匀分布
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        Text(text = todo.task)

        Icon(
            imageVector = todo.icon.imageVector,
            contentDescription = stringResource(id = todo.icon.contentDescription)
        )
    }
}

状态的定义

非结构化状态

如果要用xml写页面, 需要加上viewBinding

单向数据流



  目录