Jetpack Compose 实战 宝可梦图鉴

文章介绍了使用JetpackCompose开发的一个宝可梦图鉴应用,包括加载图片获取主色、自定义圆形进度条、Compose与navigation集成以及ViewModel的使用。开发者遇到了列表页返回时重组的问题,并通过缓存数据解决了这个问题。此外,文章提到了Coil库用于图片加载,Palette库获取图片主色,以及Hilt进行依赖注入。
摘要由CSDN通过智能技术生成


前言

阅读本文需要一定compose基础,如果没有请移步Jetpack Compose入门详解(实时更新)
学Compose学了有小半年的时间了,一直都是看官方的一些基础的教程并总结学习。最近终于实战了一个宝可梦图鉴的小项目,麻雀虽小五脏俱全。除了Compose外,还使用了一下一些Jetpack组件

接口数据来源于pokeapi

项目源代码
如果你觉得不错,请给我一个star,THKS


实现效果

闲话不多说,让我们来看看实现效果
在这里插入图片描述
可以看到我们实现了一个非常简洁的宝可梦图鉴,展示了所有世代的宝可梦,并提供了搜索,点击进入详情查看他们的属性

一、架构介绍🕹️

如图,展示了主要的一些文件:
在这里插入图片描述

  • api - 接口
  • nav - navigation导航
  • utils -工具类
  • view -Compose组件
  • viewmodels - viewmodel

二、一些的功能点的介绍📃

主要是开发过程中遇到的一些问题

加载图片并获取主色,再讲主色设置为背景📕

展示图片本来有封装好的CommonImage.kt用来展示图片,但是在列表中每一张图片的背景色都是动态获取的,当时做的时候觉得比较难弄,后面实现了过后就觉得还好啦。

引入的库

    implementation "io.coil-kt:coil-compose:2.1.0"
        //get Dominant image color
    implementation 'androidx.palette:palette-ktx:1.0.0'

主要代码在FullScreenView.kt中的 item()

	//我们所动态获取的图片背景色
	var backgroundColor by remember { mutableStateOf(0) }
    val context = LocalContext.current
    //ImageRequest
    val modelBuilder = ImageRequest.Builder(context).data(item.url.getPicUrl()).crossfade(false)
        .allowHardware(false).transformations().placeholder(R.drawable.ic_pokeball)
        .error(R.drawable.ic_pokeball)

	//开启一个获取协程的附带效应当ImageRequest发生改变时,通过ImageRequest来动态获取bitmap
	//再通过Palette来获取其主色
    LaunchedEffect(modelBuilder.build()) {
        val bitmap = context.imageLoader.execute(modelBuilder.build()).drawable?.toBitmap(
            config = Bitmap.Config.RGBA_F16
        )
        bitmap?.let {
            val palette = Palette.from(bitmap).generate()
            //当backgroundColor得到值,可组合项重组
            backgroundColor = palette.getDominantColor(0)
        }
    }

上面的代码最终能实现我想要的效果,但是我始终觉得不够优雅,后面有机会在封装一下吧

一个进度缓慢增加的圆形进度条📗

也是封装好的通用组件CommonCircularProgress.kt

@Composable
fun CommonAttributeCircularProgress(text:String,content:String,mProgress: Float,modifier: Modifier){
    Column(
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        var progress = remember {
            mutableStateOf(0.0f)
        }
        LaunchedEffect(true){
            var state = 0.0f
            while (state <= mProgress) {
                progress.value = state
                state += mProgress / 10f
                delay(50)
            }
        }

        Box(){
            Text(text = text,modifier = modifier.align(Alignment.Center))
            CircularProgressIndicator(progress = 1f,
                color = Color(0xFFffcba4),
                modifier = modifier
                    .align(Alignment.Center)
                    .size(150.cdp, 150.cdp)
            )
            CircularProgressIndicator(progress = progress.value,
                color = MaterialTheme.colors.secondary,
                modifier = modifier
                    .align(Alignment.Center)
                    .size(150.cdp, 150.cdp)
            )
        }
        Text(text = content, modifier = modifier.padding(top = 10.cdp))
    }
}

我没有找到能给CircularProgressIndicator可组合项目设置进度条范围内的背景的属性,所以使用了box将其覆盖一个CircularProgressIndicator可组合项来实现其背景的假象

单Activity使用navigation跳转Compose可组合项返回时页面重组的问题📘

