androidandroid-recyclerviewrecyclerview-layout

RecyclerView messes up when scrolling


I never asked any question before but hope you'll get my point. I am making a chat app in which I am using a RecyclerView to show messages. The problem is when I scroll the RecyclerView some of the items disappear from the top and the whole items messes up when I try to add a message it doesn't even scroll to bottom nor added in the ListView.

Here is my RecyclerView:

<android.support.v7.widget.RecyclerView
    android:id="@+id/conversation_recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    android:layout_above="@id/typingConversationLayout"
    android:layout_below="@id/topLayout_conversation_activity"
    android:layout_marginBottom="-5dp"
    android:paddingBottom="7dp" />

Initializing and setting the RecycerView:

linearLayoutManager = new LinearLayoutManager(this);
    adapter = new ConversationRecyclerViewAdapter();
    conversationRecyclerView.setAdapter(adapter);
    conversationRecyclerView.setLayoutManager(linearLayoutManager);
    linearLayoutManager.setStackFromEnd(true);
    conversationRecyclerView.setHasFixedSize(true);
    conversationRecyclerView.setNestedScrollingEnabled(false);

Here is my Adapter class:

private class ConversationRecyclerViewAdapter
        extends RecyclerView.Adapter<ConversationRecyclerViewAdapter.ConversationViewHolder> {

    @NonNull
    @Override
    public ConversationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) {

        Log.d(TAG, "onCreateViewHolder: Users Find started");

        View conversationsView = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.layout_message_received, parent, false);

        return new ConversationViewHolder(conversationsView);
    }

    @Override
    public void onBindViewHolder(@NonNull final ConversationViewHolder holderConversation, int i) {
        Log.d(TAG, "onBindViewHolder: Users Find started at position is " + i);

        final int position = holderConversation.getAdapterPosition();

        if (mOwnUser_1.get(position)) {
            holderConversation.receivedMsgLayout.setVisibility(View.GONE);

            holderConversation.sentProfileImg.setImageResource(mUserProfileImg_2.get(position));
            holderConversation.sentMsg.setText(mUserText_3.get(position));

        } else {

            holderConversation.sentMsgLayout.setVisibility(View.GONE);

            holderConversation.receivedProfileImg.setImageResource(mUserProfileImg_2.get(position));
            holderConversation.receivedMsg.setText(mUserText_3.get(position));

        }

        Log.d(TAG, "onBindViewHolder: completed at " + position);

    }

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

    public class ConversationViewHolder extends RecyclerView.ViewHolder {

        RelativeLayout receivedMsgLayout, sentMsgLayout;
        EmojiTextView receivedMsg, sentMsg;
        CircleImageView receivedProfileImg, sentProfileImg;

        public ConversationViewHolder(@NonNull View v) {
            super(v);

            receivedMsgLayout = v.findViewById(R.id.received_message_layout);
            sentMsgLayout = v.findViewById(R.id.sent_message_layout);
            receivedMsg = v.findViewById(R.id.received_message_text);
            sentMsg = v.findViewById(R.id.sent_message_text);
            receivedProfileImg = v.findViewById(R.id.received_message_user__profile_image);
            sentProfileImg = v.findViewById(R.id.sent_message_user__profile_image);

        }
    }
}

Here I am adding data to ListView and displaying to the RecyclerView:

sendBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {

            String msg = editText.getText().toString().trim();

            if (TextUtils.isEmpty(msg)) {

                editText.setError("Please add a message");
                editText.requestFocus();

            } else {
                Log.d(TAG, "onClick: send Btn ADDED TEXT.. ");

                mOwnUser_1.add(user);
                mUserProfileImg_2.add(image);
                mUserText_3.add(message);

                editText.setText("");
                editText.requestFocus();

                adapter.notifyItemInserted(mOwnUser_1.size());
                conversationRecyclerView.scrollToPosition(mOwnUser_1.size() - 1);


            }

        }
    });

I don't know what i am doing wrong but it does not seem to work as i wanted.

Update Code:

The three listviews:

private ArrayList<Boolean> mOwnUser_1 = new ArrayList<>();
private ArrayList<Integer> mUserProfileImg_2 = new ArrayList<>();
private ArrayList<String> mUserText_3 = new ArrayList<>();

And the way of adding data to adapter:

mOwnUser_1.add(true);
mUserProfileImg_2.add(R.drawable.boy);
mUserText_3.add(edittext.getText().toString().trim());
adapter.notifyItemInserted(mOwnUser_1.size());
conversationRecyclerView.scrollToPosition(mOwnUser_1.size() - 1);

