안드로이드 화면제약 극복, 기본적인 listView, adapter, ViewHolder 사용법

참고 : 이글은 오준석강사님의 영상을 보고 따라 만들었습니다. https://youtu.be/CAKwkzQvDbI

 

activity_main.xml : xml에 리스트뷰 생성

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

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

 

item_weather.xml : res > layout > item_weather.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="wrap_content"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/weather_image"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@mipmap/ic_launcher"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="8dp"
        android:layout_gravity="center">

        <TextView
            android:id="@+id/city_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="도시명"
            android:textSize="30sp"/>
        <TextView
            android:id="@+id/temp_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:gravity="end"
            android:text="30" />

    </LinearLayout>


</LinearLayout>

 

Weather.java : 날씨 클래스 생성

// 날씨 클래스
public class Weather {
    private String city;
    private String temp;
    private String weather;

    //생성자 : alt+Insert > Constrctor
    public Weather(String city, String temp, String weather) {
        this.city = city;
        this.temp = temp;
        this.weather = weather;
    }

    // get, set : alt+Insert > getter setter
    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getTemp() {
        return temp;
    }

    public void setTemp(String temp) {
        this.temp = temp;
    }

    public String getWeather() {
        return weather;
    }

    public void setWeather(String weather) {
        this.weather = weather;
    }

    // toString : alt+Insert > toString 자동 생성(Template : StringBuffer 선택)
    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Weather{");
        sb.append("city='").append(city).append('\'');
        sb.append(", temp='").append(temp).append('\'');
        sb.append(", weather='").append(weather).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

 

MyFirstAdapter.java : Weather클래스를 표시해줄 어댑터 BaseAdapter 상속

public class MyFirstAdatper extends BaseAdapter {

    private List<Weather> mData; //날씨 정보를 담는 곳
    private Map<String, Integer> mWeatherImageMap; //매칭되는 이미지 정보

    // 생성자
    public MyFirstAdatper(List<Weather> data) {
        this.mData = data;
        mWeatherImageMap = new HashMap<>();
        mWeatherImageMap.put("맑음", R.drawable.sunny);
        mWeatherImageMap.put("폭설", R.drawable.blizzard);
        mWeatherImageMap.put("구름", R.drawable.cloud);
        mWeatherImageMap.put("비", R.drawable.rainy);
        mWeatherImageMap.put("눈", R.drawable.snow);
    }

    @Override
    public int getCount() { //아이템의 갯수 표시
        return mData.size();
    }

    @Override
    public Object getItem(int position) { //몇번째 아이템인지 position 값
        return mData.get(position);
    }

    @Override
    public long getItemId(int position) { //DB사용시 중요. 하지만 예제에선 db사용 x. 때문에 일반 position값 리턴
        return position;
    }
    
    // 뷰 홀더 객체
    static class ViewHolder{
        ImageView weathgerImage;
        TextView cityText;
        TextView tempText;
    }

    // 리스트뷰의 아이템을 불러올때 하나하나 호출(+ 리스트뷰 한칸에 보여질 아이템 레이아웃 정의)
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder holder; //ViewHolder : 일종의 최적화를 위한 것

        if(convertView == null){ //새로 만들기
            holder = new ViewHolder();

            //LayoutInflater 외부 레이아웃 가져오기
            convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.itme_weather, parent, false);

            ImageView weathgerImage = convertView.findViewById(R.id.weather_image);
            TextView cityText = convertView.findViewById(R.id.city_text);
            TextView tempText = convertView.findViewById(R.id.temp_text);

            holder.weathgerImage = weathgerImage;
            holder.cityText = cityText;
            holder.tempText = tempText;

            convertView.setTag(holder); // 오브젝트 저장

        } else { // 재사용
            holder = (ViewHolder) convertView.getTag();
        }

        // 해당번째 가져오기
        Weather weather = mData.get(position); //mData : List<Weather> data
        holder.cityText.setText(weather.getCity());
        holder.tempText.setText(weather.getTemp());
        holder.weathgerImage.setImageResource(mWeatherImageMap.get(weather.getWeather()));

        return convertView;
    }

}

 

MainActivity.java : 임의로 데이터 삽입 후 리스트뷰 표시

ex) "맑음"이라고 저장시 Weather.class에서 해당 글자와 매칭되는 이미지 함께 저장

public class MainActivity extends AppCompatActivity {

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

        ArrayList<Weather> data = new ArrayList<>(); // <Weather> : Weather.class
        
        // 임의로 데이터 삽입
        data.add(new Weather("수원","25도","맑음"));
        data.add(new Weather("서울","26도","비"));
        data.add(new Weather("안양","24도","구름"));
        data.add(new Weather("부산","29도","구름"));
        data.add(new Weather("인천","23도","맑음"));
        data.add(new Weather("대구","28도","비"));
        data.add(new Weather("용인","25도","비"));

        // 뷰에 데이터를 표시하기 위한 어댑터
        MyFirstAdapter adapter = new MyFirstAdapter(data);

        ListView listView = findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }
}

 

더보기

** 조금더 공부하기

[ ViewHolder 란? ]

- listView를 그냥 쓰려고 하면, ViewHolder 사용을 추천한다고 안내 문구? 가 뜨는데, 이는 예제처럼 적은양의 데이터 상에선 괜찮지만, 대량의 데이터가 있을 경우, 스크롤을 할때 버벅거림 현상이 심하다고 한다.

이때 이 버벅거림을 최소화 하기 위한 해결방법으로 ViewHolder가 있다. (= 최적화)

- MyFirstAdapter.java에서 외부 레이아웃을 가져 올때 .inflate를 사용하는데 이는 xml로 쓰여있는 View의 정의를 실제 View 객체로 만드는 것을 말한다. 이 inflate 사용을 최소하 하기 위해 뷰를 재활용 한다.

- 각 뷰의 내용을 업데이트 할때 마다 findViewByid를 매번 호출하게 되면 성능저하가 일어나는데 ItemView의 각 요소를 바로 엑세스 할 수 있도록 저장해두고 사용하기 위한 객체 라고 한다.

 

내방식대로 이해하기 : listView에는 내가 xml로 미리 짜놓은 형식대로 아이템들이 들어가게 되는데, 데이터 1개당 이걸 매번 외부에서 레이아웃을 가져오고 호출한다고 생각해보자. 그럼 많은 데이터가 들어 갈때, 반복적인 내용들이 계속 있기 때문에 버벅거림 현상이 나타나는 것이다. 이럴때 VeiwHolder를 사용하여 객체를 따로 만들고, 1개의 틀만 만들어 놓고, 그 틀을 재사용 할 수 있다면 간편하지 않을까? 그런 이유 때문에 convertView의 값이 null(아무것도 없을때) 에만 레이아웃을 가져오고 findViewById 행위를 하고, 그 이후부턴 만들어진 틀을 재사용 하는 것이다.

 

(참고) ViewHolder에 관한 더 자세한 내용 : developside.tistory.com/88