Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Infinite animation in RecyclerView stops after scrolling

I have multityped RecyclerView with animated views(blinking white circles) inside items. During recyclerView scrolling animations can randomly stop working.

I thought this problem is connected with onCreateViewHolder or onBindViewHolder, but this issue reproducing even if none of this methods were called.

Animation repeat count set to infinite, clearAnimation() only called in onBindViewHolder.

My adapter code :

import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.TextView;

import com.annimon.stream.Stream;
import com.squareup.picasso.Picasso;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.TimeZone;

import butterknife.Bind;
import butterknife.ButterKnife;

public class ChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements View.OnClickListener {

    private final int avatarSize;
    private List<IncomingTextMessage> chatMessages = new ArrayList<>();
    private User appOwner;
    private User wallOwner;
    private int MESSAGE_TYPE_MY = 0;
    private int MESSAGE_TYPE_INTERLOCUTOR = 1;
    private SimpleDateFormat timeFormat;
    private SimpleDateFormat dateFormat;
    private static final String TAG = "ChatAdapter";

    private int bindViewHolderCallCounter = 0;


    public ChatAdapter(User appOwner, User wallOwner, Context context) {
        this.appOwner = appOwner;
        this.wallOwner = wallOwner;
        avatarSize = context.getResources().getDimensionPixelSize(R.dimen.post_avatar_size);
        timeFormat = new SimpleDateFormat(context.getString(R.string.time_format));
        dateFormat = new SimpleDateFormat(context.getString(R.string.server_date_parsing_format));
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Random rnd = new Random();
        int color = Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));

        View v;
        if (viewType == MESSAGE_TYPE_MY) {
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_my_chat_message, parent, false);
            v.setBackgroundColor(color);
            return new MyMessageViewHolder(v);
        } else  {  //viewType == MESSAGE_TYPE_INTERLOCUTOR
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_interlocutor_chat_message, parent, false);
            v.setBackgroundColor(color);
            return new InterlocutorMessageViewHolder(v);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        bindViewHolderCallCounter++;



        Log.d(TAG, "onBindViewHolder:" + chatMessages.get(position).getText() + "   " + (getItemViewType(position) == MESSAGE_TYPE_MY));
        if (getItemViewType(position) == MESSAGE_TYPE_MY) {
            MyMessageViewHolder myMessageViewHolder = (MyMessageViewHolder) holder;
            setUserAvatar(appOwner, myMessageViewHolder.ivUserAvatar);
            myMessageViewHolder.ivUserAvatar.setTag(appOwner);
            myMessageViewHolder.ivUserAvatar.setOnClickListener(this);
            myMessageViewHolder.tvText.setText(chatMessages.get(position).getText() +" bindViewHolderCallCounter " + bindViewHolderCallCounter);
            myMessageViewHolder.tvTime.setText(formatTime(chatMessages.get(position).getDateTime()));

            setupMessageState(myMessageViewHolder, chatMessages.get(position));

        } else /*if (getItemViewType(position) == MESSAGE_TYPE_INTERLOCUTOR)*/ {
            InterlocutorMessageViewHolder interlocutorMessageViewHolder = (InterlocutorMessageViewHolder) holder;
            setUserAvatar(wallOwner, interlocutorMessageViewHolder.ivUserAvatar);
            interlocutorMessageViewHolder.ivUserAvatar.setTag(wallOwner);
            interlocutorMessageViewHolder.ivUserAvatar.setOnClickListener(this);
            interlocutorMessageViewHolder.tvText.setText(chatMessages.get(position).getText());
            interlocutorMessageViewHolder.tvTime.setText(formatTime(chatMessages.get(position).getDateTime()));

        }
    }

    private void setupMessageState(MyMessageViewHolder myMessageViewHolder, IncomingTextMessage message) {
        Log.d(TAG, "setupMessageState");

        Animation animation = AnimationUtils.loadAnimation(myMessageViewHolder.ivUserAvatar.getContext(), R.anim.fade_out_in_chat_circle);

        myMessageViewHolder.vMessageStatusAwaitingSending.clearAnimation();
        myMessageViewHolder.vMessageStatusAwaitingReading.clearAnimation();
        myMessageViewHolder.vMessageStatusAwaitingSending.clearAnimation();

        switch (message.getState()) {
            case MessageNotification.SENT: {
                Log.d(TAG, "MessageNotification.SENT" + message.getText());
                myMessageViewHolder.vMessageStatusAwaitingSending.setVisibility(View.VISIBLE);
                myMessageViewHolder.vMessageStatusAwaitingReading.setVisibility(View.INVISIBLE);
                myMessageViewHolder.vMessageStatusAwaitingSending.setVisibility(View.INVISIBLE);

                myMessageViewHolder.vMessageStatusAwaitingSending.setAnimation(animation);
                break;
            }
            case MessageNotification.RECEIVED:
            {
                Log.d(TAG, "MessageNotification.RECEIVED" + message.getText());

                myMessageViewHolder.vMessageStatusAwaitingSending.setVisibility(View.VISIBLE);
                myMessageViewHolder.vMessageStatusAwaitingReading.setVisibility(View.VISIBLE);
                myMessageViewHolder.vMessageStatusAwaitingSending.setVisibility(View.INVISIBLE);

                myMessageViewHolder.vMessageStatusAwaitingReading.setAnimation(animation);
                break;
            }
            case MessageNotification.DELIEVERED:
            {
                Log.d(TAG, "MessageNotification.DELIEVERED" + message.getText());

                myMessageViewHolder.vMessageStatusAwaitingSending.setVisibility(View.VISIBLE);
                myMessageViewHolder.vMessageStatusAwaitingReading.setVisibility(View.VISIBLE);
                myMessageViewHolder.vMessageStatusAwaitingSending.setVisibility(View.VISIBLE);

                myMessageViewHolder.vMessageStatusAwaitingReading.setAnimation(animation);
                break;
            }
            case MessageNotification.READ:
            {
                Log.d(TAG, "MessageNotification.READ" + message.getText());

                myMessageViewHolder.vMessageStatusAwaitingSending.setVisibility(View.VISIBLE);
                myMessageViewHolder.vMessageStatusAwaitingReading.setVisibility(View.VISIBLE);
                myMessageViewHolder.vMessageStatusAwaitingSending.setVisibility(View.VISIBLE);
                break;
            }
        }
    }

    private String formatTime(String severDate) {
        try {
            dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

            return timeFormat.format(dateFormat.parse(severDate));
        } catch (ParseException e) {
            e.printStackTrace();
            Log.e(TAG, "Time string parsing error :" + severDate);
            return "";
        }

    }

    @Override
    public int getItemViewType(int position) {
        if (chatMessages.get(position).getAuthorId().equals(String.valueOf(appOwner.getId())))
            return MESSAGE_TYPE_MY;
        else return MESSAGE_TYPE_INTERLOCUTOR;
    }

    private void setUserAvatar(BaseUser user, ImageView imageView) {
        if (user != null && user.getPrimaryImageUrl() != null && !user.getPrimaryImageUrl().isEmpty()) {
            Picasso.with(imageView.getContext())
                    .load(user.getPrimaryImageUrl())
                    .error(R.drawable.ic_user_avatar_128)
                    .centerCrop()
                    .resize(avatarSize, avatarSize)
                    .transform(new RoundedTransformation())
                    .into(imageView);
        } else {
            imageView.setImageResource(R.drawable.ic_user_avatar_128);
        }
    }



 /*   public void addNewUsers(List<BaseUser> newUsers) {
        this.chatMessages = newUsers;
        this.notifyDataSetChanged();
    }*/

    public void addMessage(IncomingTextMessage incomingTextMessage) {
        this.chatMessages.add(incomingTextMessage);
        notifyDataSetChanged();
    }
    public void changeMessageState(MessageNotification notification) {
        Stream.of(chatMessages)
                .filter(message -> message.getId().equals(notification.getMessageId()))
                .forEach(message ->
                {
                    message.setState(notification.getState());
                    notifyItemChanged(chatMessages.indexOf(message));
                });
    }

    @Override
    public int getItemCount() {
        return chatMessages.size();
    }

    @Override
    public long getItemId(int position) {
        return chatMessages.get(position).getId().hashCode();
    }

    @Override
    public void onClick(View view) {
        if (view.getTag() instanceof User) {
            User user = (User) view.getTag();
            Intent intent = new Intent(view.getContext(), MainActivity.class);
            intent.putExtra(Config.USER_STRING_EXTRA, user.getId());
            view.getContext().startActivity(intent);
        }
    }

    public void addMessages(ArrayList<IncomingTextMessage> incomingTextMessages) {
        chatMessages.addAll(incomingTextMessages);
        this.notifyDataSetChanged();
    }

    class MyMessageViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.vMessageStatusAwaitingSending)
        View vMessageStatusAwaitingSending;

        @Bind(R.id.vMessageStatusAwaitingDelivering)
        View vMessageStatusAwaitingDelivering;

        @Bind(R.id.vMessageStatusAwaitingReading)
        View vMessageStatusAwaitingReading;

        @Bind(R.id.ivUserAvatar)
        ImageView ivUserAvatar;

        @Bind(R.id.tvText)
        TextView tvText;

        @Bind(R.id.tvTime)
        TextView tvTime;

        public MyMessageViewHolder(View view) {
            super(view);
            ButterKnife.bind(this, view);
        }
    }

    class InterlocutorMessageViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.ivUserAvatar)
        ImageView ivUserAvatar;

        @Bind(R.id.tvText)
        TextView tvText;

        @Bind(R.id.tvTime)
        TextView tvTime;

        public InterlocutorMessageViewHolder(View view) {
            super(view);
            ButterKnife.bind(this, view);
        }
    }
}

Blinking animation xml

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="650"
    android:fromAlpha="1.0"
    android:repeatMode="reverse"
    android:repeatCount="infinite"
    android:toAlpha="0.1" />

enter image description here

like image 691
Alexander Zar Avatar asked Mar 25 '16 11:03

Alexander Zar


1 Answers

I ran into the same issue and found that the animation stops when the view gets detached from the window. When it is reattached, you won't get the onBindViewHolder call so the animation won't start.

The solution is to override onViewAttachedToWindow in your RecyclerView.Adapter<> and call setAnimation from there. You'll also need to maintain a reference to the IncomingTextMessage in the ViewHolder because onViewAttachedToWindow doesn't pass in the position.

Example:

@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
  if (holder instanceof MyMessageViewHolder) {
    MyMessageViewHolder messageHolder = (MyMessageViewHolder)holder;
    setupMessageState(messageHolder, messageHolder.message); // messageHolder.message being the IncomingTextMessage kept in MyMessageViewHolder
  }
}
like image 105
Alexandre Beaulieu Avatar answered Oct 23 '22 09:10

Alexandre Beaulieu