pepperlove
pepperlove

Reputation: 275

RecyclerView animation issue Android Kotlin

I have added an animation to the recyclerview to show the below transition.

When I long press on an item it shows the radio button and the item card moves to the right. The issue is that after the initial selection when ever I click or select other items, item6 and the items below animates again.

Can someone explain why this is happening and how I can fix this.

enter image description here

ListAdapter.kt:

class ListItemAdapter(private val values: List<PlaceholderContent.PlaceholderItem>
) : RecyclerView.Adapter<ListItemAdapter.ItemViewHolder>() {

    private lateinit var itemClick: OnItemClick
    private var selectedIndex: Int = -1
    private var selectedItems: SparseBooleanArray = SparseBooleanArray()
    private var isActive: Boolean = false
    private var activateAnimation: Boolean = false

    fun setItemClick(itemClick: OnItemClick) {
        this.itemClick = itemClick
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): ListItemAdapter.ItemViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(
            R.layout.fragment_item,
            parent,
            false
        )
        return ItemViewHolder(view)
    }

    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        holder.itemView.apply {
            findViewById<TextView>(R.id.item_number).text = values[position].id
            findViewById<TextView>(R.id.content).text = values[position].content
        }

        holder.itemView.findViewById<CardView>(R.id.list_item).setOnClickListener {
            itemClick.onItemClick(values[position], position)
        }

        holder.itemView.findViewById<CardView>(R.id.list_item).setOnLongClickListener {
            itemClick.onLongPress(values[position], position)
            true
        }

        toggleIcon(holder, position)
    }

    override fun getItemCount(): Int {
        return values.size
    }
    
    private fun itemTransition(holder: ItemViewHolder){
        val animator = ObjectAnimator.ofFloat(holder.itemView.findViewById(R.id.list_item), View.TRANSLATION_X, 150f)
        animator.start()
    }

    private fun itemTransitionBack(holder: ItemViewHolder){
        val animator = ObjectAnimator.ofFloat(holder.itemView.findViewById(R.id.list_item), View.TRANSLATION_X, 0f)
        animator.start()
    }


    fun toggleIcon(holder: ItemViewHolder, position: Int){
        val checkBox = holder.itemView.findViewById<RadioButton>(R.id.is_selected)
        if(selectedItems.get(position, false)){
            checkBox.isGone = false
            checkBox.isChecked = true
        }
        else{
            checkBox.isGone = true
            checkBox.isChecked = false
        }
        if(isActive) checkBox.isGone = false

        if(activateAnimation){
            itemTransition(holder)
        }
        else
            itemTransitionBack(holder)

        if(selectedIndex == position) selectedIndex = - 1
    }

    fun selectedItemCount() = selectedItems.size()

    fun toggleSelection(position: Int){

        selectedIndex = position
        if (selectedItems.get(position, false)){
            selectedItems.delete(position)
        }else {
            selectedItems.put(position, true)
        }
        notifyItemChanged(position)

        isActive = selectedItems.isNotEmpty()
        activateAnimation = selectedItems.isNotEmpty()
        notifyDataSetChanged()
    }

    fun clearSelection(){
        selectedItems.clear()
        notifyDataSetChanged()
    }

    interface OnItemClick {
        fun onItemClick(item: PlaceholderContent.PlaceholderItem, position: Int)
        fun onLongPress(item: PlaceholderContent.PlaceholderItem, position: Int)
    }

    inner class ItemViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
    }

}

ItemFragment.kt

adapter = ListItemAdapter(PlaceholderContent.ITEMS)
val recyclerViewList = view.findViewById<RecyclerView>(R.id.list)
recyclerViewList.adapter = adapter
recyclerViewList.layoutManager = LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
val myHelper = ItemTouchHelper(myCallback)
myHelper.attachToRecyclerView(recyclerViewList)

adapter.setItemClick(object : ListItemAdapter.OnItemClick{
    override fun onItemClick(
        item: PlaceholderContent.PlaceholderItem,
        position: Int
    ) {
        if(adapter.selectedItemCount() > 0)
            toggleSelection(position)
    }

    override fun onLongPress(
        item: PlaceholderContent.PlaceholderItem,
        position: Int
    ) {
        toggleSelection(position)
    }

})

private fun toggleSelection(position: Int){
    adapter.toggleSelection(position)
}

Upvotes: 4

Views: 1523

Answers (4)

Dnyaneshwar Patil
Dnyaneshwar Patil

Reputation: 112

You can override getItemViewType(int position) this will returning the id of the view which is always unique.

@Override
public int getItemViewType(int position) {
    return position;
}

or

@Override
public int getItemViewType(final int position) {
    return R.layout.fragment_item;
}

Since the Android system stores a static reference to each layout as an Integer in the “R” (resources) class, we can simply return the layout resource id to be used in the onCreateViewHolder() method.

Upvotes: 1

KGeeks
KGeeks

Reputation: 173

Instead of position try & use holder.getAdapterPosition() same for all the click action your are performing in to the bind method of the adapter,

holder.itemView.findViewById<CardView>(R.id.list_item).setOnClickListener {
            itemClick.onItemClick(values[position], holder.getAdapterPosition())
        }

        holder.itemView.findViewById<CardView>(R.id.list_item).setOnLongClickListener {
            itemClick.onLongPress(values[position], holder.getAdapterPosition())
            true
        }

        toggleIcon(holder, holder.getAdapterPosition())

Upvotes: 0

I.Step
I.Step

Reputation: 779

This is kind of a hack, but have you consider using?

holder.setIsRecyclable(false);

If the list would not be large and if it solves the issue, it can be used as a quick solution.

Upvotes: 0

Martin Marconcini
Martin Marconcini

Reputation: 27236

You're calling notifyDataSetChanged() inside your adapter.toggleSelection(position), regardless of whether this position was updated or not, this is re-binding all the visible views (and running the animations again).

Update

As stated in the comments, the reason why the 6th item is animated is likely due to the default ViewPool that the RecyclerView keeps (5 items). The 6th view is not part of that so it gets re-bound, re-displayed, and... re-animated.

What I would do is:

  1. Could I get rid of the notifyDataSetChanged()? Why are you calling that?
  2. Could I leverage RecyclerView-Selection since it's a Google library and what they suggest we use? It would have the benefit of less "custom" code.

Other than this, you could try to increase the RecycledViewPool as suggested by Pawel in the comments. Keep in mind this would likely be considered a code smell because different resolution, densities, screen sizes, etc., may affect how this behaves at runtime; this would be flaky and prone to fail, but depending on your particular use-case, may allow you go get away with it for now.

Upvotes: 1

Related Questions