My Whole Conversation Activity Class:

public class ConversationActivity extends AppCompatActivity {
private static final String TAG = "ConversationActivity";

private EditText editText;
private LinearLayout linearLayout;
private LinearLayoutManager linearLayoutManager;

private ImageView sendBtn;
private ImageView emojiImage;
private View rootView;
private Boolean popUpShown = false;
private Boolean micShown = false;
private ImageView micBtn;
private RelativeLayout micLayout;
private RecyclerView conversationRecyclerView;

// Array Lists for Find USERS
private ArrayList<Boolean> mOwnUser_1 = new ArrayList<>();
private ArrayList<Integer> mUserProfileImg_2 = new ArrayList<>();
private ArrayList<String> mUserText_3 = new ArrayList<>();

private ConversationRecyclerViewAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "onCreate: started");
    super.onCreate(savedInstanceState);
    EmojiManager.install(new TwitterEmojiProvider());
    setContentView(R.layout.activity_conversation);

    editText = findViewById(R.id.conversationEditText);
    linearLayout = findViewById(R.id.optionsOther);
    emojiImage = findViewById(R.id.emojiIconOther);
    rootView = findViewById(R.id.root_view_conversation);
    micBtn = findViewById(R.id.microphoneBtn);
    micLayout = findViewById(R.id.microphoneLayout);
    conversationRecyclerView = findViewById(R.id.conversation_recyclerView);
    sendBtn = findViewById(R.id.sendBtnConversation);

    if (!(Build.VERSION.SDK_INT >= 21))
        findViewById(R.id.typingConversationLayout).setBackgroundResource(R.drawable.edit_text_conversation_background_below_api);

    sendBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {

            String msg = editText.getText().toString().trim();

            if (TextUtils.isEmpty(msg)) {

                editText.setError("Please add a message");
                editText.requestFocus();

            } else {
                Log.d(TAG, "onClick: send Btn ADDED TEXT.. ");

                addData(true, R.drawable.boy0, msg);
            }

        }
    });

    initConversationArrayList();

}

private void addData(Boolean user, int image, String message) {

    mOwnUser_1.add(user);
    mUserProfileImg_2.add(image);
    mUserText_3.add(message);

    editText.setText("");
    editText.requestFocus();

    adapter.notifyItemInserted(mOwnUser_1.size());
    conversationRecyclerView.scrollToPosition(mOwnUser_1.size() - 1);

}


private void initConversationArrayList() {
    Log.d(TAG, "initConversationArrayList: created");

    mOwnUser_1.add(true);
    mUserProfileImg_2.add(R.drawable.boy0);
    mUserText_3.add("Hello How are you?");


    Log.d(TAG, "initConversationArrayList: completed");

    initConversationRecyclerView();

}

private void initConversationRecyclerView() {
    Log.d(TAG, "initConversationRecyclerView: started");

    linearLayoutManager = new LinearLayoutManager(this);
    adapter = new ConversationRecyclerViewAdapter();
    conversationRecyclerView.setAdapter(adapter);
    conversationRecyclerView.setLayoutManager(linearLayoutManager);
    linearLayoutManager.setStackFromEnd(true);
    conversationRecyclerView.setHasFixedSize(true);
    conversationRecyclerView.setNestedScrollingEnabled(false);

    Log.d(TAG, "initConversationRecyclerView: completed");
}

