RecyclerView가 등장한 이후로, ListView, GridView는 잘 이용되지 않제 됬지만 ListView를 이해하고 있으면 RecyclerView도 쉽게 이해할 수 있기때문에, Adapter View를 먼저 정리해보았다.
Adapter View?
- 화면이 작은 모바일 장비에서, 다양한 정보를 한번에 표현하기 위해 이용하는 View들
- 많은 정보를 효과적으로 처리하기 위해, View에 직접 정보를 주입하지 않고,
Adapter
라는 중간 매개체를 이용하기 때문에 붙여진 이름이다. - AdapterView는
ViewGroup을 상속
받으므로, 내부적으로 많은 뷰들을 담을 수 있다. - 대표적인 AdapterView의 서브 클래스 : ListView, GridView, Spinner, Gallery
Adapter?
- 데이터의 원본을 받아서 관리하고, 어댑터뷰가 출력할 수 있는 형태로 데이터를 제공하는 중간 객체
- ListAdapter, SpinnerAdapter 인터페이스를 구현한 BaseAdapter 추상클래스의 서브클래스들을 이용해 다양한 데이터들을 다룰 수 있고, 원한다면 BaseAdapter를 직접 상속받아서 구현할 수도 있다.
- ArrayAdapter :
T 타입의 배열 데이터
를 이용한 Adapter.List<T>
,T[]
타입 둘다 가능하다.(T[] 타입일 경우 데이터의 변경이 불가능하다.) - CursorAdapter :
Database의 Cursor
를 이용한 Adapter. - SimplAdapter :
XML파일
의 정적인 데이터를 이용한 Adapter.
- ArrayAdapter :
- 제공받은 원본을 가공하여 여러
어댑터뷰와 연결
하는 기능을 제공한다.- 어댑터는 결국 받은 원본을 화면에 어떻게 보여줄지 정해주는 객체.
- 어댑터와 연결된 원본 데이터가 변경되면,
notifyDataSetChanged
메서드를 호출하여 원본이 변경되었다고 어댑터뷰에 알려주어 화면이 다시 그려지도록 해야한다.- 즉, 어댑터뷰를 변경하고 싶으면
원본을 변경
하고 화면을 다시 그려야 한다.
- 즉, 어댑터뷰를 변경하고 싶으면
동작원리
- 어댑터뷰는 화면 드로잉에 필요한 정보를 어댑터에게 요청하게 되고, 어댑터는 자신이 가지고 있는 데이터를 가지고 요청받은 정보를 어댑터뷰에 리턴한다.
- 다음의 메서드는 BaseAdapter를 상속받게 되면 반드시 구현해야 하는 메서드로, 어댑터뷰에서 화면 드로잉을 할때 어댑터에게 정보를 요청하는 메서드이다.
- int getCount() : 화면에 표시해야 하는
데이터의 갯수
반환 - T getItem(int position) : 인자로 받은 위치의
데이터
반환 - long getItemId(int position) : 인자로 받은 위치의
데이터 id 구분자
반환 - View getView(int position, View convertView, ViewGroup parent) : 인자로 받은 위치의 데이터가
화면에 표시될 뷰
반환
- int getCount() : 화면에 표시해야 하는
- getCount 메서드로부터 반환된 데이터의 갯수만큼 나머지 메서드들이 호출되며, 화면을 그린다.
getView에 대하여
- 원본의 각 아이템들이 어댑터뷰에 어떻게 보일지 뷰를 그려서 반환하는 메서드이다.
- 화면이 그려져야할 때 어댑터뷰로부터 호출된다.
- 기존에 화면에 그렸던 뷰가 존재하지 않으면 convertView는 null값이 넘어오며, 이전에 화면에 그려졌다가 보이지 않게 된 뷰가 존재한다면 null이 아닌 값이 넘어온다.
- 4.0 이전에 convertView는 무작위 순서로 넘어왔으나, 4.0 이후에는 화면에서 사라지는 순서대로 넘어오는 듯하다.
- 이 Recycling 기법으로, 어댑터뷰는 화면에 표시할 최소한의 뷰만을 생성하게 된다.
getView최적화1 - View Holder 패턴
- 뷰 전개(inflating)은 매우 비싼 연산이므로, getView에서 findViewById 등, 뷰 전개하는 연산을 반복적으로 하는 것은 퍼포먼스를 떨어뜨린다.
- View Holder 패턴은 convertView를 처음 생성할 때, 미리 뷰를 전개하여 View의 태그에 참조객체를 저장하는 방법이다.
- 뷰 전개를 view 생성시에 딱 한번만 하므로, 퍼포먼스가 많이 향상된다.
// 전개된 뷰의 참조값을 저장할 객체
private class ViewHolder {
private TextView textName;
private TextView textType;
}
// 어댑터의 getView 메서드
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null) {
// convertView가 null이면 Holder 객체를 생성하고
// 생성한 Holder 객체에 inflating 한 뷰의 참조값을 저장
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.layout_list_item, parent, false);
holder.textName = (TextView) convertView.findViewById(R.id.text_item_name);
holder.textType = (TextView) convertView.findViewById(R.id.text_item_type);
// View의 태그에 Holder 객체를 저장
convertView.setTag(holder);
} else {
// convertView가 null이 아니면 뷰를 생성할때 태그에 저장했던 Holder 객체가 존재
// 이 Holder 객체는 자신을 inflating한 참조값(다시 전개할 필요가 없다.)
holder = (ViewHolder) convertView.getTag();
}
// 속성값 변경
ParcelableModel parcelableModel = getItem(position);
holder.textName.setText(parcelableModel.name);
holder.textType.setText(Integer.toString(parcelableModel.type));
return convertView;
}
getView최적화2 - 지연 로딩(Lazy Loading)
- 항목중에 네트워크로부터 받아와야 하는 파일이 있을 경우에, 네트워크 작업을 getView에서 진행하게 되면 getView가 리턴되지 않아서 어댑터뷰의 화면이 그려지지 않아 화면이 멈추게 된다.
- 이런 경우, AsyncTask 등의 방법을 이용해 작업을 비동기로 처리한다. 비동기 처리시 뷰를 바인딩하여, 작업이 완료된 후 자동적으로 뷰의 속성을 변경되도록 구현한다.