본문 바로가기

프로그래밍/안드로이드

[Java] 안드로이드 채팅 화면을 리사이클러뷰와 뷰바인딩을 이용해서 만들어보자

728x90
반응형

요즘 코틀린으로 앱을 많이 만든다곤 한다... 하지만 이미 프로젝트를 자바로 만든지 오래되었고

학교에서는 자바를 쓰기 때문에 자바 실력 향상을 위해서라도 계속 자바로 만들려고 하는데

 

새로운 기능을 적용하는 예제는 거의 코틀린으로 올라와서 자바로 어떻게 적용해야할지 생각하느라 시간이 많이 들었다..

 

암튼 이번에는 인터넷의 안드로이드 채팅 기능이 필요해서 찾아보면서 내 앱에 적용을 했는데

리사이클러뷰와 뷰바인딩을 사용했다

 

덕분에 공부를 좀 많이 한거같다

 


채팅을 만들려면 내가 입력한 채팅이 있을것이고 상대방이 입력한 채팅이 있을것이다

채팅의 데이터를 보관할 채팅 클래스를 만들어야하고

이 채팅을 보여줄 리사이클러뷰와 그 어댑터가 있어야할것이다

하나의 리사이클러뷰에서 내가 입력한 채팅인지 상대방이 입력한 채팅인지 확인할 수 있어야할것이다

 

채팅을 리스트뷰로 만들지 리사이클러뷰로 만들지는 자유지만

요즘은 최적화니 뭐니 하면서 리사이클러뷰로 많이 만들기 때문에 리사이클러뷰로 만든다

 

어차피 지금은 채팅 화면만 있으면 되므로 따로 네트워크 연결하거나

읽은 사람의 수는 ui만 넣고 기능은 여기선 안만들거다

 

인터넷을 찾아보다가 어떤분이 "어느어느분이 들어오셨습니다"와 같은 채팅도 만들어놓은것을 봤는데

이런 기능은 어차피 비슷하니까 나중에 만들고 주석처리 했다

 


Chat.java (채팅 데이터를 보관할 클래스)

package com.zynar.starvoca.items;

import java.util.Date;

public class Chat {
    Date date;
    String id;
    String nickname;
    String message;
    int readNumbs;

    public Chat(Date date, String id, String nickname, String message, int readNumbs) {
        this.date = date;
        this.id = id;
        this.nickname = nickname;
        this.message = message;
        this.readNumbs = readNumbs;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public int getReadNumbs() {
        return readNumbs;
    }

    public void setReadNumbs(int readNumbs) {
        this.readNumbs = readNumbs;
    }
}

지금 생각하고 있는것은 Date 클래스를 날짜로 저장하고 있는데 그냥 String으로 바꿀지 고려해야한다

일단 지금은 Date클래스를 사용

 

 

fragment_learn_ai_chat_start.xml (채팅 화면 프래그먼트 xml)

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".learn.ai_chat.AIChatStartFragment">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@id/bottom_app_bar"
            android:id="@+id/rv_chat"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />


        <com.google.android.material.bottomappbar.BottomAppBar
            app:layout_constraintBottom_toBottomOf="parent"
            android:id="@+id/bottom_app_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            tools:ignore="BottomAppBar">

            <LinearLayout
                android:layout_marginVertical="10dp"
                android:layout_marginHorizontal="10dp"
                android:orientation="horizontal"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <EditText
                    android:id="@+id/et_chat"
                    android:background="@android:color/transparent"
                    android:layout_gravity="center"
                    android:textSize="16sp"
                    android:layout_weight="1"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"/>

                <ImageButton
                    android:id="@+id/ib_send"
                    android:layout_gravity="bottom"
                    android:backgroundTint="@color/trans"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/ic_baseline_send_24"/>
            </LinearLayout>

        </com.google.android.material.bottomappbar.BottomAppBar>


    </androidx.constraintlayout.widget.ConstraintLayout>

</FrameLayout>

 

여긴 딱히 중요한건 아니라서 리사이클러뷰를 사용한것만 설명하면 될듯하다

 

 

AIChatStartFragment.java (채팅 xml과 연결되어 있는 프래그먼트 클래스이다)

package com.zynar.starvoca.learn.ai_chat;

import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.zynar.starvoca.R;
import com.zynar.starvoca.adpaters.AIChatAdapter;
import com.zynar.starvoca.databinding.FragmentLearnAiChatStartBinding;
import com.zynar.starvoca.items.Chat;
import com.zynar.starvoca.items.UserAccount;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class AIChatStartFragment extends Fragment {

    private FragmentLearnAiChatStartBinding binding;
    private final List<Chat> chatList = new ArrayList<>();

    public AIChatStartFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        binding = FragmentLearnAiChatStartBinding.inflate(inflater, container, false);

        binding.etChat.requestFocus();
        UserAccount userAccount = UserAccount.getInstance();
        chatList.add(new Chat(new Date(), userAccount.getUid(), userAccount.getNickname(), "qwe", 1));
        chatList.add(new Chat(new Date(), "abcd", "다른 사람 닉네임1", "asd", 1));
        chatList.add(new Chat(new Date(), userAccount.getUid(), userAccount.getNickname(), "abc", 1));
        chatList.add(new Chat(new Date(), "abcd", "다른 사람 닉네임1", "asd", 1));
        chatList.add(new Chat(new Date(), userAccount.getUid(), userAccount.getNickname(), "qwe", 1));
        AIChatAdapter aiChatAdapter = new AIChatAdapter(chatList);
        binding.rvChat.setHasFixedSize(true);
        binding.rvChat.setAdapter(aiChatAdapter);

        return binding.getRoot();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        UserAccount userAccount = UserAccount.getInstance();

        binding.ibSend.setOnClickListener(v->{
            if(binding.etChat.getText().toString().isEmpty()) {
                Toast.makeText(requireContext(), "텍스트를 입력해주세요.", Toast.LENGTH_SHORT).show();
            } else {
                Chat chat = new Chat(new Date(), userAccount.getUid(), userAccount.getNickname(), binding.etChat.getText().toString(), 1);
                chatList.add(chat);
                binding.rvChat.getAdapter().notifyDataSetChanged();
                binding.etChat.setText("");
            }
            Log.d("__star__", "click");
        });
        
        // 채팅 클래스를 만들어서 일단 해보는중
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        binding = null;
    }
}

 

