Xây dựng úng dụng chát đơn giản bằng RecyclerView
Bài đăng này đã không được cập nhật trong 6 năm
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_RECEIVE
và TYPE_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.java
và ViewHolderSend.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è.
All rights reserved