AdapterView_RecyclerView

소정·2023년 2월 17일
0

Android_with_Java

목록 보기
11/33

RecyclerView

[1] RecyclerView의 탄생이유

ListView는 item이 많아지면 속도가 느림 findViewById 가 많아지면 속도가 느려진다
view item은 재사용하지만 참조변수는 지역변수라 매번 찾아오느라 속도에 영향을 준다
이런 속도 퍼포먼스를 해결하기 위해 참조변수들을 객체로 만들어 놓고 주소를 저장하도록함 (ViewHolder) view들은 모두 가질 수 있는 Tag를 사용하여 홀더와 자식뷰를 연결해둬 매번 find를 하지 않도록 했다

ListView 와 GridView를 합쳐 RecicleView 만듦

ListViewHolder 사용방법

  1. 데이터 준비
  2. item layout 시안 준비
  3. MyAdapter.java파일 준비

    3-1) 아답터 extends 받음
    3-2) 참조변수로 아이템과 Context를 만들고 생성자 생성

3-3) 이 셋 메소드는 백이면 백 이렇게 쓴다

3-4) 내가 신경 써야 할 곳 getView()
3-4-1) Create view / bind view 해줘야 한다는 걸 기억하고 주석 달아놓기
3-4-1-1) Create view에서 할 일
①재활용 할 뷰가 있는지 확인 if문으로 더이상 재활용 할 뷰가 없을때만 만들어달라고 하기 if(view == null)
②listview_item.xml 문서를 읽어와서 View 객체로 생성해주는 Inflater 소환

LayoutInflater inflater = LayoutInflater.from(context);

③ view에 xml파일 대입해주기 (inflate를 통해)

view = inflater.inflate(R.layout.listview_item, viewGroup, false);

④ 우선 리턴해보기

⑤ 위에까지 하고 MainActivity에서 준비 할 일
a. ListView와 내 어답터 참조변수로 가져오기
b. 레이아웃 findViewById
c. new MyAdapter
d. layout.setAdapter(adapter);

⑥ 참조변수도 재활용 하기 위한 작업 만들어진 view안에 있는 자식 뷰들의 참조값을 tag로 저장하기 자식객체의 참조변수를 멤버로 가지는 별도의 클래스를 설계하여 객체로 생성

ViewHolder holder = new ViewHolder(view);

//뷰에 태그문 셋팅
view.setTag(holder);

3-4-1-2) bind view 하는 일
①현재 보여줄 데이터를 얻어오기

String item = items.get(i);

②아이템뷰 안에 태그로 저장되어 있던 viewHolder 객체를 빼오기

ViewHolder holder = (ViewHolder) view.getTag();

③set 하고 return

holder.tv.setText(item);

return view;

adapter 총 소스

package com.bsj0420.ex26adapterlistviewholder;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.ArrayList;

public class MyAdapter extends BaseAdapter {

    Context context;
    ArrayList<String> items;

    public MyAdapter(Context context, ArrayList<String> items) {
        this.context = context;
        this.items = items;
    }

    @Override
    public int getCount() {
        return items.size();
    }