Solution

  • Currently I am also working on chat module, let me show you how am I doing this. I am going to show you in steps.

    Step 1: make two separate layout for recyclerview items, one for message that has been sent from your side and one for message received from another side.

    Step 2 : make two view holders to populate different layout according to your scenario, made in above step, like this:

    public class ChatNewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private List<Chat> chats;
    
    public ChatNewAdapter(List<Chat> chats) {
        this.chats = chats;
    }
    
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    
        if (viewType == 0) {
            View viewSend = (View) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_message_send, parent, false);
            return new ViewHolderSend(viewSend);
        } else {
            View viewReceive = (View) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_message_received, parent, false);
            return new ViewHolderReceive(viewReceive);
        }
    }
    
    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
        switch (holder.getItemViewType()) {
            case 0:
                ViewHolderSend viewHolderSend = (ViewHolderSend) holder;
                viewHolderSend.messageSend.setText(chats.get(position).getMessage());
                break;
    
            case 1:
                ViewHolderReceive viewHolderReceive = (ViewHolderReceive) holder;
                viewHolderReceive.messageReceived.setText(chats.get(position).getMessage());
                break;
        }
    }
    
    @Override
    public int getItemCount() {
        return chats.size();
    }
    
    @Override
    public int getItemViewType(int position) {
        if (chats != null && !chats.get(position).fromAdmin) {
            return 0;
        } else
            return 1;
    }
    
    class ViewHolderSend extends RecyclerView.ViewHolder {
        TextView messageSend;
    
        public ViewHolderSend(View itemView) {
            super(itemView);
            messageSend = (TextView) itemView.findViewById(R.id.messageSend);
        }
    }
    
    class ViewHolderReceive extends RecyclerView.ViewHolder {
        TextView messageReceived;
    
        public ViewHolderReceive(View itemView) {
            super(itemView);
            messageReceived = (TextView) itemView.findViewById(R.id.messageReceived);
        }
    }
    
    public int addMessages(Chat chat) {
        chats.add(chat);
        notifyDataSetChanged();
        return chats.size();
    }
    

    Step 3 : now in your activity:

    public class Test extends AppCompatActivity {
    
    RecyclerView chatList;
    RecyclerView.LayoutManager mLayoutManager;
    ChatNewAdapter adapter;
    ImageView sendButton;
    EditText messageEditText;
    boolean keyboardUp = false;
    boolean isRunning = false;
    ArrayList<Chat> chats;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
        isRunning = true;
        setUpComponents();
    }
    
    
    public void setUpComponents() {
        chatList = (RecyclerView) findViewById(R.id.chat_list);
        chatList.setHasFixedSize(true);
        mLayoutManager = new LinearLayoutManager(this);
        chatList.setLayoutManager(mLayoutManager);
        messageEditText = (EditText) findViewById(R.id.messageText);
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
        sendButton = (ImageView) findViewById(R.id.send);
    
        adapter = new ChatNewAdapter(chats);
        chatList.setAdapter(adapter);
        chatList.scrollToPosition(chatList.getAdapter().getItemCount() - 1);
    
        messageEditText.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (keyboardShown(messageEditText.getRootView())) {
                    Log.d("keyboard", "keyboard UP");
    
                    if (keyboardUp == false) {
                        if (chats.size() > 0)
                            chatList.smoothScrollToPosition(chats.size() + 1);
                        keyboardUp = true;
                    }
    
                } else {
                    Log.d("keyboard", "keyboard Down");
                    keyboardUp = false;
                }
            }
        });
    
    
        sendButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
    
                final String message = messageEditText.getText().toString().trim();
                if (!message.equals("")) {
                    Chat chat = new Chat();
                    String name = message;
                    chat.setMessage(name);
                    messageEditText.setText("");
                    adapter.addMessages(chat);
                    chatList.scrollToPosition(chatList.getAdapter().getItemCount() - 1);
    
                } else {
                    Log.d("sending message Error", "error fetching dates");
                }
            }
        });
    }
    
    private boolean keyboardShown(View rootView) {
    
        final int softKeyboardHeight = 100;
        Rect r = new Rect();
        rootView.getWindowVisibleDisplayFrame(r);
        DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
        int heightDiff = rootView.getBottom() - r.bottom;
        return heightDiff > softKeyboardHeight * dm.density;
    }
    

    And this is my model class, ignore @PrimaryKey and @Required annotation it just because I am using Realm for local DB. In your case you wont required these annotation.

    public class Chat extends RealmObject {
    
    @PrimaryKey
    @Required
    public Long id;
    public boolean fromAdmin;
    @Required
    public String message;
    public int type;
    public boolean isRead;
    public boolean isSent;
    public Date date;
    
    public Long getId() {
        return id;
    }
    
    public void setId(Long id) {
        this.id = id;
    }
    
    public boolean isFromAdmin() {
        return fromAdmin;
    }
    
    public void setFromAdmin(boolean fromAdmin) {
        this.fromAdmin = fromAdmin;
    }
    
    public String getMessage() {
        return message;
    }
    
    public void setMessage(String message) {
        this.message = message;
    }
    
    public int getType() {
        return type;
    }
    
    public void setType(int type) {
        this.type = type;
    }
    
    public boolean isRead() {
        return isRead;
    }
    
    public void setRead(boolean read) {
        isRead = read;
    }
    
    public boolean isSent() {
        return isSent;
    }
    
    public void setSent(boolean sent) {
        isSent = sent;
    }
    
    public Date getDate() {
        return date;
    }
    
    public void setDate(Date date) {
        this.date = date;
    }
    

    I hope it will be helpful for you, you can ask further if you want to know anything else related to code.