rishabhsinghbisht
11/14/2022, 5:04 PMAndroidView
inside LazyList
. And the list started janking when scrolled fast. Profiled and noticed that android view is inflating the view every-time a new item comes becomes visible.
Is there a way to make them recycle views? have added key and content type to list items but that doesn’t help.FunkyMuse
11/14/2022, 7:32 PMChris Sinco [G]
11/14/2022, 9:30 PMBen Trengrove [G]
11/14/2022, 9:31 PMrishabhsinghbisht
11/15/2022, 5:27 AMrishabhsinghbisht
11/21/2022, 3:50 AMmattinger
11/22/2022, 3:53 AMcontentType: Any? = null
the type of the content of this item. The item compositions of the same type could be reused more efficiently. Note that null is a valid type and items of such type will be considered compatible.
object MyViewType
item(contentType=MyViewType) {
AndroidView(...) { ... }
}
I think this will solve your issue in a much more simplistic way.rishabhsinghbisht
11/22/2022, 3:57 AMmattinger
11/22/2022, 5:27 AMrishabhsinghbisht
11/22/2022, 5:29 AMremember{ViewPool()}
And ideally when the box is disposed view pool should just get GC collected.mattinger
11/22/2022, 5:35 AMmattinger
11/22/2022, 6:18 AMclass ViewPool<T : View>(
val factory: (Context) -> T,
val viewBlockCreateSize: Int = 1,
) {
private val allocatedViews = mutableListOf<T>()
private val freeViews = mutableListOf<T>()
private var createCounter = 0
fun checkoutView(context: Context): T {
Log.d("VIEWPOOL", "checkout")
return synchronized(this) {
if (freeViews.isEmpty()) {
(0 until viewBlockCreateSize).forEach {
createCounter += 1
Log.d("VIEWPOOL", "create ${createCounter}")
freeViews.add(factory(context))
}
}
freeViews.removeAt(0).apply {
allocatedViews.add(this)
}
}
}
fun returnView(view: T) {
Log.d("VIEWPOOL", "return")
synchronized(this) {
(view.parent as? ViewGroup)?.removeView(view)
if (allocatedViews.remove(view)) {
freeViews.add(view)
}
}
}
fun dispose() {
synchronized(this) {
freeViews.clear()
allocatedViews.forEach {
(it.parent as ViewGroup)?.removeView(it)
}
allocatedViews.clear()
}
}
}
@Composable
fun RecycledIcon(
viewPool: ViewPool<AppCompatImageView>,
@DrawableRes drawableRes: Int,
) {
val contentColor = LocalContentColor.current
val tintColor = Color.argb(
contentColor.toArgb().alpha,
contentColor.toArgb().red,
contentColor.toArgb().green,
contentColor.toArgb().blue,
)
RecycledView(
viewPool = viewPool,
update = { view ->
val drawable = view.context.getDrawable(drawableRes)
drawable?.let {
view.setImageDrawable(
it.mutate().apply {
setTint(this, tintColor)
}
)
view.layoutParams?.width = it.intrinsicWidth ?: 0
view.layoutParams?.width = it.intrinsicHeight ?: 0
}
}
)
}
@Composable
fun <T : View> RecycledView(
viewPool: ViewPool<T>,
update: (T) -> Unit,
) {
lateinit var view: T
AndroidView(
factory = { context ->
view = viewPool.checkoutView(context)
view
},
update = update
)
DisposableEffect(key1 = true) {
onDispose {
viewPool.returnView(view)
}
}
}
@Composable
@Preview
fun RecycledIconPreview() {
MaterialTheme {
Surface(modifier = Modifier.fillMaxSize()) {
val viewPool = remember {
ViewPool<AppCompatImageView>(
factory = { context ->
AppCompatImageView(context)
},
viewBlockCreateSize = 1,
)
}
LazyColumn {
(0..60).forEach {
item {
RecycledIcon(viewPool, androidx.appcompat.R.drawable.abc_ic_menu_cut_mtrl_alpha)
}
}
}
DisposableEffect(key1 = true) {
onDispose {
viewPool.dispose()
}
}
}
}
}