Xây dựng úng dụng chát đơn giản bằng RecyclerView



  • Hầu hết các ứng dụng di động bây giờ đều có tính năng chát, với những ứng dụng chát phức tạp thì đã có khá nhiều thư viện hỗ trợ, nhưng nếu bạn chỉ cần 1 ứng dụng đơn giản mà phải thêm những lib cồng kềnh vào thì sẽ kiến ứng dụng của bạn nặng nề. Dưới đây mình sẽ hướng dẫ các bạn sử dụng RecyclerView để tạo 1 chức năng Chat đơn giản.

    1. Cài đặt

    Cấu trúc thư mục.

    thêm các thư viện hỗ trợ.

        android {
        ...
            // Sử dụng data binding
            dataBinding {
                enabled = true
            }
        }
        dependencies {
        ...
            implementation 'com.android.support:recyclerview-v7:27.1.0'
            implementation 'com.android.support:cardview-v7:27.1.0'
    
            // Thư viện sử dụng để hiển thị ảnh
            implementation "com.github.bumptech.glide:glide:4.5.0"
        }
    

    2. Chi Tiết

    Bây giờ chúng ta sẽ đi vào chi tiết từng file.

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        >
        <data>
            <variable
                name="viewModel"
                type="com.demochatrecyclerview.MainActivity"
                />
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            tools:context="com.demochatrecyclerview.MainActivity"
            >
    
            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                />
            <View
                android:layout_width="wrap_content"
                android:layout_height="1dp"
                android:background="##FFE3E3E3"
                />
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:minHeight="56dp"
                android:orientation="horizontal"
                >
    
                <android.support.v7.widget.AppCompatImageView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_gravity="center"
                    android:layout_marginStart="10dp"
                    android:onClick="@{ viewModel::chooseImage }"
                    android:src="@mipmap/ic_camera"
                    />
    
                <android.support.v7.widget.AppCompatEditText
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_margin="10dp"
                    android:layout_weight="1"
                    android:background="@drawable/bg_edit_text_talk"
                    android:gravity="center_vertical"
                    android:hint="Write something"
                    android:padding="6dp"
                    android:text="@={ viewModel.message }"
                    android:textSize="14sp"
                    />
    
                <android.support.v7.widget.AppCompatTextView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_marginEnd="16dp"
                    android:gravity="center"
                    android:onClick="@{ viewModel::sendMessage }"
                    android:text="Send"
                    android:textColor="#FF318496"
                    android:textSize="16sp"
                    android:textStyle="bold"
                    />
            </LinearLayout>
        </LinearLayout>
    </layout>
    

    ở đây mình chỉ thiết kế layout đơn giản gồm 1 RecyclerView để hiển thị nội dung chát và 1 ô nhâp dữ liệu ở dưới.

    MainActivity.java

    public class MainActivity extends AppCompatActivity {
        private ActivityMainBinding binding;
        private RecyclerView recyclerView;
        private ChatAdapter chatAdapter;
        private List<Mesage> messageList;
    
        public ObservableField<String> message = new ObservableField<>();
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
            binding.setViewModel(this);
    
            messageList = new ArrayList<>();
            chatAdapter = new ChatAdapter(messageList);
    
            recyclerView = binding.recyclerView;
            LinearLayoutManager linearLayoutManager =
                    new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
            linearLayoutManager.setReverseLayout(true);
            linearLayoutManager.setSmoothScrollbarEnabled(true);
            linearLayoutManager.setAutoMeasureEnabled(true);
            linearLayoutManager.setStackFromEnd(true);
    
            recyclerView.setLayoutManager(linearLayoutManager);
            recyclerView.setNestedScrollingEnabled(false);
            recyclerView.setAdapter(chatAdapter);
        }
    
        public void chooseImage(View view) {
            
        }
    
        public void sendMessage(View view) {
    
        }
    }
    

    Cài đặt cơ bản cho file MainActivity

    Cài đặt file ChatAdapter.java

    public class ChatAdapter extends RecyclerView.Adapter<ChatViewHolder> {
        private static final int TYPE_MESSAGE_RECEIVE = 1;
        private static final int TYPE_MESSAGE_SEND = 2;
    
        private List<Message> messageList;
    
        public ChatAdapter(List<Message> messageList){
            this.messageList = messageList;
        }
        @NonNull
        @Override
        public ChatViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            switch (viewType) {
                case TYPE_MESSAGE_RECEIVE:
                    ItemMessageReceiveBinding itemMessageReceiveBinding =
                            ItemMessageReceiveBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
                    return new ViewHolderReceive(itemMessageReceiveBinding);
    
                case TYPE_MESSAGE_SEND:
                    ItemMessageSendBinding itemMessageSendBinding =
                            ItemMessageSendBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
                    return new ViewHolderSend(itemMessageSendBinding);
            }
            return null;
        }
    
        @Override
        public void onBindViewHolder(@NonNull ChatViewHolder holder, int position) {
            Message messageNext = position == 0 ? null : messageList.get(position - 1);
            Message messagePrevious = position >= getItemCount() - 1 ? null : messageList.get(position + 1);
    
            switch (holder.getItemViewType()) {
                case TYPE_MESSAGE_RECEIVE:
                    ((ViewHolderReceive) holder).bind(messageNext, messageList.get(position), messagePrevious);
    
                    break;
                case TYPE_MESSAGE_SEND:
                    ((ViewHolderSend) holder).bind(messageNext, messageList.get(position), messagePrevious);
                    break;
            }
        }
    
        @Override
        public int getItemViewType(int position) {
            if (messageList.get(position).isMyMessage()) {
                return TYPE_MESSAGE_SEND;
            } else {
                return TYPE_MESSAGE_RECEIVE;
            }
        }
    
        @Override
        public int getItemCount() {
            return messageList == null ? 0 : messageList.size();
        }
    }
    

    Ở đây chúng ta sẽ có 2 kiểu tin nhắn là tin nhắn do mình gửi đi và tin nhắn nhận về
    TYPE_MESSAGE_RECEIVETYPE_MESSAGE_SEND chúng ta sẽ lựa chọn từng kiểu tin nhắn để hiển thị sao cho đúng vị trí(giả sử tin nhắn gửi đi sẽ hiển thị ở bên phải còn con nhắn nhận về sẽ hiển thị ở bên trái)

    Ở trong hàm onBindViewHolder mình có truyền vào messagePrevious để khi hiển thị chúng ta có thể kiểm tra các điều kiện.

    File ViewHolderReceive.javaViewHolderSend.java

    public class ViewHolderReceive extends ChatViewHolder<ItemMessageReceiveBinding> {
    
        public ViewHolderReceive(ItemMessageReceiveBinding itemView) {
            super(itemView);
        }
    
        @Override
        public void bind(Message messageCurrent, Message messagePrevious) {
            if (binding.getViewModel() == null) {
                binding.setViewModel(this);
            }
            super.bind(messageCurrent, messagePrevious);
        }
    }
    ...
    public class ViewHolderSend extends ChatViewHolder<ItemMessageSendBinding> {
        public ViewHolderSend(ItemMessageSendBinding itemView) {
            super(itemView);
        }
    
        @Override
        public void bind(Message messageCurrent, Message messagePrevious) {
            if (binding.getViewModel() == null) {
                binding.setViewModel(this);
            }
            super.bind(messageCurrent, messagePrevious);
        }
    }
    

    File item_message_send.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tool="http://schemas.android.com/tools"
        >
        <data>
            <variable
                name="viewModel"
                type="com.demochatrecyclerview.ViewHolderSend"
                />
            <import type="android.view.View"/>
    
            <import type="android.text.TextUtils"/>
        </data>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="end"
            >
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:background="#FFFFFF"
                android:orientation="vertical"
                >
    
                <android.support.v7.widget.AppCompatTextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="20dp"
                    android:gravity="center"
                    android:text="@{ viewModel.date }"
                    android:textColor="#FF909090"
                    android:visibility="@{ TextUtils.isEmpty(viewModel.date) ? View.GONE : View.VISIBLE }"
                    tool:text="2018-02-28"
                    />
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="10dp"
                    android:gravity="end"
                    android:minHeight="36dp"
                    android:orientation="horizontal"
                    >
    
                    <android.support.v7.widget.AppCompatTextView
                        android:id="@+id/time"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="4dp"
                        android:minWidth="40dp"
                        android:text="@{ viewModel.time }"
                        android:textSize="14sp"
                        android:visibility="@{ TextUtils.isEmpty(viewModel.time) ? View.INVISIBLE : View.VISIBLE }"
                        tool:text="15:03"
                        />
    
                    <android.support.v7.widget.AppCompatTextView
                        android:id="@+id/content"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:layout_marginStart="10dp"
                        android:background="@drawable/bg_edit_text_talk"
                        android:gravity="center_vertical"
                        android:minHeight="36dp"
                        android:text="@{ viewModel.content }"
                        android:textColor="#000000"
                        android:textSize="14sp"
                        android:visibility="@{TextUtils.isEmpty(viewModel.content) ? View.GONE : View.VISIBLE }"
                        />
                </LinearLayout>
            </LinearLayout>
        </LinearLayout>
    </layout>
    

    file item_message_receive.xml có thiết kế gần giống với file item_message_send.xml nên mình sẽ không nêu ở đây. Và các bạn áp dụng và project của mình thì nên căn chỉnh lại chút cho đẹp nhé.

    Thực chất các tin nhắn hiển thị có logic giống nhau nên mình sẽ viết chung logic của chúng trong file ChatViewHolder.java còn 2 file này mục đích là chia View mà thôi.

    File logic chính để hiển thị Message là trong file ChatViewHolder.java

    public class ChatViewHolder<T extends ViewDataBinding> extends RecyclerView.ViewHolder {
        @BindingAdapter({ "glideImageUrl" })
        public static void setGlideImageUrl(ImageView imageview, String url) {
            RequestOptions requestOptions = new RequestOptions().placeholder(R.mipmap.ic_launcher_round)
                    .diskCacheStrategy(DiskCacheStrategy.RESOURCE);
    
            Glide.with(imageview.getContext()).load(url).apply(requestOptions).into(imageview);
        }
    
        protected T binding;
        protected Message messageCurrent;
    
        public ObservableField<String> date = new ObservableField<>();
        public ObservableField<String> time = new ObservableField<>();
        public ObservableField<String> content = new ObservableField<>();
        public ObservableField<String> fullName = new ObservableField<>();
    
        public ObservableField<String> imageAvatar = new ObservableField<>();
        public ObservableBoolean isShowAvatar = new ObservableBoolean(true);
    
        public ChatViewHolder(T itemView) {
            super(itemView.getRoot());
            binding = itemView;
        }
    
        public void bind(Message message, Message messagePrevious) {
            this.messageCurrent = messageCurrent;
    
            // Show Date
            // Hiển thị thời gian khi 2 tin nhắn nằm ở 2 ngày khác nhau
            if (messagePrevious == null || DateUtils.compareTwoDate(messagePrevious.getCreatedAt(),
                    messageCurrent.getCreatedAt(), DateUtils.TIMEZONE_FORMAT_YYYY_MM_DD)) {
    
                date.set(DateUtils.convertStringToStringFormat(messageCurrent.getCreatedAt(),
                        DateUtils.TIMEZONE_FORMAT_MESSAGE, DateUtils.TIMEZONE_FORMAT_YYYY_MM_DD_VIEW));
            } else {
                date.set("");
            }
    
            // Show Avatar
            if (messagePrevious == null
                    || messageCurrent.getUser() == null
                    || messagePrevious.getUser() == null
                    || messageCurrent.getUser().getId() != messagePrevious.getUser().getId()) {
    
                isShowAvatar.set(true);
                if (messageCurrent.getUser() != null) {
                    imageAvatar.set(messageCurrent.getUser().getAvata());
                    fullName.set(TextUtils.isEmpty(messageCurrent.getUser().getName()) ? "User unknown"
                            : messageCurrent.getUser().getName());
                } else {
                    imageAvatar.set("");
                    fullName.set("User unknown");
                }
            } else {
                isShowAvatar.set(false);
            }
    
            // Show Time
            // Hiển thị thời gian gửi hoặc nhận tin nhắn
            if (messagePrevious == null
                    || messagePrevious.getUser() == null
                    || messageCurrent.getUser() == null
                    || messageCurrent.getUser().getId() != messagePrevious.getUser().getId()
                    || Math.abs(DateUtils.compareTwoTime(messagePrevious.getCreatedAt(), messageCurrent.getCreatedAt()))
                    > 0) {
    
                time.set(DateUtils.convertStringToStringFormat(messageCurrent.getCreatedAt(),
                        DateUtils.TIMEZONE_FORMAT_MESSAGE, DateUtils.TIMEZONE_FORMAT_HH_MM));
            } else {
                time.set("");
            }
    
            // Hiển thị nội dung tin nhắn văn bản
            content.set(messageCurrent.getMessage());
        }
    }
    

    Vậy là chúng ta đã hoàn thành phần viết code chính cho tính năng chát. Bây giờ sẽ quay lại file MainActivity.java để thêm phần code cho hàm sendMessage

        public void sendMessage(View view) {
                long time = System.currentTimeMillis();
                User user = User.fakeUser();
                Message message = new Message();
                message.setMessageId(time);
                message.setUser(user);
                message.setMessage(messageChat.get());
                message.setMyMessage(time % 2 == 0);
                message.setCreatedAt(dateToString(time, "yyyy/MM/dd HH:mm:ss"));
    
                // Thêm item vào List và cập nhật lại hiển thị trên View
                messageList.add(0, message);
                chatAdapter.notifyItemInserted(0);
            }
        public String dateToString(long timestamp, String format) {
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format, Locale.getDefault());
                Date date = new Date(timestamp);
                return simpleDateFormat.format(date);
            }
    

    Đến đây gần như là chúng ta đã hoàn thành 1 tính năng chát cơ bản rồi. Hi vọng thông qua bài viết này các bạn có thể tự xây dựng cho mình 1 app chát đơn giản để nói chuyện với bạn bè.
    Nguồn: Viblo


Hãy đăng nhập để trả lời
 

Có vẻ như bạn đã mất kết nối tới LaptrinhX, vui lòng đợi một lúc để chúng tôi thử kết nối lại.