bcsta
bcsta

Reputation: 2307

How can I animate a child view in a recyclerView element after notifyItemChange() in onBindViewHolder()

I want to animate a textView inside a recyclerView to transition upwards when I call notifyItemChanged(position). This is my onBindViewholder() that is being called automatically when a change in the adapter occurs:

@Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    if (unfoldedIndexes.contains(position)) { // if already unfolded
        bindUnfoldedState(holder, holder.getLayoutPosition());
    }else{ // show the folded state}
}

public void bindUnfoldedState(ViewHolder holder, int position){


    if(lc.isBtnEnabled()){
            holder.Price.setText("text");
            holder.Price.animate().alpha(1.0f).setDuration(500);
            holder.Price.animate().translationY(-38).setDuration(500);
            holder.checkText.animate().translationY(38).setDuration(500);
        }else {
            holder.Price.animate().alpha(0.0f).setDuration(500);
            holder.Price.animate().translationY(0).setDuration(500);
            holder.checkText.animate().translationY(0).setDuration(500);
        }

}

my notifyItemChanged() is being called from a fragment onClick like so:

 ok.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view) {
            // some changes to the data
            adapter.getLcns().get(Position).setBtnEnabled(true);
            adapter.notifyItemChanged(Position);
            }
}

isBtnEnabled() is true if setBtnEnabled(true) is called.

The problem is that the animation is not happening. With logs I can confirm that onBindViewHolder() is being called and the animation should appear but it is not happening.

I was using anormal listView before I converted to a recyclerView and the animations were working in on getView()

Upvotes: 9

Views: 2192

Answers (4)

skRana
skRana

Reputation: 114

You can set a global flag(say boolean) in Adapter, make the boolean false when Adapter is called first time, and make a public method which set the flag true and notifies at that position.

The code may look like this

private boolean animation=false;

    public notifyAtPosition(int position){
       animation=true;
       notifyItemChanged(position);
     }
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    if (unfoldedIndexes.contains(position)) { // if already unfolded
        bindUnfoldedState(holder, holder.getLayoutPosition());
    }else{ // show the folded state}
}

public void bindUnfoldedState(ViewHolder holder, int position){


    if(animation){
            holder.Price.setText("text");
            holder.Price.animate().alpha(1.0f).setDuration(500);
            holder.Price.animate().translationY(-38).setDuration(500);
            holder.checkText.animate().translationY(38).setDuration(500);
        }else {
            holder.Price.animate().alpha(0.0f).setDuration(500);
            holder.Price.animate().translationY(0).setDuration(500);
            holder.checkText.animate().translationY(0).setDuration(500);
        }

}

Hope, you got the answer.

Upvotes: 0

Sahil Manchanda
Sahil Manchanda

Reputation: 10012

notifyItemChange() without payload will force the item to redraw whereas you want to animate existing View. The idea is to get the View from RecyclerView and then perform the animation directly onto it. e.g.

ok.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Adapter.ViewHolder holder = (Adapter.ViewHolder) recyclerView.findViewHolderForAdapterPosition(Position);
                adapter.getLcns().get(Position).setBtnEnabled(true);
                if (holder != null){
                    holder.Price.setText("text");
                    holder.Price.animate().alpha(1.0f).setDuration(500);
                    holder.Price.animate().translationY(-38).setDuration(500);
                    holder.checkText.animate().translationY(38).setDuration(500);
                }
            }
        });

Note the line

Adapter.ViewHolder holder = (Adapter.ViewHolder) recyclerView.findViewHolderForAdapterPosition(Position);

here the findViewHolderForAdapterPosition is returning the RecyclerView.ViewHolder which you need to typecast into your ViewHolder

or

use the nofityItemChanged with payload:

from the docs

Client can optionally pass a payload for partial change. These payloads will be merged and may be passed to adapter's onBindViewHolder(ViewHolder, int, List) if the item is already represented by a ViewHolder and it will be rebound to the same ViewHolder

Upvotes: 0

lukjar
lukjar

Reputation: 7425

Try to invoke setHasStableIds(true) in the adapter constructor. It is much easier to animate in onBindViewHolder when ids are stable.

Upvotes: 0

Pawel
Pawel

Reputation: 17258

Use Adapter.notifyItemChanged(position, payload) to deploy an update to already visible ViewHolders:

OnClick:

ok.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View view) {
        // some changes to the data
        adapter.getLcns().get(Position).setBtnEnabled(true);
        adapter.notifyItemChanged(Position, true);   // "true" is the payload
    }
}

Adapter:

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, List<Object> payloads) {
    if(payloads != null && !payloads.isEmpty()){  // update payloads, partial rebind
        Object lastPayload = payloads.get(payloads.size -1)); // in this case only last payload is meaningful
        if(lastPayload instanceof Boolean)
            bindUnfoldedState(holder, lastPayload);
    }else
        onBindViewHolder(holder, position);   // regular binding of item
}

public void bindUnfoldedState(ViewHolder holder, boolean unfold){
     if(unfold){ ... } 
     else { ... }
}

// you should also consider adding this override to prevent animation leaking when viewholder
// is recycled and reused for another position:
@Override
public void onViewRecycled(ViewHolder holder){
    holder.Price.animate().cancel();
    holder.checkText.animate().cancel();
}

Upvotes: 1

Related Questions