ListView는 item이 많아지면 속도가 느림 findViewById 가 많아지면 속도가 느려진다
view item은 재사용하지만 참조변수는 지역변수라 매번 찾아오느라 속도에 영향을 준다
이런 속도 퍼포먼스를 해결하기 위해 참조변수들을 객체로 만들어 놓고 주소를 저장하도록함 (ViewHolder) view들은 모두 가질 수 있는 Tag를 사용하여 홀더와 자식뷰를 연결해둬 매번 find를 하지 않도록 했다
ListView 와 GridView를 합쳐 RecicleView 만듦
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;
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);
}
}
}
<?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 : 카드를 얼마나 띄울거니?
멤버변수와 생성자 만들기
RecyclerView.ViewHolder를 상속받은 itemview의 참조변수를 가진 이너 클래스 만들기
내가 만든 item 설계도면에 만든 view들을 여기서 파인드 해줌!!
얘가 뷰한테 태그를 달아서 참조변수를 매번 만들지 않고 재사용가능하게 함
뷰를 만드는 onCreateViewHolder 메소드
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();
}
});
}
}
}
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 처리해야한다...
}
}
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);
}
});
}
}