세라쌤의 IT 튜토리얼

안드로이드의 View Holder 패턴 본문

Android

안드로이드의 View Holder 패턴

issell 2019. 4. 2. 03:09

지난 포스팅에서 ListView를 배웠다. ListView의 단점은 스크롤을 넘길 때마다 새롭게 보일 View를 매번 findViewById()로 생성하기 때문에 아이템이 아주 많거나 스크롤을 빠르게 하는 경우, 스크롤이 부드럽지 않고 메모리를 상당히 많이 잡아 먹는 단점이 있었다. 따라서 각 View들을 처음 생성할 때만 생성해두고 이를 붙잡아다가(hold), 이후 화면에 보일 차례가 다시금 되었을 때 잡아두었던 View를 보여준다는 컨셉으로 View Holder 패턴을 사용하는 것이 좋다고 했다.

 


구현 순서 

기본적으로 ListView 구현 순서와 동일하고 몇가지가 추가되었다. 

 

1. Adapter 클래스에 내부 클래스를 추가한다. 

- 물론 반드시 내부 클래스일 필요는 없지만 Adapter 클래스에서만 사용할 것이므로 Adapter 클래스의 내부 클래스가 좋다.

- 이름은 아무거나 해도 된다. 여기서는 MyHolder로 이름 지었다.

- 이 클래스는 Adapter의 getView() 메서드에서 활용될 것이다.

public class MyAdapter extends BaseAdapter {

    // ....
   
    class MyHolder {
        TextView textView;
        ImageView imageView;
    }
	
    // .....
}

2. getView() 재정의 

- view 파라미터가 null인지 아닌지로 구분하여 null일 경우 view를 inflate하여 생성하고, 추가적으로 MyHolder 인스턴스를 생성하여 해당 view 파라미터의 tag형태로 인스턴스를 등록한다.  null이 아닐 경우는 tag로 등록되었던 MyHolder 인스턴스 꺼낸다. 두 가지 경우 모두 마지막은 해당 MyHolder 인스턴스의 View와 위젯의 내용을 재구성하여 최종적으로 view 파라미터를 다시 return한다. 

  * tag는 View 클래스가 가지는 setTag() / getTag()로 접근할 수 있는 인스턴스를 의미한다. 참고로 Object형이기 때문에 무엇을 tag로 지정하든 상관없다. 여기서는 MyHolder 객체를 저장할 보조 저장소(?)로 사용할 것이다.

public class MyAdapter extends BaseAdapter {

    Context context;
    ArrayList<ViewVo> list; // ViewVo는 하단 코드 참조

    class MyHolder {
        TextView textView;
        ImageView imageView;
    }

    // 생성자
    public MyAdapter(Context context, ArrayList<ViewVo> list) {
        this.context = context;
        this.list = list;
    }

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

    @Override
    public Object getItem(int i) { // i번째 아이템 (아무거나 해도 된다.)
        return list.get(i);
    }

    @Override
    public long getItemId(int i) { // i번째 아이템의 id (아무거나 해도 된다.)
        return i;
    }


	/////////////////////////////////////////////////////////// 
    @Override 
    public View getView(int i, View view, ViewGroup viewGroup) { // 이 부분에 View Holder 패턴을 적용 
        MyHolder myHolder;
        ViewVo vo = list.get(i); 
        if(view == null){ // i번 째 view의 최소 생성
            view = View.inflate(context, R.layout.item, null); // 아이템 레이아웃 inflate
            myHolder = new MyHolder(); // MyHoler 인스턴스 생성
            myHolder.textView = view.findViewById(R.id.item_text_view); // textView 생성
            myHolder.imageView = view.findViewById(R.id.item_image_view); // imageView 생성
            view.setTag(myHolder); // MyHolder 객체를 tag로 등록
        }
        else { // i번 째 view가 이미 생성된 이력이 있다면
            myHolder = (MyHolder)view.getTag(); // tag에서 hold되었던 MyHolder 인스턴스 꺼내기
        }
        myHolder.textView.setText(vo.text); // MyHolder 인스턴스의 textView에 텍스트 지정
        myHolder.imageView.setImageResource(vo.imageId); // 이미지 지정
        return view;
    }
    /////////////////////////////////////////////////////////// 
}

3. ViewVo 클래스 정의 (선택 사항)

예제에서는 ViewVo가 나오는데, ViewVo 클래스는 다음과 같이 정의했다. (별거 없다. 아이템 뷰에 이미지(res/drawable)와 텍스트가 있기 때문에 이를 하나의 클래스로 묶기 위해 만들었다.)

public class ViewVo {
    String text; // TextView에 띄울 텍스트
    int imageId; // ImageView에 띄울 res/drawable의 이미지 id 값

    // 생성자
    public ViewVo(String text, int imageId){
        this.text = text;
        this.imageId = imageId;
    }
}

4. item.xml

ListView의 아이템으로 보여줄 아이템 뷰다. 저번에 했던 포스트와 동일한 모양이다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
    android:id="@+id/item_image_view"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:src="@drawable/ic_launcher_foreground"/>
<TextView
    android:id="@+id/item_text_view"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="2"
    android:text="이부분은 TextView"
    android:gravity="center_vertical">
</TextView>
</LinearLayout>

5. 액티비티 구현

public class Ex01Activity extends AppCompatActivity {

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

        ArrayList<ViewVo> list = new ArrayList<>();
        
        // 반복문을 쓰려했지만 헷갈릴 분들이 계실까봐..
        list.add(new ViewVo("1번 텍스트", R.drawable.ic_launcher_foreground));
        list.add(new ViewVo("2번 텍스트", R.drawable.ic_launcher_foreground));
        list.add(new ViewVo("3번 텍스트", R.drawable.ic_launcher_foreground));
        list.add(new ViewVo("4번 텍스트", R.drawable.ic_launcher_foreground));
        list.add(new ViewVo("5번 텍스트", R.drawable.ic_launcher_foreground));
        list.add(new ViewVo("6번 텍스트", R.drawable.ic_launcher_foreground));
        list.add(new ViewVo("7번 텍스트", R.drawable.ic_launcher_foreground));
        list.add(new ViewVo("8번 텍스트", R.drawable.ic_launcher_foreground));
        list.add(new ViewVo("9번 텍스트", R.drawable.ic_launcher_foreground));
        list.add(new ViewVo("10번 텍스트", R.drawable.ic_launcher_foreground));

        ListView listView = findViewById(R.id.list_view);
        listView.setAdapter(new MyAdapter(this, list));

    }
}

결과는 이렇게 나올 것이다!

Comments