여기는 프래그먼트를 만들면 나오는 클래스에 간단한 변수 대입과 어댑터를 만들어놓았고

EditText와 Button의 리스너를 만들어놨다

 

버튼 리스너

채팅을 입력하고 버튼을 누르면 채팅 리스트에 추가되며 어댑터의 데이터가 변경되었다는 것을 메소드로 알려준다

 

 

AIChatAdapter.java

package com.zynar.starvoca.adpaters;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.zynar.starvoca.R;
import com.zynar.starvoca.databinding.CustomChatOtherBinding;
import com.zynar.starvoca.databinding.CustomChatUserBinding;
import com.zynar.starvoca.items.Chat;
import com.zynar.starvoca.items.UserAccount;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import de.hdodenhof.circleimageview.CircleImageView;

public class AIChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private final List<Chat> chatList;
    private final UserAccount userAccount;
    private final int SYSTEM_CHAT = 0, USER_CHAT = 1, OTHER_CHAT = 2;

    public AIChatAdapter(List<Chat> chatList) {
        this.userAccount = UserAccount.getInstance();
        this.chatList = chatList;
    }

    @Override
    public int getItemViewType(int position) {
        if(chatList.get(position).getId().equals("") && chatList.get(position).getNickname().equals("")) {
            return SYSTEM_CHAT;
        } else if(userAccount.getNickname().equals(chatList.get(position).getNickname())) {
            return USER_CHAT;
        } else {
            return OTHER_CHAT;
        }
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
        if (viewType == USER_CHAT) {
            CustomChatUserBinding binding = CustomChatUserBinding.inflate(layoutInflater, parent, false);
            return new UserChatViewHolder(binding);
        }
        else if(viewType == OTHER_CHAT) {
            CustomChatOtherBinding binding = CustomChatOtherBinding.inflate(layoutInflater, parent, false);
            return new OtherChatViewHolder(binding);
        } else {
            // 시스템 메시지 추가 예정
            return null;
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if(holder instanceof UserChatViewHolder) {
            ((UserChatViewHolder) holder).bind(chatList.get(position));
        } else if(holder instanceof OtherChatViewHolder) {
            ((OtherChatViewHolder) holder).bind(chatList.get(position));
        } else {
            // 시스템 메시지 추가 예정
        }
    }

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

    public class UserChatViewHolder extends RecyclerView.ViewHolder {

        CustomChatUserBinding binding;

        public UserChatViewHolder(@NonNull CustomChatUserBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }

        public void bind(Chat chat) {
            binding.tvMessage.setText(chat.getMessage());
            binding.tvDate.setText(new SimpleDateFormat("HH:mm", Locale.getDefault()).format(chat.getDate()));
            binding.tvReadNumberOfPeople.setText(String.valueOf(chat.getReadNumbs()));
        }
    }

    public class OtherChatViewHolder extends RecyclerView.ViewHolder {

        CustomChatOtherBinding binding;

        public OtherChatViewHolder(@NonNull CustomChatOtherBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }

        public void bind(Chat chat) {
            binding.tvMessage.setText(chat.getMessage());
            binding.tvDate.setText(new SimpleDateFormat("HH:mm", Locale.getDefault()).format(chat.getDate()));
            binding.tvReadNumberOfPeople.setText(String.valueOf(chat.getReadNumbs()));
            // 상대 프로필 이미지 추가 예정
        }
    }
}

어댑터 전체코드인데 설명이 필요한 사람은 아래 설명을 보면된다

 


 

먼저 리사이클러뷰는 나의 채팅인지 상대방의 채팅인지에 따라서 레이아웃이 다르므로

뷰 홀더를 여러개 만들어야한다

 

뷰를 하나만 만들고 아이디도 같이 해서 만드는 대신 return 하는 뷰를 다르게 하면 어떨까 했는데

아이디 오류가 나서 이렇게 했다

 

일단 뷰 홀더를 상속받아 리사이클러뷰 어댑터를 만들고 생성자도 알아서 만든다

뷰홀더를 제외한 나머지는 알트 엔터 누르면 자동으로 만들어짐

 

 

이제 뷰홀더를 만들어야하는데 일단 나의 채팅 뷰홀더, 상대방 채팅 뷰홀더를 따로 만든다

그리고 각각의 레이아웃 바인딩 클래스를 선언하고 텍스트같은 것을 대입하는 메소드를 만든다

 

 

그리고 나의 채팅인지 아닌지 닉네임 같은 것을 대조해서 확인하도록 한다

이거는 오버라이드 받으면 된다

 

 

이제 나의 채팅과 상대방 채팅에 따라서 바인딩을 받고 뷰홀더를 생성하면된다

 

 

 

 

 

728x90
반응형