阅读 877

使用Jetpack Compose遇到的一些问题及解决方案

状态管理

LiveData and State

如果你想通过数据变化自动刷新UI显示,LiveData和State都只能在它所包裹的对象发生变化时刷新UI。 所以当我们包裹的是一个对象,只是更改了对象中某个属性的值时,这并不会触发重组,刷新UI。

对于这种情况可以针对对象的某个属性使用MutableState<T>包裹,例如:

 data class People(var name: MutableState<String>, var sex:String) 复制代码

使用时还需注意,需要刷新的Widget还需显示的调用一次该属性,如:

 val tom by viewModel.student  Box(modifier = Modifier.fillMaxSize()) {      Column() {          Text(text = tom.name.value)          Button(onClick = {              viewModel.changeSex()          }) {              Text(text = "change value")          }      }  } 复制代码

   val tom by viewModel.student     Box(modifier = Modifier.fillMaxSize()) {         Column() {             Text(text = tom.toString())             ...         }     } 复制代码

当改变name属性值时,只有前者会触发UI重组(Compose),这与compose的重组机制有关。

手动触发重组

currentRecomposeScope.invalidate()

数据类管理

对于后台返回的展示数据类,推荐自定义一个用于展示的数据类,这样做有几点好处:

  • 方便UI状态控制,使用 MutableState包裹数据

  • 对数据进行预处理,让后续业务流程可以直接使用该数据

  • 方便维护,如果接口返回数据类型有变化,不会影响到业务模块

写法展示:

 class GoodsInfoResp(     //根据typeGuid已经分好类     val typeList: List<PadTypeRespDTO>? ) {     fun toDisplayData() = this.typeList?.map {         it.toDisplayData()     } } data class PadTypeResp(     val menuClassifyPictureType: Int? = 0,     val name: String,     val sort: Int,     val typeGuid: String,     val itemList: List<PadItemRespDTO> = mutableListOf(), ) {     fun toDisplayData() = DisplayTypeData(         menuClassifyPictureType = this.menuClassifyPictureType!!,         name = mutableStateOf(this.name),         ...         padItemRespDTOList = this.itemList.map {             it.toDisplayData(this.menuClassifyPictureType)         }     ) } //获取数据时,在repository中将数据转换成能直接使用的数据类 suspend fun requestGoodsList(): Flow<List<DisplayTypeData>?> {      return source.getGoodsList()          .map { it.toDisplayData() }          .map {...}  } 复制代码

事件传递

使用回调

在官方给的demo中,compose的事件都是通过回调层层下发的,像是这样:

@Composable fun RegisterScreen(     modifier: Modifier,     defaultPhone: String? = null,     jump2Login:(String,String?) -> Unit,     onHasRegistered:(String) -> Unit,     onRegisterClick: (         registerMemberReq: RegisterMemberReq,         onRegisterSuc: () -> Unit,         onRegistered: () -> Unit     ) -> Unit ) 复制代码

如果只有一层还好,如果要层层传递就很难受了,每个widget都要写。

使用viewmodel

如果直接将viewmodel传入widget,的确会省不少事,这样也会出现新的问题。

widget不解耦,需要传入特定的viewmodel

综上暂时没有完美的解决方案,只能权衡利弊使用这两种方式。

Dialog

compose中dialog是通过状态来控制显隐的,这样写也会遇到几个问题:

  • 需要显示这个dialog的activity都要提前写好widget,并用状态去控制它

  • dialog不能单独处理业务,需要依附于viewmodel

这些问题导致它完全不能复用,所以对于需要复用的业务dialog,推荐还是使用DialogFragment。

ViewModel膨胀

如果使用了官方给的Compose Navigation会导致一个问题,页面其实还是使用的同一个Activity,只有一个ViewModel。
如果业务不够复杂还好,如果界面多、业务复杂会导致ViewModel越来越膨胀,针对这种情况最好还是使用原生的fragment,每个fragment再创建自己的ViewModel。

换肤

如果使用官方给的api换肤会有个颜色数量限制,只能使用这些命名:

class Colors(     primary: Color,     primaryVariant: Color,     secondary: Color,     secondaryVariant: Color,     background: Color,     surface: Color,     error: Color,     onPrimary: Color,     onSecondary: Color,     onBackground: Color,     onSurface: Color,     onError: Color,     isLight: Boolean ) 复制代码

所以我模仿官方的写法自定义了个colorSet。代码如下:

 class CustomColors(     val primary: Color,     val background: Color,     val primaryVariant: Color,     val secondary: Color,     ... ) val darkColorSet = CustomColors(     primary = green6DDACB,     background = Color.Black,     primaryVariant = Color.Yellow,     secondary = Color.Blue ) val lightColorSet = CustomColors(     primary = green6DDACB,     background = Color.Cyan,     primaryVariant = Color.Gray,     secondary = Color.Blue ) @Composable fun ProvideColors(     colorSet: CustomColors,     content: @Composable () -> Unit ) {     CompositionLocalProvider(LocalAppColors provides remember { colorSet }, content = content) } private val LocalAppColors = staticCompositionLocalOf {     darkColorSet } object AppTheme {     val colors: CustomColors         @Composable         get() = LocalAppColors.current } //最后在Theme外面包一层 //传入想要使用的主题 ProvideColors(colorSet = customSkin) {     MaterialTheme(         typography = Typography,         shapes = Shapes,         content = content     ) } //使用时调用  AppTheme.colors.primary 复制代码

屏幕适配

屏幕适配方面我们采用了宽高分别计算比例,再来进行缩放,理论上适配任何屏幕。

首先获取屏幕宽高dp,根据设计稿宽高计算比例。

 @Composable fun initScreenConfigInfo() {     val config = LocalConfiguration.current     val widthDp = config.screenWidthDp.toFloat()     val heightDp = config.screenHeightDp.toFloat()     scale = config.densityDpi/160f     if (heightFactor == 0f) heightFactor = heightDp / designHeightDp     if (widthFactor == 0f) widthFactor = widthDp / designWidthDp } @Stable inline val Int.wdp: Dp     get() {         val result = this.toFloat() * widthFactor         return Dp(value = result)     } @Stable inline val Int.hdp: Dp     get() {         val result = this.toFloat() * heightFactor         return Dp(value = result)     } @Stable inline val Int.spi:TextUnit     get() {         return this* heightFactor.sp } 复制代码

具体使用时需要根据宽高来选择wdp和hdp。


作者:baima
链接:https://juejin.cn/post/7021818856003862564


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