列表项数据我按照官方推荐使用的方法直接使用了collectAsLazyPagingItems,然后我发现它确实起作用了,但是也发生了上诉问题,我每一次从详情页返回到列表页,列表页都会闪烁然后重组。这是因为每一次返回,可组合项都重新进行了请求导致数据发生变化,从而导致重组,为了解决这一问题,我在viewmodel中保存了一份请求得到的数据

@HiltViewModel
class PokemonListViewModel @Inject constructor(
    private val pokemonRepository: PokemonRepository
) :
    ViewModel() {


    // 宝可梦列表数据流  currentResult是为了保证从详情页面返回时不重新加载数据
    var currentResult: Flow<PagingData<PokemonResult>>? = null

    fun getPokemon(searchString: String?): Flow<PagingData<PokemonResult>> {
        val newResult = pokemonRepository.getPokemon(searchString).cachedIn(viewModelScope)
        currentResult = newResult
        return newResult
    }



}

并在页面中这样正确使用collectAsLazyPagingItems

    var list: LazyPagingItems<PokemonResult>? = null
    list = if (vm.currentResult != null) {
        vm.currentResult!!.collectAsLazyPagingItems()
    } else {
        vm.getPokemon(searchString.value).collectAsLazyPagingItems()
    }

这样当我从详情页返回到列表页时,页面显示正常

hiltViewModel()📙

在使用viemodel时,一开始我按照官方使用viewmodel的示例使用了**viewModel()**方法,并且在调试中是正常的。

val vm: PokemonListViewModel = viewModel()

可当我在加入navigation 后,viewmodel并不能正常的工作,后来查阅资料发现要使用

    implementation "androidx.navigation:navigation-compose:2.5.0-beta01"

包中的 hiltViewModel()

val vm: PokemonListViewModel = hiltViewModel()

主要参考项目

Jetpack Compose仿写网易云音乐
NCMusic;
一个使用原生xml kotlin写的宝可梦图鉴
PokeApi-Pokedex;

总结

整个项目其实就两个可组合项,一个是FullScreenView(列表页),一个是AttributeDetailView(详情页),每个页面就200行代码左右,对比起原生xml,的确是少了很多样板代码

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Jetpack Compose是一个全新的Android UI工具包,可以帮助开发者更快速、更简单地构建Android应用程序的用户界面。以下是一个Jetpack Compose实战项目的简介: 项目名称:Compose Countdown Timer 项目描述:这是一个基于Jetpack Compose的倒计时计时器应用程序。用户可以设置计时器的时间,并在计时器倒计时时观看动画。 实现步骤: 1.创建一个Compose项目并添加所需的依赖项。 2.创建一个计时器组件,该组件将显示计时器的当前时间,并在计时器倒计时时触发动画。 3.创建一个设置计时器时间的组件,该组件将允许用户设置计时器的时间。 4.将计时器组件和设置时间组件组合在一起,以创建一个完整的倒计时计时器应用程序。 代码示例: ```kotlin @Composable fun CountdownTimer() { var time by remember { mutableStateOf(0) } var isRunning by remember { mutableStateOf(false) } LaunchedEffect(isRunning) { while (isRunning && time > 0) { delay(1000) time-- } } Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { if (time > 0) { Text( text = time.toString(), fontSize = 60.sp, fontWeight = FontWeight.Bold ) } else { Text( text = "Time's up!", fontSize = 60.sp, fontWeight = FontWeight.Bold ) } } } @Composable fun SetTime(onTimeSelected: (Int) -> Unit) { var time by remember { mutableStateOf(0) } Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = "Set time", fontSize = 24.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(vertical = 16.dp) ) Row( modifier = Modifier.padding(vertical = 16.dp) ) { Text( text = "Minutes:", fontSize = 18.sp, modifier = Modifier.padding(end = 8.dp) ) OutlinedTextField( value = time.toString(), onValueChange = { time = it.toIntOrNull() ?: 0 }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.width(100.dp) ) } Button( onClick = { onTimeSelected(time) }, modifier = Modifier.padding(vertical = 16.dp) ) { Text(text = "Start timer") } } } @Composable fun ComposeCountdownTimer() { var time by remember { mutableStateOf(0) } var isRunning by remember { mutableStateOf(false) } if (time == 0) { SetTime(onTimeSelected = { selectedTime -> time = selectedTime * 60 isRunning = true }) } else { CountdownTimer() } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我怀里的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值