    @Override
    public Object getItem(int i) {
        return items.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {

        //1. Create view
        //1)재활용 할 뷰가 있는지 확인
        if(view == null) {
            //listview_item.xml 문서를 읽어와서 View 객체로 생성해주는 Inflater 소환
            LayoutInflater inflater = LayoutInflater.from(context);
            view = inflater.inflate(R.layout.listview_item, viewGroup, false);
            //root는 listview_item를 붙일 부모레이아웃 : viewGroup
            //root는 return 하면 알아서 붙는 곳이라 내가 부모레이아웃을 맘대로 참조하면 안됨
            //하지만 뷰 객체가 있어야 사이즈를 알고 만들어짐 
            //그래서 부모 레이아웃의 매개변수 viewGroup를 쓰고
            //부모가 누군지는 알려주지만 지금 당장은 만들지 말아라 라는 attachToRoot에 false를 쓴다!

            //참조변수도 재활용 하기 위한 작업
            //만들어진 view안에 있는 자식 뷰들의 참조값을 tag로 저장하기
            //자식객체의 참조변수를 멤버로 가지는 별도의 클래스를 설계하여 객체로 생성
            ViewHolder holder = new ViewHolder(view);

            //뷰에 태그문 셋팅
            view.setTag(holder);
        }

        //2. bind view
        //현재 보여줄 데이터를 얻어오기
        String item = items.get(i);

        //아이템뷰 안에 태그로 저장되어 있던 viewHolder 객체를 빼오기
        ViewHolder holder = (ViewHolder) view.getTag();

        holder.tv.setText(item);

        return view;
    }
    
    //항목 1개의 ''itemview안에 있는 자식뷰들'' 참조변수를 멤버로 가지는 이너 클래스
    //생성자도 만듦
    class ViewHolder {
        TextView tv;

        public ViewHolder(View itemView) {
            tv = itemView.findViewById(R.id.tv);
        }
    }
}



[2] RecyclerView 사용

  • ListView와 GridView를 개량한 AdapterView
  • layoutManager(얘가 뭔지)와 orientation(방향) 를 안해주면 화면에 안보임

사용방법

1) Adapter without 제네릭

  1. 데이터 준비 item 담을 class 준비
  2. item layout 시안 준비
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:contentPadding="16dp"
    app:cardElevation="4dp"
    app:cardCornerRadius="8dp"
    android:layout_margin="4dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="NAME"
            android:textColor="@color/black"
            android:textStyle="bold"
            android:textSize="24sp"/>

        <TextView
            android:id="@+id/tv_msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/tv_name"
            android:text="mesaage"/>
    </RelativeLayout>

</androidx.cardview.widget.CardView>

🧨 Card View
FrameLayout을 상속받아 만듦
카드뷰는 그 안에 다른 레이아웃을 두고 쓰는게 배치에 용이함
속성
contentPadding : 카드뷰만의 padding
cardElevation : 카드를 얼마나 띄울거니?

  1. RecyclerView.Adapter 상속받은 MyAdapter 만들기
  • 멤버변수와 생성자 만들기

  • RecyclerView.ViewHolder를 상속받은 itemview의 참조변수를 가진 이너 클래스 만들기
    내가 만든 item 설계도면에 만든 view들을 여기서 파인드 해줌!!

    얘가 뷰한테 태그를 달아서 참조변수를 매번 만들지 않고 재사용가능하게 함

  • 뷰를 만드는 onCreateViewHolder 메소드

  • 뷰와 저장된 데이터를 붙여주는 onBindViewHolder 메소드

MyAdapter 총 소스

package com.bsj0420.ex27recyclerview;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

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

import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter {

    Context context;

    ArrayList<Item> items;

    public MyAdapter(Context context, ArrayList<Item> items) {
        this.context = context;
        this.items = items;
    }

    //재활용할 뷰가 없으면 뷰를 만들기 위해 자동으로 호출되는 메소드
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { //화면에 보이는 만큼만 호출
        //1. inflater 만들기
        LayoutInflater inflater = LayoutInflater.from(context);
        View itemView = inflater.inflate(R.layout.recyclerview_item, parent, false);

        // RecyclerView.ViewHolder 리턴값이 ViewHolder 이기 때문에 홀더 만들어야함
        VH holder = new VH(itemView);

        return holder;
    }

    //현재 연결할 번째(position) 데이터를 뷰에 넣어주는 작업을 위해 호출되는 메소드
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { //전체 개수만큼 호출됨
        //position : 현재 연결될 item번째수
        //첫번째 파라미터는 위에서 리턴 받은 holder임
        //첫번째 파라미터 holder가 가진 뷰를 참조변수를 통해 값 설정 - 먼소리요
        VH vh = (VH)holder;
        
        //현재 번째 아이템 요소를 얻어와서 뷰들에 설정
        Item item  = items.get(position);
        
        // 셋팅
        vh.tvName.setText(item.nane);
        vh.tvMsg.setText(item.message);

    }

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

    //item 한개 view 안에 있는 "자식뷰들의 참조값을 저장하는 참조변수들을 멤버"로 가지는 이너 클래스
    class VH extends RecyclerView.ViewHolder { //ViewHolder를 상속하여 Tag를 안해도 되게 함
		
        //내가 만든 item 설계도면에 만든 view들을 여기서 파인드 해줌!!
        TextView tvName;
        TextView tvMsg;

        public VH(@NonNull View itemView) { //itemView는 현재 CardView
            super(itemView);

            tvName = itemView.findViewById(R.id.tv_name);
            tvMsg = itemView.findViewById(R.id.tv_msg);


            //itemview를 클릭 햇을 때 반응하는 리스너 처리
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    
                    //클릭한 아이템의 위치 index번호
                    int position = getLayoutPosition();

                    //클릭한 번째 아이템요소 데이터 얻어오기
                    Item item = items.get(position);
                    
                    Toast.makeText(context, position +" 번째, " + item.nane, Toast.LENGTH_SHORT).show();
                }
            });

        }
    }

}
  1. 화면에 출력

