ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [UI] RecyclerView 공부
    Android/UI 2021. 7. 31. 13:04

    출처: https://recipes4dev.tistory.com/154?category=790402

    • RecyclerView

    public class RecyclerView extends ViewGroup implements ScrollingView....etc

     

    • ViewHolder

    public static abstract class RecyclerView.ViewHolderpublic static abstract class RecyclerView.Adapter

    RecyclerView.ViewHolder

     

    • Adapter<Viewholder구현 클래스>

    public static abstract class RecyclerView.Adapter

    RecyclerView.Adapter<VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder>

     

    • RecyclerView.LayoutManager

    public static abstract class RecyclerView.LayoutManager

    Known direct subclassesGridLayoutManager, LinearLayoutManager, StaggeredGridLayoutManager

     

    RecyclerView.Adapter, RecyclerView.ViewHolder는 추상클래스로 구현이 필요하다.

    RecyclerView.LayoutManager는 구현필요없이 안드로이드에서 제공해주는 위의 레이아웃 매니저들을 사용할 수도 있다.


    채팅방의 대화목록을 가져와서 RecyclerView의 각 아이템뷰 하나에 대화를 하나씩 보여주는 예제

     

     

    1. RecyclerView가 출력될 곳을 정의한다.

     

    activity_chatting_room.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="@color/activity_chatting_background"
        tools:context=".chatting.ChattingRoomActivity">
    
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerview_chatting"
                android:paddingTop="@dimen/activity_chatting_recyclerView_paddingTop"
                android:layout_width="match_parent"
                android:layout_height="match_parent" >
    
            </androidx.recyclerview.widget.RecyclerView>
    
    </LinearLayout>

    2. 로컬로 테스트하기 위하여 데이터 셋을 만들어준다. 

     

    ChattingItem.java

    public class ChattingItem {
        private String userName; //대화를 보낸 사용자 이름
        private String time; //보낸 시간
        private String message; //메세지 내용
        private int viewType; //left 1, right 2 //내가 보냈으면 2, 상대방이 보냈으면 1
    
        public ChattingItem(String userName, String message, String time, int viewType) {
            this.userName = userName;
            this.message = message;
            this.time = time;
            this.viewType = viewType;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public String getTime() {
            return time;
        }
    
        public void setTime(String time) {
            this.time = time;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public int getViewType() { //ViewHolder의 ViewType을 정할 때 사용될 예정
            return viewType;
        }
    
        public void setViewType(int viewType) {
            this.viewType = viewType;
        }
    }

    카카오톡의 대화방의 메세지 내용을 보면

    상대방이 보낸 메세지는 왼편에. 내가 보낸 대화내용은 오른쪽에 표시된다.

    대화를 누가 보냈느냐에 따라 리싸이클러뷰의 아이템에 해당하는 뷰의 구조가 달라지게 된다.

    int값 viewType값을 통해 어떠한 형태의 view를 만들것인지 선택할 수 있다.


    3. 만들어질 뷰에 해당하는 형태의 xml을 선언

     

    chatting_left_item.xml (상대방이 보낸 대화라면 만들어질 뷰)

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/chatting_right_item_rootlayout_layout_marginTop"
        android:baselineAligned="false"
        android:orientation="vertical">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">
    
            <ImageView
                android:id="@+id/img_user_profile_l"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:src="@drawable/test"/>
    
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical">
    
                <TextView
                    android:id="@+id/txt_userName_l"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="left"
                    android:text="홍길동" />
    
                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="left"
                    android:orientation="horizontal">
    
                    <TextView
                        android:id="@+id/txt_message_l"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:background="@color/chatting_left_item_textView_message_background"
                        android:maxWidth="200dp"
                        android:padding="@dimen/chatting_left_item_txt_message_padding"
                        android:text="안녕하세요. 날씨가 많이 덥네요ㅎㅎ"
                        android:textSize="@dimen/chatting_left_item_txt_message_textSize" />
    
                    <TextView
                        android:id="@+id/txt_time_l"
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:layout_marginLeft="@dimen/chatting_left_item_txt_time_layout_marginLeft"
                        android:gravity="bottom"
                        android:text="오전 10:40"
                        android:textSize="@dimen/chatting_left_item_txt_time_textSize" />
                </LinearLayout>
            </LinearLayout>
            
        </LinearLayout>
        
    </LinearLayout>

    chatting_right_item.xml (내가 보낸 대화라면 만들어질 아이템뷰)

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/chatting_right_item_rootlayout_layout_marginTop"
        android:baselineAligned="false"
        android:orientation="vertical">
    
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right"
            android:orientation="horizontal">
    
            <TextView
                android:id="@+id/txt_time_r"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_marginRight="@dimen/chatting_right_item_txt_time_layout_marginRight"
                android:gravity="bottom"
                android:text="오전 9:10"
                android:textSize="@dimen/chatting_right_item_txt_time_textSize" />
    
            <TextView
                android:id="@+id/txt_message_r"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@color/chatting_right_item_textView_message_background"
                android:maxWidth="200dp"
                android:padding="@dimen/chatting_right_item_txt_message_padding"
                android:text="Firebase Realtime Database 는  JSON형태의 NoSQL 데이터베이스입니다. 그리고 연결된 모든 클라이언트에 실시간 동기화되는 특징이 있습니다. 이로 인하여 일반적인 SQL 데이터베이스로 구현하는 웹개발과는 다른 특징을 가집니다."
                android:textSize="@dimen/chatting_right_item_txt_message_textSize" />
        </LinearLayout>
    </LinearLayout>

     

    지금까지의 과정 - 데이터를 넣어줄 ArrayList를 생성했고, 리싸이클러뷰의 아이템 뷰에 해당하는 xml들을 선언했다.


    Adapter와 ViewHolder에 대하여...

     

    ViewHolder는 리싸이클러뷰내에서 하나의 아이템에 해당하는 뷰로 생각될 수 있다. 

     

    앱이 시작되면 Adapter는 설정된 LayoutManager에 따라 특정개수의 ViewHolder만 만든다.

    사용자가 리싸이블러뷰를 밑으로 스크롤하면 가장 윗부분에 있던 ViewHolder는 재활용되어 밑에서 새로 만들어질 아이템에 다시 사용된다. (재활용된다는 것은 이 View를 다시만들지 않고 결국 이View의 구조를 그래도 사용한다는 것이다)

    그렇기 때문에 아이템뷰에 데이터만 재설정만 해주면된다.

     

    구조화된 코드부터 올리고 설명

    public class RecyclerViewAdapterChattingRoom extends RecyclerView.Adapter<RecyclerViewAdapterChattingRoom.ViewHolder> {
        //멤버
        private ArrayList<ChattingItem> chattingItems;
    
        //inner class ViewHolder
        public class ViewHolder extends RecyclerView.ViewHolder { //ViewHolder상속 및 구현
    
            private int viewType; //뷰 타입
            private TextView txt_time; //시간
            private TextView txt_message; //메세지
    
            private ImageView img_user_profile; //사용자 프로필 사진
            private TextView txt_userName; //사용자 이름
    
            public ViewHolder(View itemView, int viewType) {
                super(itemView);
                this.viewType=viewType; //코드 짜면서 오류 발생한 구간 - viewType에 값이 들어가지 않아 데이터 셋의 데이터가 적용이 되지 않았었다.
                if (this.viewType == 2) {
                    txt_time = itemView.findViewById(R.id.txt_time_r);
                    txt_message = itemView.findViewById(R.id.txt_message_r);
                } else if (this.viewType == 1) {
                    txt_time = itemView.findViewById(R.id.txt_time_l);
                    txt_message = itemView.findViewById(R.id.txt_message_l);
                    txt_userName = itemView.findViewById(R.id.txt_userName_l);
                    img_user_profile = itemView.findViewById(R.id.img_user_profile_l);
                } else {
                    //to do!!!! chatting_item_center.xml 구현 할 경우
                }
            }
    
            public void setViewHolder(ChattingItem chattingItem) {
                if (this.viewType == 2) {
                    txt_time.setText(chattingItem.getTime());
                    txt_message.setText(chattingItem.getMessage());
                } else if (this.viewType == 1) {
                    txt_time.setText(chattingItem.getTime());
                    txt_message.setText(chattingItem.getMessage());
                    //txt_img_userProfile   to do!! local에서 firebase로 바뀌면 프로필 설정 추가
                    txt_userName.setText(chattingItem.getUserName());
                }
            }
        }
    
        //생성자
        public RecyclerViewAdapterChattingRoom(ArrayList<ChattingItem> chattingItems) {
            this.chattingItems = chattingItems; //ArrayList 셋팅
        }
    
        //메서드
    
        //각 아이템의 ViewType을 반환함. 여기서 반환 된 ViewType-> onCreateViewHolder로 넘어감. overriding안하면 default 0
        @Override
        public int getItemViewType(int position) {
            return this.chattingItems.get(position).getViewType();
        }
    
        @NonNull
        @Override //ViewHolder 생성(-> 구조화된 View생성) (Adapter call back 메서드)
        public RecyclerViewAdapterChattingRoom.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            printLog("onCreateViewHolder호출- ViewType: " + viewType);
    
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    
            if (viewType == 2) { //나
                View itemView = inflater.inflate(R.layout.chatting_right_item, parent, false);
                return new RecyclerViewAdapterChattingRoom.ViewHolder(itemView, viewType);
            } else { //다른 사용자
                View itemView = inflater.inflate(R.layout.chatting_left_item, parent, false);
                return new RecyclerViewAdapterChattingRoom.ViewHolder(itemView, viewType);
            }
        }
    
        @Override //ViewHolder 바인딩(-> 데이터 셋팅) (Adapter call back 메서드)
        public void onBindViewHolder(@NonNull RecyclerViewAdapterChattingRoom.ViewHolder viewHolder, int position) {
            printLog("onBindViewHolder호출- position: " + position + ", ViewType: " + viewHolder.getItemViewType());
            viewHolder.setViewHolder(this.chattingItems.get(position));
        }
    
        @Override
        public int getItemCount() { //RecyclerView의 전체 아이템 개수
            return chattingItems.size();
        }
    
        public void printLog(String string) {
            Log.d("RECYCLERVIEW", string);
        }
        
    }

     

     

    설명

     

    Adapter의 생성자와 내부 멤버 ArrayList

     

    나중에 테스트 데이터에 쓰일 ArrayList를 액티비티에서 만들것인데 그 ArrayList를 넘겨 받기 위해 내부멤버에 ArrayList선언을 하고 생성자에서 받을 수 있도록 하였다.

     

    viewType을 정의하는 방법

    콜백 메서드- 리싸이클러뷰에 몇번째에 해당하는 아이템 뷰인지에 따라 (position을 사용하여) 사용자가 viewType을 정의한다.

    //각 아이템뷰의 ViewType를 정함. 
    //여기서 반환 된 ViewType-> onCreateViewHolder로 넘어감. overriding안하면 default 0
        @Override
        public int getItemViewType(int position) {
            return this.chattingItems.get(position).getViewType();
        }

     

     

    onCreateViewHolder 콜백 메서드(Adapter 콜백 메서드) <- 새로운 ViewHolder가 만들어져야 할 경우 자동으로 호출된다.

     @NonNull
        @Override //ViewHolder 생성(-> 구조화된 View생성) (Adapter call back 메서드)
        public RecyclerViewAdapterChattingRoom.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            printLog("onCreateViewHolder호출- ViewType: " + viewType);
    
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    
            if (viewType == 2) { //나
                View itemView = inflater.inflate(R.layout.chatting_right_item, parent, false);
                return new RecyclerViewAdapterChattingRoom.ViewHolder(itemView, viewType);
            } else { //다른 사용자
                View itemView = inflater.inflate(R.layout.chatting_left_item, parent, false);
                return new RecyclerViewAdapterChattingRoom.ViewHolder(itemView, viewType);
            }
        }

     

    인자의 parent는 아이템뷰가 붙을 부모 ViewGroup에 해당된다. viewType을 설정하는 것은 나중에 보자. viewType에 따라 다른 형태의 xml을 infaltion하고 있다. 그런다음 만들어진 View itemView의 참조를 ViewHolder의 생성자에 넘긴다.

     

     

    onBindViewHolder 콜백 메서드를 보기 전에 ViewHolder의 구현을 보자.

    public ViewHolder(View itemView, int viewType) {
                super(itemView);
                this.viewType=viewType; //코드 짜면서 오류 발생한 구간 - viewType에 값이 들어가지 않아 데이터 셋의 데이터가 적용이 되지 않았었다.
                if (this.viewType == 2) {
                    txt_time = itemView.findViewById(R.id.txt_time_r);
                    txt_message = itemView.findViewById(R.id.txt_message_r);
                } else if (this.viewType == 1) {
                    txt_time = itemView.findViewById(R.id.txt_time_l);
                    txt_message = itemView.findViewById(R.id.txt_message_l);
                    txt_userName = itemView.findViewById(R.id.txt_userName_l);
                    img_user_profile = itemView.findViewById(R.id.img_user_profile_l);
                } else {
                    //to do!!!! chatting_item_center.xml 구현 할 경우
                }
     }

    onCreateViewHolder에서 만들어진 itemView의 참조를 ViewHolder의 생성자에 넘겼었다.

    ViewHolder의 생성자를 보면 넘겨받은 itemView  참조를 통해 자식 뷰들을 바인딩 하고 있다.

     

    이제 아이템뷰가 만들어졌고 내부의 모든 자식들도 뷰 바인딩 시켰다. 이제 아이템뷰에 데이터만 셋팅하면 된다.

    onBindViewHolder 콜백 메서드가 자동으로 호출되기 때문에 요 메서드에서 데이터를 적절하게 셋팅하면된다.

     

    Adapter클래스의 내가 만들어 놓은 멤버메서드. ViewHolder의 데이터를 셋팅하는 메서드로 onBindViewHolder에서 쓰기위해 만들었다.

     public void setViewHolder(ChattingItem chattingItem) {
                if (this.viewType == 2) {
                    txt_time.setText(chattingItem.getTime());
                    txt_message.setText(chattingItem.getMessage());
                } else if (this.viewType == 1) {
                    txt_time.setText(chattingItem.getTime());
                    txt_message.setText(chattingItem.getMessage());
                    //txt_img_userProfile   to do!! local에서 firebase로 바뀌면 프로필 설정 추가
                    txt_userName.setText(chattingItem.getUserName());
                }
            }

     

    onBindViewHolder(Adapter 콜백 메서드)

    @Override //ViewHolder 바인딩(-> 데이터 셋팅) (Adapter call back 메서드)
        public void onBindViewHolder(@NonNull RecyclerViewAdapterChattingRoom.ViewHolder viewHolder, int position) {
            printLog("onBindViewHolder호출- position: " + position + ", ViewType: " + viewHolder.getItemViewType());
            viewHolder.setViewHolder(this.chattingItems.get(position));
        }

    인자는 viewHolder와 position인데 리싸이클러뷰 내부에서 몇번째 위치에 해당하는 뷰 홀더인가가 넘어온다.

    위에서 만들어준 setViewHolder메서드로 데이터를 셋팅한다.

     

     

    어댑터의 콜백메서드. RecyclerView의 총 아이템 개수를 결정한다. 

     @Override
        public int getItemCount() { //RecyclerView의 전체 아이템 개수
            return chattingItems.size();
        }

    액티비티.java

    public class ChattingRoomActivity extends AppCompatActivity { //firebase 연동 추가
        RecyclerView recyclerView_chatting_room;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_chatting_room);
    
            recyclerView_chatting_room = findViewById(R.id.recyclerview_chatting);
    
            ArrayList<ChattingItem> chattingItems = new ArrayList<ChattingItem>();
    
            //테스트 데이터
            String me = "";
            String you = "";
            for (int i = 1; i <= 200; i++) {
                if ((i % 2) == 0) {
                    chattingItems.add(new ChattingItem("나", "나" + me, "오전 10:11", 2)); //right 2
                    me = me + "test";
                } else {
                    chattingItems.add(new ChattingItem("모르는", "상대방의 메세지" + you, "오전 10:11", 1)); //left 1
                    you = you + "test";
                }
            }
    
            LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
            recyclerView_chatting_room.setLayoutManager(layoutManager);
            RecyclerViewAdapterChattingRoom chattingAdapter = new RecyclerViewAdapterChattingRoom(chattingItems);
            recyclerView_chatting_room.setAdapter(chattingAdapter);
    
        }
    }

    리싸이클러뷰에 LinearLayoutManager를 사용하였따. 리니어 레이아웃 매니저의 인자를 Horizontal, true등등으로 아이템들이 출력되는 결과를 따로 확인해보자.

     

    리싸이클러뷰에 어댑터를 설정하였다.

     

     

    댓글

Designed by Tistory.