开头感谢我的粉丝头子 (这位同学拒绝提供博客地址)提供的水群话题
不多说废话,直接进入正题
现在有.9图如下——
总所周知,compose中加载图片用Image()
image 的第一个参数:painter,
大家一般用自带的,或者官方推荐的coil库
当前文章使用的两者API版本分别为compose 1.2.0-alpha02和coil-compose:1.4.0
于是我们加载图片试试前者使用painterResource(),会报错——
java.lang.IllegalArgumentException: only VectorDrawables and rasterized asset types are supported ex. PNG, JPG
简直是滑天下之大稽!我这个图是不是png我自己心里没点数?
后者使用rememberImagePainter()倒是能够加载图片出来,来,我们一步步试试看。
首先,很自然的一个想法:coil库支持直接填入类型为int的ResIdDrawable,我们直接——
Image( rememberImagePainter(R.drawable.xxx), // "testFor.9", Modifier.size(300.dp, 50.dp), contentScale = ContentScale.FillBounds //拉伸图片以填充 )
得到图片如下——
显然是玩崩了,这个.9图片失去了它的特性,变成了普通的PNG图片。
然后我们想到,在传统ImageView直接使用Drawable其实也会被警告并推荐使用ContextCompat.getDrawable(context , @ResId resId)方法。
那我们这里试试看——
Image( rememberImagePainter( ContextCompat.getDrawable(context R.drawable.xxx) ), "testFor.9", Modifier.size(300.dp, 50.dp),//设置一个与图片明显不符的宽高测试 contentScale = ContentScale.FillBounds //拉伸图片以填充 )
得到图片如下——
wow~ awesome!
但显然大部分人会蹉跎这么一个小小的 *** 作,根本想不到这里去,并认为compose不支持.9图,起码目前不支持
这时候大家就会想到——我添加一个AndroidView,里面展示.9图,岂不美哉!
添加AndroidView语法很简单,直接看代码——
AndroidView({ it:Context -> //传入了一个context供你初始化该view //AndroidView中第一个参数默认返回一个传统View //在这里进行view的初始化,它只会被调用一次,且保证在UI线程上被调用 ImageView(it).apply { this.setImageDrawable(ContextCompat.getDrawable(it, R.drawable.left_bg)) this.scaleType = ImageView.ScaleType.FIT_XY } }, Modifier.size(300.dp, 50.dp)){ it:ImageView -> //这是一个可选参数,它是最后一个参数所以可以用kotlin的语法糖挪出来 //此lambda在view每次recompose过程中被调用,也运行在UI线程,方便你根据数据进行一些view状态的更新 }
好,我会了,然后呢
然后群友提出一个有意思的问题:这个传统view啊,我想给其他模块使用
听起来很简单,好说好说——
//这个参数用mutableState和放在这里只是为了方便演示 //实际上应该它的位置应该在ViewModel或者其他地方 //也需要按需使用LiveData或者其他数据结构 var dotNineImage: ImageView? by remember { mutableStateOf(null) } AndroidView( { ImageView(it).apply { this.setImageDrawable(ContextCompat.getDrawable(it, R.drawable.left_bg)) this.scaleType = ImageView.ScaleType.FIT_XY dotNineImage = this //初始化时把当前View保存 } }, Modifier.size(300.dp, 50.dp) )
按理来说这个dotNineImage对象就能被挪作他用了?
那肯定不行啊!
你要是直接拿此view添加到其他地方,会得到报错如下——
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
查看堆栈,出现在addView()的时候。
简单说就是:孩子只能有一个父亲!!
如果要给孩子换个家庭,那他得先和已有的parent断绝关系!!
这个报错已经很明显了,并且提示了你应该怎么做,但切记在hide此view后再将其移除
否则会在recompose过程由于尝试设置其属性出现类似以下错误的报错(具体报错可能因为各种原因略有不同,但肯定都是同一原因引发的)——
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.setMinimumWidth(int)' on a null object reference
关键代码如下——
var curImageView: ImageView? by remember { mutableStateOf(null) } var show by remember { mutableStateOf(true) } if (show) AndroidView({ if (curImageView == null) ImageView(it).apply { this.setImageDrawable(ContextCompat.getDrawable(it, R.drawable.left_bg)) this.scaleType = ImageView.ScaleType.FIT_XY curImageView = this } else curImageView!! }, Modifier .size(300.dp, 50.dp)) LaunchedEffect(show) { if (!show) { (curImageView?.parent as? ViewGroup)?.removeView(curImageView) } } //来个button简单切换show的值试试 Button({show=!show}){ Text("显隐AndroidView") }
如上代码,关键就是这个launchEffect——
它在此处被设定为观察show,
被观察的值一旦改变,
它一定会在compose过程中紧跟着执行它的block中描述的内容。
显而易见,
当show的值为false,当compose过程到launchEffect所在位置时,AndroidView已经隐藏了,
在show的值下一次改变之前,该AndroidView因为已经从compose树中移除,不会再参与后续的compose过程,
所以它此时可以被安全移除
更妙的是:当show值改变,下一次尝试显示该AndroidView时,
只要那时候——它没有变成别人的孩子或已经被断绝亲子关系,
它——还会被自动执行addView() *** 作添加回来。
基于这段逻辑的原理稍作修改,它已经具备了出现在其他地方,又从其他地方再回来的基础。
——提出这个问题的童鞋终于可以“理论上实现将视频转到小窗播放且无需 *** 心进度、加载、加载时的空白等等一系列问题了。
错误典型然后本来这个文章按理来说该结尾了,但童鞋说不对不对,他说他最终还是另开了一个view解决问题——
显然,之所以多用了一个view,是因为他没get到launchEffect的真谛!
敲黑板两个共用同一AndroidView的Composable绝对不能用同一个key去控制两者的显隐,否则必然会出现图中童鞋出现的问题!
两个共用同一AndroidView的Composable绝对不能用同一个key去控制两者的显隐,否则必然会出现图中童鞋出现的问题!
两个共用同一AndroidView的Composable绝对不能用同一个key去控制两者的显隐,否则必然会出现图中童鞋出现的问题!
用本文的例子说人话就是:
当我想在另外的地方使用这个ImageView时,我必须用launchEffect,确保这一处的ImageView消失后,再让该ImageView显示在另一处。
上代码自己领悟吧——
var curImageView: ImageView? by remember { mutableStateOf(null) } var show by remember { mutableStateOf(true) } var showSecond by remember { mutableStateOf(false) } Box { LazyColumn { item { if (show) AndroidView( { if (curImageView == null) ImageView(it).apply { this.setImageDrawable(ContextCompat.getDrawable(it, R.drawable.left_bg)) this.scaleType = ImageView.ScaleType.FIT_XY curImageView = this } else curImageView!! }, Modifier.size(300.dp, 50.dp) ) LaunchedEffect(show) { if (!show) { showSecond = true } } } } Box { if (showSecond) AndroidView({ curImageView!! }, Modifier.size(360.dp, 70.dp) ) LaunchedEffect(showSecond) { if (!showSecond) { show = true } } } } Button({ if (show) show = !show else showSecond = !showSecond }) { Text("显隐") }
这里用lazyColumn在写法错误的情况下必出问题
把LazyColumn替换成Column,或者将两个AndroidView放在同一Column中,即便用一个key同时控制两个view,问题也可能不会出现,
但如果你没认识到问题的本质,常在河边走,必定会湿鞋!
本质1:
你必须保证一个view已经在一处被移除后,才被添加到另一处——这是Android要求的。
本质2:
通过写在View后面,且观察view的控制key的LaunchEffect去保证此view已经在compose层次结构中消失,
此时这个view才可以被安全地从整个compose代码中移除,然后添加到其他地方
(或者从compose代码的这一处消失,然后出现在compose代码的另一处)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)