package com.bsj0420.ex27recyclerview;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    //대량의 데이타 준비
    ArrayList<Item> items = new ArrayList<>();

    RecyclerView recyclerView;
    MyAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //데이터 준비
        items.add(new Item("sam", "Hello"));
        items.add(new Item("robin", "Hello ion"));
        items.add(new Item("kim", "Nice"));
        items.add(new Item("park", "Have a goood day"));
        items.add(new Item("lee", "오 예쓰"));
        items.add(new Item("sam", "Hello"));
        items.add(new Item("robin", "Hello ion"));
        items.add(new Item("kim", "Nice"));
        items.add(new Item("park", "Have a goood day"));
        items.add(new Item("lee", "오 예쓰"));
        items.add(new Item("sam", "Hello"));
        items.add(new Item("robin", "Hello ion"));
        items.add(new Item("kim", "Nice"));
        items.add(new Item("park", "Have a goood day"));
        items.add(new Item("lee", "오 예쓰"));

        recyclerView = findViewById(R.id.recyclerview);
        adapter = new MyAdapter(this,items);

        recyclerView.setAdapter(adapter);

        //리사이클러뷰에 아이템뷰 한개를 클릭했을 때 반응하기 -> 반응하는 리스너 처리가 없음
        //리사이클은 성능과 커스텀에 촛점을 맞춤
        //그래서 처리하려면...
        //아이템뷰 한개를 만드는 Myadapter에서 click 처리해야한다...


    }
}

2) Adapter with 제네릭

0. 메인화면

<?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="#888888"
    tools:context=".MainActivity">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_add"
            android:text="add"
            android:layout_margin="2dp"
            android:backgroundTint="#4CAF50"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <Button
            android:id="@+id/btn_del"
            android:text="delete"
            android:layout_margin="2dp"
            android:layout_width="wrap_content"
            android:backgroundTint="#F44336"
            android:layout_height="wrap_content"/>

        <Button
            android:id="@+id/btn_linear"
            android:text="Linear"
            android:layout_margin="2dp"
            android:layout_width="wrap_content"
            android:backgroundTint="#673AB7"
            android:layout_height="wrap_content"/>

        <Button
            android:id="@+id/btn_grid"
            android:text="grid"
            android:layout_margin="2dp"
            android:layout_width="wrap_content"
            android:backgroundTint="#FFC107"
            android:layout_height="wrap_content"/>

    </LinearLayout>


    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:padding="16dp"
        android:layout_marginTop="10dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        android:orientation="vertical"
        />


</LinearLayout>

1. 데이터 준비 item 담을 class 준비

package com.bsj0420.ex28recyclerview2;

public class Item {

    String name;
    String role;
    int profileImgId;
    int imgId;

    public Item(String name, String role, int profileImgId, int imgId) {
        this.name = name;
        this.role = role;
        this.profileImgId = profileImgId;
        this.imgId = imgId;
    }

}

