小蒋的技术栈记录

小蒋的技术栈记录

《Jetpack Compose从入门到实战》第九章 Accompanist 与第三方组件库-LMLPHP

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

  1. 添加依赖配置 implementation “com.airbnb.android:lottie-compose:$lottieVersion”
  2. 创建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 与第三方组件库

10-02 15:01