본문 바로가기

[안드로이드] ListView '리스트뷰' / ViewHolder '뷰홀더'

ListView '리스트뷰'

  • 데이터 목록(List)를 세로 방향의 리스트 형태로 나열하여 화면에 보여주는 ViewGroup 중 하나
  • ViewGroup는 스크롤 기본지원

ListView 사용법

  • ListView는 3가지의 구성요소로 존재
  • 1) View - 화면에 보여줄 레이아웃 내에 View 객체
  • 2) Item - 화면에 표시할 실질적인 item리스트 (데이터)
  • 3) Adapter - Item리스트(데이터)를 View와 연결해서 뷰(View) 생성 및 관리하는 객체

  Item -> Adapter, 아이템(리스트)와 Adapter를 연결하면 Adapter가 View를 생성

  Adapter -> ListView, 어답터(Adapter)가 View를 생성해서 ListView에 배치

 

ListView 구현

ListView - 구현 순서

    1) 보여줄 레이아웃 XML 정의

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

    <!-- Activity 타이틀 -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:layout_marginBottom="30dp"
        android:text="SeekBar Activity"
        android:textSize="25sp" />

    <!-- 리스트뷰, ListView -->
    <ListView
        android:id="@+id/listview_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

 

    2) ListView Item, 아이템 레이아웃 XML 정의

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

    <!--  리스트뷰 순서 표시할 TextView  -->
    <TextView
        android:id="@+id/listitem_number"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:text="번호" />

    <!--  리스트뷰 아이템 내용 'Title'  -->
    <TextView
        android:id="@+id/listitem_title"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"
        android:gravity="center_vertical"
        android:text="title"
        android:textSize="20sp" />

    <!--  리스트뷰 아이템 이미지 'image'  -->
    <ImageView
        android:id="@+id/listitem_image"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:src="@drawable/ic_message" />

</LinearLayout>

 

    3) item 데이터 객체, 클래스 생성

    private class ListView_Item {
        // 아이템 각각 내용, 'Title'
        private String title;
        // 아이템 각각 이미지 리소스 ID, 'Image'
        private int image;

        // 생성자 함수
        public ListView_Item(String title, int image) {
            this.title = title;
            this.image = image;
        }

        public String getTitle() {
            return title;
        }

        public int getImage() {
            return image;
        }
    }

    4) ListView 표현할 Adapter 구현, BaseAdapter 상속

         -> getView()에서 item 레이아웃 inflation하는 방법은 2가지

// 첫번째 방법 1
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = layoutInflater.inflate(R.layout.listview_item, parent, false);

// 두번째 방법 2
View view = LayoutInflater.from(context).inflate(R.layout.listview_item, parent, false);

         int getCount() : Adapter에 연결된 아이템 총 개수 반환

         아이템타입 getItem(int position) : 아이템리스트 중 해당 위치(position) 아이템 반환

         long getItemId(int position) : 해당 위치 position 반환

         View getView() : 해당 위치 View 반환

    private class ListView_Adapter extends BaseAdapter {
    	// 보여줄 Item 목록을 저장할 List
        List<ListView_Item> items = null;
        Context context;
		
        // Adapter 생성자 함수
        public ListView_Adapter(Context context, List<ListView_Item> items) {
            this.items = items;
            this.context = context;
        }

	// Adapter.getCount(), 아이템 개수 반환 함수 
        @Override
        public int getCount() {
            return items.size();
        }
	
    	// Adapter.getItem(int position), 해당 위치 아이템 반환 함수
        @Override
        public ListView_Item getItem(int position) {
            return items.get(position);
        }

	// Adapter.getItemId(int position), 해당 위치 반환 함수
        @Override
        public long getItemId(int position) {
            return position;
        }
        
	// Adapter.getView() 해당위치 뷰 반환 함수
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            // Infalter 구현 방법 1
            LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View view = layoutInflater.inflate(R.layout.listview_item, parent, false);

	// ListView의 Item을 구성하는 뷰 연결
            TextView number = view.findViewById(R.id.listitem_number);
            TextView title = view.findViewById(R.id.listitem_title);
            ImageView image = view.findViewById(R.id.listitem_image);
			
            // ListView의 Item을 구성하는 뷰 세팅
            ListView_Item item = items.get(position);
            number.setText(String.valueOf(position+1));		// 해당위치 +1 설정, 배열순으로 0부터 시작
            title.setText(item.getTitle());					// item 객체 내용을 가져와 세팅
            image.setImageResource(item.getImage());		// item 객체 내용을 가져와 세팅
			
            // 설정한 view를 반환해줘야 함
            return view;
        }
    }

    5) Adapter 생성 - ListView 연결

         -> Item 리스트를 먼저 생성한 다음, Item리스트로 Adapter를 생성

         -> 생성한 Adapter를 ListView에 추가.

         -> 클릭 이벤트 처리

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

	// ListView 생성
    ListView listView = findViewById(R.id.listview_list);
    
	// Item 리스트 선언 함수 init_ArrayList(20), 20은 추가할 아이템 개수
    init_ArrayList(20);

	// 만들어진 item 리스트로 Aapter 생성
    ListView_Adapter mAdapter = new ListView_Adapter(this, items);
    // ListView에 Adapter 연결
    listView.setAdapter(mAdapter);
    
    // ListView Item 클릭이벤트 처리
	listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
		@Override
    	public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
        	// AdapterView - 리스트뷰에 연결한 Adapter, getItemAtPosition(),
            // Adapter의 메소드 getItem()과 동일한 메소드
	    	ListView_Item item = (ListView_Item) adapterView.getItemAtPosition(position);
	        
            // 클릭한 위치의 Item의 title 문자열 반환
            String title = item.getTitle();
	// 클릭한 위치의 Item Title 문자열 토스트로 보여주기
    	    Toast.makeText(context, title, Toast.LENGTH_SHORT).show();
	    }
	});
}

 