2. item layout 시안 준비

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:cardCornerRadius="16dp"
    android:layout_marginTop="4dp"
    android:layout_marginBottom="4dp"
    android:layout_marginLeft="2dp"
    android:layout_marginRight="2dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <de.hdodenhof.circleimageview.CircleImageView
            android:id="@+id/civ_profile"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:src="@drawable/crew_luffy"
            android:layout_margin="8dp"
            app:civ_border_width="2dp"
            app:civ_border_color="#FF000000"/>

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="NAME"
            android:textSize="24sp"
            android:textStyle="bold"
            android:layout_toRightOf="@+id/civ_profile"
            android:layout_marginTop="4dp"
            android:textColor="@color/black"/>

        <TextView
            android:id="@+id/tv_role"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="role"
            android:layout_alignLeft="@+id/tv_name"
            android:layout_below="@+id/tv_name"/>

        <ImageView
            android:id="@+id/iv_img"
            android:layout_width="match_parent"
            android:layout_height="240dp"
            android:src="@drawable/bg_one01"
            android:layout_below="@+id/civ_profile"
            android:scaleType="centerCrop"/>


    </RelativeLayout>

</androidx.cardview.widget.CardView>

3. RecyclerView.Adapter 상속받은 MyAdapter 만들기

package com.bsj0420.ex28recyclerview2;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

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

import java.util.ArrayList;

import de.hdodenhof.circleimageview.CircleImageView;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.VH> { //이너 클래스는 언제나 (아웃클래스.내 클래스)

    Context context;
    ArrayList<Item> items;

    public MyAdapter(Context context, ArrayList<Item> items) {
        this.context = context;
        this.items = items;
    }

    @NonNull
    @Override
    public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        View itemView = LayoutInflater.from(context).inflate(R.layout.recycle_item, parent, false);

        VH holder = new VH(itemView);

        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull VH holder, int position) {

        //현재번째 아이템요소 얻어오기
        Item item = items.get(position);

        //VH 가 가지고 있는 자식뷰들에 item값을 설정(연결)
        holder.tvName.setText(item.name);
        holder.tvRole.setText(item.role);
        holder.civProfile.setImageResource(item.profileImgId);
        holder.iv.setImageResource(item.imgId);

    }

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


    //아이템 뷰 한개 안에 있는 자식뷰들의 참조 변수를 저장하는 이너 클래스
    class VH extends RecyclerView.ViewHolder {

        CircleImageView civProfile;
        TextView tvName;
        TextView tvRole;
        ImageView iv;

        public VH(@NonNull View itemView) {
            super(itemView);

            civProfile = itemView.findViewById(R.id.civ_profile);
            tvName = itemView.findViewById(R.id.tv_name);
            tvRole = itemView.findViewById(R.id.tv_role);
            iv = itemView.findViewById(R.id.iv_img);

            //항목하나 클릭할 때 반응
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    //위치 인덱스 번호 얻어오기
                    int position = getLayoutPosition();

                    Item item = items.get(position);

                    Toast.makeText(context, "position : "+ position + "item : "+ item.name, Toast.LENGTH_SHORT).show();
                }
            });

        }

    }
    
}

4. 화면에 출력

