Reputation: 16777
I have a view and I want to change its size on click.
I have following layout for test:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<pro.labster.coloringbook.ui.view.ColorView
android:id="@+id/colorView"
android:layout_width="@dimen/colorSize"
android:layout_height="@dimen/colorSize"
android:layout_centerInParent="true"
app:normalSize="@dimen/colorSize"
app:selectedSize="@dimen/selectedColorSize" />
</RelativeLayout>
And the following code:
val colorView = findViewById<ColorView>(R.id.colorView)
colorView.setBackgroundColor(Color.RED)
colorView.setOnClickListener {
isSelected = !isSelected
colorView.setColorSelected(isSelected)
}
Size change code:
fun setColorSelected(isSelected: Boolean) {
if (isColorSelected != isSelected) {
if (isSelected) {
setCurrentSize(selectedSize.toInt())
} else {
setCurrentSize(normalSize.toInt())
}
}
isColorSelected = isSelected
}
private fun setCurrentSize(size: Int) {
if (layoutParams.height != size || layoutParams.width != size) {
layoutParams.width = size
layoutParams.height = size
requestLayout()
}
}
It works good:
https://www.youtube.com/watch?v=Ft8xcX5Qxbg
But if I add this view to RecyclerView, it lags on size change:
class ColorsAdapter(colorsHex: List<String>) : RecyclerView.Adapter<ColorsAdapter.ViewHolder>() {
private val colors = mutableListOf<Int>()
private var selectedPosition: Int = 0
init {
colorsHex.forEach {
colors.add(Color.parseColor(it))
}
}
override fun getItemCount() = colors.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val color = colors[position]
holder.colorView.setBackgroundColor(color)
holder.colorView.tag = position
holder.colorView.setColorSelected(position == selectedPosition)
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent?.context)
val view = inflater.inflate(R.layout.view_item_color, parent, false)
return ViewHolder(view)
}
inner class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
val colorView: ColorView = itemView!!.findViewById(R.id.colorView)
init {
colorView.setOnClickListener {
val oldPosition = selectedPosition
selectedPosition = colorView.tag as Int
if (oldPosition != selectedPosition) {
notifyItemChanged(oldPosition)
notifyItemChanged(selectedPosition)
}
}
}
}
}
view_item_color.xml
:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="@dimen/selectedColorSize"
android:layout_height="@dimen/selectedColorSize">
<pro.labster.coloringbook.ui.view.ColorView
android:id="@+id/colorView"
android:layout_width="@dimen/colorSize"
android:layout_height="@dimen/colorSize"
android:layout_gravity="center"
app:normalSize="@dimen/colorSize"
app:selectedSize="@dimen/selectedColorSize" />
</FrameLayout>
https://www.youtube.com/watch?v=m8g6zpj9aDg
As I can see, it also tries to animate size change — is it true?
And how to fix this lag?
Upvotes: 4
Views: 2255
Reputation: 17268
You are using notifyItemChanged(position: Int)
, RecyclerView doesn't know what exactly changed - maybe item was replaced with another item all together, so new ViewHolder gets bound in adapters onBindViewHolder(holder: ViewHolder, position:Int)
and replaces old one using default animation.
If you know explicitly what changed you can use notifyItemChanged(position: Int, payload: Any)
to supply change payload for adapter, then RecyclerView will only perform partial update of a ViewHolder in adapters onBindViewHolder(holder: ViewHolder, position:Int, payloads: MutableList<Any>)
and will not replace it.
For example you can change in your ViewHolder:
init {
colorView.setOnClickListener {
val oldPosition = selectedPosition
selectedPosition = colorView.tag as Int
if (oldPosition != selectedPosition) {
notifyItemChanged(oldPosition, false) // include payloads
notifyItemChanged(selectedPosition, true)
}
}
}
Then override in your Adapter:
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
if(payloads.size < 1){
//if there are no payloads perform full binding
onBindViewHolder(holder, position)
return
}
// if viewHolder can consume incremental updates iterate over payloads
// otherwise it's enough to grab the last update
// cast as Boolean is safe because only Booleans are passed as payloads
holder.colorView.setColorSelected(payloads.last() as Boolean)
}
This is also a good place to start your own animations by running them on ViewHolders views (remember to override adapters onViewRecycled(holder: ViewHolder)
and cancel/undo their state there)
Upvotes: 0
Reputation: 1110
You can do this by Item Animator and initialize them item animator
Upvotes: -1
Reputation: 62189
As seen in the docs of DefaultItemAnimator
:
This implementation of RecyclerView.ItemAnimator provides basic animations on remove, add, and move events that happen to the items in a RecyclerView. RecyclerView uses a DefaultItemAnimator by default.
If you want to remove those animations, then null out the default animator:
recyclerview.itemAnimator = null
Upvotes: 3