Item List에 item을 추가하는 함수, 사용자 입력 아이템 개수만큼

 

// Item 리스트를 생성하는 함수
private void init_ArrayList(int count) {
	// item을 저장할 List 생성
	items = new ArrayList<>();
    
    // Drawable 이미지 리소스 ID 값을 가져오기 위해 Resource객체 생성
	Resources res = getResources();
    
    // 함수의 인자로 넘겨준 count 아이템 개수만큼 반복, 아이템 추가 
	for (int i = 0; i < count; i++) {
    	// 이미지리소스 id값을 가져옴, res.getIdentifier("이미지 이름", "리소스 폴더 이름", 현재패키지 이름)
		int img_ID = res.getIdentifier("listview_item" + (i % 4), "drawable", getPackageName());
        // item 객체 생성하여 리스트에 추가
	    items.add(new ListView_Item((i + 1) + "번째 아이템", img_ID));
    }
}

 

 

getView() 실행 모습

ViewHolder '뷰홀더' 필요성

     - ListView는 스크롤 시 안보이던 아이템은 매번 getView로 재호출

     - 아이템 내 View들을 매번 findViewById()로 로 재연결 (자원 낭비)

     - 해결을 위해 ViewHolder로 한번 생성된 View는 findViewById 재호출 막음

       @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = convertView;
			// 아이템 내 View들을 저장할 Holder 생성
            ViewHolder holder;

		// convertView는 생성된적이 없으면 Null을 반환, 최초생성시 Null이므로 최초생성인지 판단 			
            if (view == null) {
            	// 최초생성 View인 경우, inflation -> ViewHolder 생성 -> 해당 View에 setTag 저장
                view = LayoutInflater.from(context).inflate(R.layout.listview_item, parent, false);
                
                holder = new ViewHolder();
                holder.number = view.findViewById(R.id.listitem_number);
                holder.title = view.findViewById(R.id.listitem_title);
                holder.image = view.findViewById(R.id.listitem_image);
                // 해당 View에 setTag로 Holder 객체 저장
                view.setTag(holder);
            } else {
            	// ConvertView가 이미 생성된적 있다면, 저장되어있는 Holder 가져오기 
                holder = (ViewHolder) convertView.getTag();
            }
            
            // Holder 객체 내의 뷰(TextView,ImageView)를 세팅
            ListView_Item item = items.get(position);
            holder.number.setText(String.valueOf(position + 1));
            holder.title.setText(item.getTitle());
            holder.image.setImageResource(item.getImage());

			// 해당 View 반납 필수
            return view;
        }
    }

 

 

참조