package com.bsj0420.ex28recyclerview2;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    ArrayList<Item> items = new ArrayList<Item>();

    RecyclerView recyclerView;
    MyAdapter adapter;

    Button btnAdd;
    Button btnDel;

    Button btnLnear;
    Button btnGrid;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //실무에선 db나 서버에서 데이터 읽어오는 작업을 한다
        items.add(new Item("루피", "선장", R.drawable.crew_luffy, R.drawable.bg_one01));
        items.add(new Item("조로", "부선장", R.drawable.crew_zoro, R.drawable.bg_one02));
        items.add(new Item("나미", "항해사", R.drawable.crew_nami, R.drawable.bg_one03));
        items.add(new Item("우솝", "저격수", R.drawable.crew_usopp, R.drawable.bg_one04));
        items.add(new Item("상디", "요리사", R.drawable.crew_sanji, R.drawable.bg_one05));
        items.add(new Item("초파", "의사", R.drawable.crew_chopper, R.drawable.bg_one06));
        items.add(new Item("니코로빈", "역사학자", R.drawable.crew_nicorobin, R.drawable.bg_one07));
        items.add(new Item("루피", "선장", R.drawable.crew_luffy, R.drawable.bg_one01));
        items.add(new Item("조로", "부선장", R.drawable.crew_zoro, R.drawable.bg_one02));
        items.add(new Item("나미", "항해사", R.drawable.crew_nami, R.drawable.bg_one03));
        items.add(new Item("우솝", "저격수", R.drawable.crew_usopp, R.drawable.bg_one04));
        items.add(new Item("상디", "요리사", R.drawable.crew_sanji, R.drawable.bg_one05));
        items.add(new Item("초파", "의사", R.drawable.crew_chopper, R.drawable.bg_one06));
        items.add(new Item("니코로빈", "역사학자", R.drawable.crew_nicorobin, R.drawable.bg_one07));
        items.add(new Item("루피", "선장", R.drawable.crew_luffy, R.drawable.bg_one01));
        items.add(new Item("조로", "부선장", R.drawable.crew_zoro, R.drawable.bg_one02));
        items.add(new Item("나미", "항해사", R.drawable.crew_nami, R.drawable.bg_one03));
        items.add(new Item("우솝", "저격수", R.drawable.crew_usopp, R.drawable.bg_one04));
        items.add(new Item("상디", "요리사", R.drawable.crew_sanji, R.drawable.bg_one05));
        items.add(new Item("초파", "의사", R.drawable.crew_chopper, R.drawable.bg_one06));
        items.add(new Item("니코로빈", "역사학자", R.drawable.crew_nicorobin, R.drawable.bg_one07));

        recyclerView = findViewById(R.id.recyclerview);
        adapter = new MyAdapter(this,items);

        recyclerView.setAdapter(adapter);

        //리사이클뷰는 클릭 리스너 없음 adpter에서 클릭이벤트 처리 해야함


        //추가 삭제 버튼
        btnAdd = findViewById(R.id.btn_add);
        btnDel = findViewById(R.id.btn_del);

        btnAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //리스트에 새로운 데이트를 추가
                items.add(0,new Item("NEW","해적단 신입",R.drawable.bg_one08, R.drawable.bg_one09));

                //아답터한테 데이터 변경됐다고 공지하기
                //adapter.notifyDataSetChanged(); //데이터 여러개가 바뀌었다 라는 뜻
                //내부적으로 데이터 전체를 다시 첨부터 셋팅함 -> 데이터 낭비
                //데이터를 완전히 클리어 했을때나 쓴다

                //띨롱 추가 했을때는
                //데이터 아이템 1개가 추가되었다고 공지
                //adapter.notifyItemMoved(); : 드로그앤 드롭 할때 순서 바꾸는 거
                adapter.notifyItemInserted(0);
                
                //스크롤 위치 조정
                recyclerView.scrollToPosition(0);
            }
        });

        btnDel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //아이템리스트에서 첫번째 데이터 제거
                items.remove(0);

                adapter.notifyItemRemoved(0);

            }
        });


        //뷰 바꾸기
        btnLnear = findViewById(R.id.btn_linear);
        btnGrid = findViewById(R.id.btn_grid);

        btnLnear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //리사이클뷰의 레이아웃매니저(배치관리자)를 새로이 설정
                LinearLayoutManager linearLayoutManager = new LinearLayoutManager(MainActivity.this,LinearLayoutManager.VERTICAL,false);

                recyclerView.setLayoutManager(linearLayoutManager);

            }
        });

        btnGrid.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                GridLayoutManager layoutManager = new GridLayoutManager(MainActivity.this,2);

                recyclerView.setLayoutManager(layoutManager);
            }
        });


    }
}
profile
보조기억장치

0개의 댓글