目录
- Accompanist
- SystemUiController
- Pager
- SwipeRefresh
- Flow Layout
- Insets
- Lottie
- Coil
- AsyncImage
- SubcomposeAsyncImage
- AsyncImagePainter
Accompanist
- 最新可用版本
- accompanist官方文档
SystemUiController
- 依赖:implementation “com.google.accompanist:accompanist-systemuicontroller:<version>”
@Composable
fun SystemUiTest() {
Box(modifier = Modifier.fillMaxSize()) {
val systemUiController = rememberSystemUiController()
val useDarkIcons = MaterialTheme.colors.isLight
SideEffect {
systemUiController.setSystemBarsColor(color = Color.Transparent, darkIcons = useDarkIcons)
}
// TopAppBar(title = { Text(text = "TopAppBar") }, modifier = Modifier.statusBarsPadding(), backgroundColor = Color.Gray)
//使用com.google.accompanist:accompanist-insets-ui:0.30.1 实现沉浸式状态栏的效果
TopAppBar(title = { Text(text = "TopAppBar") }, backgroundColor = Color.Gray, contentPadding = WindowInsets.statusBars.asPaddingValues())
}
}
@Composable
fun SystemUiControllerDemo() {
val systemUiController = rememberSystemUiController()
val useDarkIcons = MaterialTheme.colors.isLight
val colorPanel = listOf(
Color.Gray,
Color.Red,
Color.Black,
Color.Cyan,
Color.Transparent,
Color.DarkGray,
Color.LightGray,
Color.Yellow
)
SideEffect {
systemUiController.setSystemBarsColor(Color.Transparent, useDarkIcons)
}
Column(Modifier.systemBarsPadding().fillMaxSize().background(Color(0xFF0079D3))) {
colorPanel.forEach {
Box(
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
.background(it)
.clickable {
systemUiController.setSystemBarsColor(it, useDarkIcons)
}
)
}
}
}
- 略直接设置状态栏和底部导航栏颜色的方法
Pager
- implementation “com.google.accompanist:accompanist-pager:$accompanist_version”
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PagerTest() {
Box(modifier = Modifier.fillMaxSize()) {
val pagerState = rememberPagerState()
val scope = rememberCoroutineScope()
HorizontalPager(pageCount = 3, modifier = Modifier.fillMaxSize(), state = pagerState) { page ->
when (page) {
0 -> ColorBox(color = Color.Blue, pageIndex = page)
1 -> ColorBox(color = Color.Cyan, pageIndex = page)
2 -> ColorBox(color = Color.Magenta, pageIndex = page)
}
}
SideEffect {
scope.launch {
delay(3000)
pagerState.scrollToPage(2)
}
}
}
}
@Composable
fun ColorBox(color: Color, pageIndex: Int) {
Box(
modifier = Modifier
.fillMaxSize()
.background(color = color), contentAlignment = Alignment.Center
) {
Text(text = "page $pageIndex")
}
}
- 略直接跳转到某个页面的方法
@OptIn(ExperimentalPagerApi::class)
@Composable
fun PagerDemo() {
val pagerState = rememberPagerState()
val scope = rememberCoroutineScope()
var selectedScreens by remember { mutableStateOf(0) }
val screens = listOf(
Screens("首页", Icons.Filled.Home) { Home() },
Screens("我喜欢的", Icons.Filled.Favorite) { Favorite() },
Screens("设置", Icons.Filled.Settings) { Settings() }
)
Scaffold(
bottomBar = {
BottomNavigationBar(
selectedScreens,
screens,
onClick = {
selectedScreens = it
scope.launch { pagerState.scrollToPage(selectedScreens) }
}
)
}
) {
HorizontalPager(
count = screens.size,
modifier = Modifier.fillMaxSize(),
state = pagerState
) { page ->
screens.forEachIndexed { index, screens ->
when (page) {
index -> screens.content()
}
}
}
}
LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }.collect { page ->
selectedScreens = page
}
}
}
@Composable
fun Home() {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Gray),
contentAlignment = Alignment.Center
) {
Text(
text = "1. 首页🤭",
style = MaterialTheme.typography.h5
)
}
}
@Composable
fun Favorite() {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF8F8F8)),
contentAlignment = Alignment.Center
) {
Text(
text = "2. 我喜欢的❤",
style = MaterialTheme.typography.h5
)
}
}
@Composable
fun Settings() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "3. 设置⚙",
style = MaterialTheme.typography.h5
)
}
}
@Composable
fun BottomNavigationBar(
selectedScreen: Int,
screens: List<Screens>,
onClick: (targetIndex: Int) -> Unit
) {
NavigationBar {
screens.forEachIndexed { index, screen ->
NavigationBarItem(
icon = { Icon(screen.iconVector, contentDescription = null) },
label = { Text(screen.label) },
selected = selectedScreen == index,
onClick = { onClick(index) }
)
}
}
}
data class Screens(
val label: String,
val iconVector: ImageVector,
val content: @Composable () -> Unit
)
SwipeRefresh
- implementation “com.google.accompanist:accompanist-swiperefresh:$accompanist_version”
@Preview
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SwipeRefreshTest() {
Box(modifier = Modifier.fillMaxSize()) {
val viewModel: MyViewModel = viewModel()
val isRefreshing by viewModel.isRefreshing.collectAsState()
val background by animateColorAsState(targetValue = viewModel.background, animationSpec = tween(1000), label = "backgroundAnim")
/* SwipeRefresh(state = rememberSwipeRefreshState(isRefreshing), onRefresh = { viewModel.refresh() }) {
Box(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.background(background)
)
}*/
val pullRefreshState = rememberPullRefreshState(refreshing = isRefreshing, onRefresh = { viewModel.refresh() })
Box(
modifier = Modifier
.fillMaxSize()
.pullRefresh(pullRefreshState)
.verticalScroll(rememberScrollState())
.background(background),
) {
PullRefreshIndicator(isRefreshing, pullRefreshState, modifier = Modifier.align(Alignment.TopCenter))
}
}
}
class MyViewModel : ViewModel() {
private val _isRefreshing = MutableStateFlow(false)
private val colorPanel = listOf(Color.Gray, Color.Red, Color.Black, Color.Cyan, Color.DarkGray, Color.LightGray, Color.Yellow)
val isRefreshing: StateFlow<Boolean>
get() = _isRefreshing
var background by mutableStateOf(Color.Gray)
fun refresh() {
viewModelScope.launch {
_isRefreshing.emit(true)
delay(1000)
background = colorPanel.random()
_isRefreshing.emit(false)
}
}
}
Flow Layout
- 会自动换行的row和Column
- implementation “com.google.accompanist:accompanist-flowlayout:$accompanist_version”
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun FlowLayoutTest() {
Column(modifier = Modifier.fillMaxSize()) {
FlowRow(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically, maxItemsInEachRow = 3) {
Text(text = "text1")
Text(text = "text2")
Text(text = "text3")
Text(text = "text4")
Text(text = "text5")
Text(text = "text6")
}
Divider(modifier = Modifier.fillMaxWidth())
FlowColumn(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.SpaceEvenly, horizontalAlignment = Alignment.CenterHorizontally, maxItemsInEachColumn = 3) {
Text(text = "text1")
Text(text = "text2")
Text(text = "text3")
Text(text = "text4")
Text(text = "text5")
Text(text = "text6")
}
}
}
@Composable
fun Tag(
modifier: Modifier = Modifier,
shape: Shape = CircleShape,
elevation: Dp = 0.dp,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
text: String,
textStyle: TextStyle = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
letterSpacing = 0.15.sp
),
backgroundColor: Color = Color(0xFFE8E8E8),
border: BorderStroke? = null,
onClick:() -> Unit
){
Surface(
shape = shape,
color = backgroundColor,
modifier = modifier,
elevation = elevation,
border = border
) {
Row(
modifier = Modifier
.clickable(
onClick = onClick
)
.padding(start = 15.dp, end = 15.dp, top = 8.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
when{
leadingIcon != null -> {
CompositionLocalProvider(
LocalContentAlpha provides ContentAlpha.high,
content = leadingIcon
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(
text = text,
style = textStyle,
)
}
trailingIcon != null -> {
Text(
text = text,
style = textStyle,
)
Spacer(Modifier.padding(horizontal = 4.dp))
CompositionLocalProvider(
LocalContentAlpha provides ContentAlpha.high,
content = trailingIcon
)
}
else -> {
Text(
text = text,
style = textStyle,
)
}
}
}
}
}
@Composable
fun FlowLayoutDemo() {
Surface(
modifier = Modifier
.systemBarsPadding()
.fillMaxWidth(),
elevation = 8.dp
) {
FlowRow(
modifier = Modifier.padding(8.dp),
crossAxisSpacing = 12.dp,
mainAxisSpacing = 10.dp
) {
Tag(
leadingIcon = {
Icon(painterResource(id = R.drawable.wechat), null, tint = Color.White)
},
text = "WeChat",
elevation = 6.dp,
textStyle = TextStyle(Color.White),
backgroundColor = Color(0xFF07C160)
) { }
Tag(
leadingIcon = {
Icon(painterResource(id = R.drawable.twitter), null, tint = Color.White)
},
text = "Twitter",
elevation = 6.dp,
textStyle = TextStyle(Color.White),
backgroundColor = Color(0xFF1DA1F2)
) { }
Tag(
leadingIcon = {
Icon(painterResource(id = R.drawable.github), null, tint = Color.White)
},
text = "Github",
elevation = 6.dp,
textStyle = TextStyle(Color.White),
backgroundColor = Color(0xFF181717)
) { }
Tag(
leadingIcon = {
Icon(painterResource(id = R.drawable.microsoftedge), null, tint = Color(0xFF0078D7))
},
text = "Edge",
elevation = 6.dp
) { }
Tag(
leadingIcon = {
Icon(painterResource(id = R.drawable.microsoft), null, tint = Color(0xFF5E5E5E))
},
text = "Microsoft",
elevation = 6.dp
) { }
}
}
}
Insets
- implementation “com.google.accompanist:accompanis-inss-ui:<version>”
@Composable
fun InsetsDemo() {
val systemUiController = rememberSystemUiController()
val useDarkIcons = MaterialTheme.colors.isLight
SideEffect {
systemUiController.setSystemBarsColor(Color.Transparent, useDarkIcons)
}
Scaffold(
topBar = {
TopAppBar(
title = {
Text("TopAppBar")
},
backgroundColor = Color.Gray,
contentPadding = WindowInsets.statusBars.asPaddingValues()
)
},
modifier = Modifier.fillMaxSize(),
contentColor = Color.Black
) { }
}
Lottie
- 添加依赖配置 implementation “com.airbnb.android:lottie-compose:$lottieVersion”
- 创建Lottie动画
- 创建两个状态用来描述动画的速度和开始暂停状态
var isPlaying by remember {
mutableStateOf(true)
}
var speed by remember {
mutableStateOf(1f)
}
- 加载Lottie动画资源,这里用本地加载方式
- Lottie框架提供了加载res/raw,加载URL,加载手机目录下的静态资源,加载asset目录下的静态资源,加载json字符串的功能
val lottieComposition by rememberLottieComposition(
spec = RawRes(R.raw.lottie),
)
- 接下来创建Lottie动画状态
val lottieAnimationState by animateLottieCompositionAsState (
composition = lottieComposition,
iterations = LottieConstants.IterateForever,
isPlaying = isPlaying,
speed = speed,
restartOnPlay = false
)
- 最后设置动画资源句柄和动画状态
LottieAnimation(
lottieComposition,
lottieAnimationState,
modifier = Modifier.size(400.dp)
)
Coil
- 添加依赖: implementation “io.coil-kt:coil-compose:$coil_version”
AsyncImage
@Preview
@Composable
fun AsyncImageDemo() {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(ImageUrl)
.crossfade(true)
.build(),
//model = (ImageUrl),
contentDescription = stringResource(R.string.description),
placeholder = painterResource(id = R.drawable.place_holder),
error = painterResource(id = R.drawable.error),
onSuccess = {
Log.d(TAG, "success")
},
onError = { error ->
Log.d(TAG, "error")
},
onLoading = { loading ->
Log.d(TAG, "loading")
},
modifier = Modifier.clip(CircleShape)
)
}
SubcomposeAsyncImage
@Preview
@Composable
fun SubcomposeAsyncImageDemo() {
SubcomposeAsyncImage(
model = "ImageUrl",
loading = { CircularProgressIndicator() },
contentDescription = "compose_museum"
)
}
@Preview
@Composable
fun SubcomposeAsyncImageDemo() {
SubcomposeAsyncImage(
model = "ImageUrl",
contentDescription = "compose_museum"
) {
if (painter.state is AsyncImagePainter.State.Loading || painter.state is AsyncImagePainter.State.Error) {
CircularProgressIndicator()
} else {
SubcomposeAsyncImageContent()
}
}
}
SubcomposeAsyncImage(
model = ImageRequest
.Builder(LocalContext.current)
.data(ImageUrl.testUrl1)
.size(1920,1080),
.build(),
contentDescription = null,
) {
val state = painter.state
when(state) {
is AsyncImagePainter.State.Loading -> CircularProgressIndicator()
is AsyncImagePainter.State.Error -> Text("${state.result.throwable}")
is AsyncImagePainter.State.Success -> SubcomposeAsyncImageContent()
is AsyncImagePainter.State.Empty -> Text("Empty")
}
}
AsyncImagePainter
- 这个组件是底层API,使用时会出现很多不可预期的行为,所以建议用前面两个
- 如果项目要求不能用AsyncImage则用这个
- 不能用Success状态来判断,否则图片不能加载成功
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data("https://pic-go-bed.oss-cn-beijing.aliyuncs.com/img/20220316151929.png")
.build()
)
if (painter.state is AsyncImagePainter.State.Loading) {
CircularProgressIndicator()
}
Image(
painter = painter,
contentDescription = stringResource(R.string.description)
)
《Jetpack Compose从入门到实战》第一章 全新的 Android UI 框架
《Jetpack Compose从入门到实战》 第二章 了解常用UI组件
《Jetpack Compose从入门到实战》第三章 定制 UI 视图
《Jetpack Compose从入门到实战》第八章 Compose页面 导航
《Jetpack Compose从入门到实战》第九章 Accompanist 与第三方组件库