EMDI는 지금도 개발중

Android with Kotlin : 안드로이드 기초편 - ListView 기초 프로세스 파악하기 본문

네이티브/Android

Android with Kotlin : 안드로이드 기초편 - ListView 기초 프로세스 파악하기

EMDI 2020. 9. 21. 12:32

* ListsView : 같은 모양의 목록이 반복적으로 나타날 때 여러줄을 비슷한 모양, 자리에 맞는 데이터만 변경해서 출력

* ListView는 Data / ArrayList / Adapter 세 가지를 같이 사용

저번 시간까지는 Intent에 대해 배워보았습니다. 이번 시간부터는 ListView에 대해 공부해보도록 하겠습니다.

​* 참고로 최근에는 ListView 대신 RecylerView를 많이 사용하는데 제가 학원에서 배운 부분은 ListView이기에 복습도 ListView로 우선 하도록 하겠습니다.

 

* 연습내용 : 직방 목록 사진과 같이 보증금, 월세, 주소, 층수, 설명에 대해 ListView를 만들어 보는 연습을 할 계획입니다. - 프로젝트명 : Review_ListView

 

순서 위치 프로세스
1 java > BaseActivity.kt 생성 공통적으로 사용하는 함수들을 미리 정의해두는 부분
- MainActivity처럼 자동생성하는 것이 아니고 우리가 직접 Kotlin Class로 생성
▶ mContext, setupEvents(), setValues() 생성
※ 역할 : 기본 틀 구성(기초작업)
2 java > MainActvity.kt ▶ BaseActivity에서 생성한 setupEvents, setValues함수 override 하기
* 주의사항 : 꼭 TO-DO 주석 제거 및 onCreate함수에 setupEvents, setValues 넣기
※ 역할 : 기본틀 만든 것과 연결(기초작업)
3 res > layout > activity_main.xml 앱을 실행했을 때 화면에 바로 보여줄 부분
▶ ListView 태그를 추가 및 id부여
※ 역할 : 화면 리스트뷰 생성(디자인)
4 java > datas 패키지 생성
java > datas > Room.kt 생성
ListView가 뿌려줄 데이터들을 묶어서 표현하는 데이터 클래스 작업
▶ 클래스의 생성자에서 val 변수이름 : 자료형
클래스가 가져야하는 정보 항목들로 설정
※ 역할 : 데이터 항목 설정(DB)
5 res > layout > room_list_item.xml 생성 ListView에 데이터가 뿌려질 모양을 xml로 설정
▶ 한 줄에 해당하는 모양 설정
※ 역할 : 한 줄씩 뿌려줄 때 보여줄 모습 생성(디자인)
6 java > adapters 패키지 생성
java > adapters > RoomAdapter 생성
데이터 클래스 객체들을 ArrayList에 담아서 Adapter에게 전달
하나하나의 객체들을 한 줄에 해당하는 XML과 연결해서 ListView에 뿌려주는 역할
▶ Room 뿌려줄 데이터클래스 상속받고 필요한 데이터 순으로 텍스튜 변수에 담기
※ 역할 : 매개체 생성
7 java > MainActivity.kt ▶ 액티비티에서 실제 목록을 담아줄 ArrayList만들고 실제 데이터 담아주기
DB와 관련된 데이터를 연결해줘야하는데 현재는 연습용이라 가라 데이터를 가지고 활용
※ 역할 : 데이터 ArrayList에 담아놓기(소스)
8 java > MainActivity.kt ▶ Adapter 클래스를 액티비티에 있는 리스트뷰와 연결
※ 역할 : 매개체와 데이터 담아놓은 ArrayList 연결, 리스트뷰의 매개체인 것 지정
9 java > adapters > RoomAdapter ▶ 실제 데이터가 있는 목록이 반영되도록 하기
※ 역할 : 리스트뷰의 텍스트뷰와 row 안에 있는 데이터와 연결하기

 

package com.mwsniper.review_listview

import androidx.appcompat.app.AppCompatActivity

abstract class BaseActivity : AppCompatActivity() {

    // 어느 화면인지 알려줄 때 쓰는 용도의 변수 mContext 멤버변수로 지정
    val mContext = this

    // 이벤트 처리 코드를 모아두게 될 함수
    abstract fun setupEvents()

    // 화면에 데이터를 뿌리는데에 관련된 코드를 모아두게 될 함수
    abstract fun setValues()

}
package com.mwsniper.review_listview

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setupEvents()
        setValues()
    }

    override fun setupEvents() {
    }

    override fun setValues() {
    }
}

1. BaseActivity 클래스 생성

자주 사용할 함수 와 mContext를 멤버변수로 잡고 MainActivity가 해당 클래스를 상속받도록 설정해줍니다

* BaseActivity는 AppCompatActivity()를 상속받고

* MainActvity는 BaseActivity()를 상속받도록 설정해줘야 합니다

2. MainActivity 클래스에 override 하기

BaseActivity에 2개의 추상메소드를 생성하였으니 상속받고 있는 MainActivity에도 해당 함수들을 상속받아야 합니다.

 

<!-- activity_main.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">

    //1. 액티비티 화면 (xml)에 리스트뷰 배치 + id 부여
    <ListView
        android:id="@+id/roomListView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

3. activity_main.xml에 ListView태그 추가

실제 화면에서 보여줄 ListView태그를 추가합니다

​* 참고로 디자인에서는 ListView의 모습이 보이지만 실제 앱을 실행했을 때는 ListView의 모습이 보이지 않습니다.

이와 같은 이유 : ListView는 Adapter의 도움을 받아서 내용목록을 출력하는데 아직 Adapter가 없어서 그렇습니다.

 

package com.mwsniper.review_listview.datas

// ListView가 뿌려줄 데이터들을 묶어서 표현하는 데이터 클래스 생성
class Room(
    // 클래스의 생성자에서 변수들을 나열해서 클래스가 가져야하는 정보 항목들로 설정
    val deposit: Int,
    val monthlyRent: Int,
    val address: String,
    val floor: Int,
    val description: String
) {
    
}

4. datas 패키지 생성 및 Room 데이터 클래스 생성

activity_main.xml에 있는 ListView에 뿌려줄 데이터들을 묶어서 표현하는 클래스. 생성자( val 변수명: 자료형 ) 이렇게 표현하며 우리는 앞서 보증금, 월세, 주소, 층수, 설명을 보여주기로 약속했으니 해당과 관련된 변수들을 생성하였습니다.

 

<!-- room_list_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="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/imgTxt"
            android:layout_width="120dp"
            android:layout_height="100dp"
            android:src="@drawable/ic_launcher_foreground"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:gravity="center_vertical"
            android:paddingLeft="15dp">

            <TextView
                android:id="@+id/priceTxt"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="8,000(보증금) / 30(월세)"
                android:textSize="20sp"
                android:textStyle="bold" />

            <TextView
                android:id="@+id/addressTxt"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="주소, 1층"
                android:textSize="16sp"
                android:layout_marginTop="3dp"/>

            <TextView
                android:id="@+id/descTxt"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="설명문구 쓰는 자리입니다."
                android:layout_marginTop="3dp"/>
        </LinearLayout>
        
    </LinearLayout>

</LinearLayout>

5. room_list_item.xml 추가

room_list_item.xml은 ListView의 List 하나마다 보여줄 모양을 설정하는 xml입니다. res > layout 에 Layout Resource File을 하나 추가해줍니다. 보통 ~_list_item.xml 이라고 이름을 짓습니다.

​* 참고로 데이터들이 있는 LinearLayout 속성 중 gravity=center_vertical을 설정 안하면 글씨들이 제일 위에 설정이 되버립니다.

 

package com.mwsniper.review_listview.adapters

import android.content.Context
import android.content.LocusId
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import com.mwsniper.review_listview.R
import com.mwsniper.review_listview.datas.Room

// 2) 상속받은 뒤, Adapter 주 생성자에서 필요한 재료 받고
class RoomAdapter(
    val mContext: Context,
    val resId: Int,
    val mList: List<Room>

// 1) ArrayAdapater<Room(뿌려줄 데이터클래스)>()를 상속받고
// 3) ArrayAdapter<Room>(mContext, resId, mList) 생성자에서 필요한 재료 순으로 부모에게 넘기기
) : ArrayAdapter<Room>(mContext, resId, mList) {

    // 4) 객체로 변환해주는 변수를 멤버변수로 생성
    val inf = LayoutInflater.from(mContext)

    // 5) getView 오버라이딩
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {

        // 6) convertView 변수를 tempRow에 옮겨닮아서 null인경우 새로운 inflate해서 담기
        // 이렇게 사용하는 이유는 listView를 재사용성하기 위해
        var tempRow = convertView
        if(tempRow == null) {
            tempRow = inf.inflate(R.layout.room_list_item, null)
        }

        // tempRow는 맞지만 null은 절대 아니다 (= !!)
        val row = tempRow!!

        return row
    }

}

6. adapters 패키지 및 adapter 생성

Adapter 클래스는 Data 클래스 + xml을 조합해서 ListView에 뿌리는 역할을 합니다. ArrayAdapter를 상속받는다고 명시를 하고 그 안에 뿌려줄 데이터클래스를 적용합니다.

* ArrayAdapter는 기본생성사 ()를 지원하지 않기때문에 만약 데이터 받는 재료를 적용안하고 넘어가면 오류가 뜹니다.

 

package com.mwsniper.review_listview

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.mwsniper.review_listview.datas.Room

class MainActivity : BaseActivity() {

    // 액티비테이서 실제 목록을 담아줄 ArrayList를 만들고 실제 데이터들을 담기
    val mRoomList = ArrayList<Room>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setupEvents()
        setValues()

    }

    override fun setupEvents() {

    }

    override fun setValues() {

        // 실제로 뿌려줄 데이터들 ArrayList변수에 추가
        // 보증금, 월세, 주소, 층수, 설명
        mRoomList.add(Room(8000, 30, "서울시 마포구", 1, "마포구 1층 방입니다."))
        mRoomList.add(Room(10000, 50, "서울시 은평구", 10, "은평구 10층 방입니다."))
        mRoomList.add(Room(3000, 80, "서울시 영등포구", 5, "영등포구 5층 방입니다."))
        mRoomList.add(Room(5000, 15, "서울시 종로구", 4, "종로구 4층 방입니다."))
        mRoomList.add(Room(9000, 20, "서울시 강서구", 8, "강서구 8층 방입니다."))
        mRoomList.add(Room(12000, 40, "서울시 강북구", 17, "강북구 17층 방입니다."))

    }

}

7. MainActivity에 ArrayList 만들기

액티비티에 실제 목록을 담아줄 ArrayList를 만들고 그 안에서 실제 데이터들을 담아줍니다. val 목록변수이름 = ArrayList<뿌려줄데이터클래스>() 실제로 뿌려줄 데이터들은 원래 서버에서 가져와야하지만 현재는 서버가 없으니 가라데이터를 만들어서 연습하였습니다.

 

package com.mwsniper.review_listview

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.mwsniper.review_listview.adapters.RoomAdapter
import com.mwsniper.review_listview.datas.Room
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : BaseActivity() {

    val mRoomList = ArrayList<Room>()

    // 만들어둔 Adapter 클래스를 액티비티에 있는 리스트뷰와 연결
    lateinit var mRoomAdapter: RoomAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setupEvents()
        setValues()

    }

    override fun setupEvents() {

    }

    override fun setValues() {

        mRoomList.add(Room(8000, 30, "서울시 마포구", 1, "마포구 1층 방입니다."))
        mRoomList.add(Room(10000, 50, "서울시 은평구", 10, "은평구 10층 방입니다."))
        mRoomList.add(Room(3000, 80, "서울시 영등포구", 5, "영등포구 5층 방입니다."))
        mRoomList.add(Room(5000, 15, "서울시 종로구", 4, "종로구 4층 방입니다."))
        mRoomList.add(Room(9000, 20, "서울시 강서구", 8, "강서구 8층 방입니다."))
        mRoomList.add(Room(12000, 40, "서울시 강북구", 17, "강북구 17층 방입니다."))

        // Adapter 클래스를 객체화
        // BaseActivity의 mContext, 어떤 리스트를 보여줄건지, 목록변수의 이름
        // lateinit var로 초기화를 미뤘던 변수의 실제 초기화 코드
        // (mContext=어떤화면에서?, romm_list_item=어떤모양으로그릴지?, mRoomList=어떤목록?)
        mRoomAdapter = RoomAdapter(mContext, R.layout.room_list_item, mRoomList)

        // 객체화된 adapter변수를 리스트뷰의 어댑터로 지정
        // 실제 목록을 리스트뷰에 뿌려준다.
        roomListView.adapter = mRoomAdapter
    }
}

8. Adapter 클래스를 액티비티의 리스트뷰와 연결

RoomAdapter 클래스를 setValues에 만들었던 mRoomList와 연결하고 그것을 실제 화면에 보일 수 있도록 리스트뷰에 연결 시켜줍니다.

​* 여기까지하면 리스트뷰에 아까 예시로 지정해둔 문구들이 줄줄이 생성되는 것을 확인할 수 있습니다.

* 참고로 실제 데이터들은 아직 반영되지 않은 상태

package com.mwsniper.review_listview.adapters

import android.content.Context
import android.content.LocusId
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import com.mwsniper.review_listview.R
import com.mwsniper.review_listview.datas.Room

class RoomAdapter(
    val mContext: Context,
    val resId: Int,
    val mList: List<Room>
) : ArrayAdapter<Room>(mContext, resId, mList) {

    val inf = LayoutInflater.from(mContext)
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        var tempRow = convertView
        if(tempRow == null) {
            tempRow = inf.inflate(R.layout.room_list_item, null)
        }
        val row = tempRow!!

        // 실제 데이터가 있는 목록이 반영되도록 Adapter 클래스의 getView 함수를 수정
        // 뿌려줄 row 안에 있는 텍스트 뷰 변수로 담기
        val data = mList[position]
        val price = row.findViewById<TextView>(R.id.priceTxt)
        val address = row.findViewById<TextView>(R.id.addressTxt)
        val desc = row.findViewById<TextView>(R.id.descTxt)

        price.text = "${data.deposit} / ${data.monthlyRent}"
        address.text = "${data.address},  ${data.floor}"
        desc.text = data.description

        return row
    }

}

9. 리스트뷰의 id와 row안에있는 실제데이터 연결

마지막 작업으로 리스트뷰 안에 있는 TextView id와 row의 데이터를 연결하는 작업입니다. 해당 작업을 해야 실제 데이터가 리스트뷰에 출력됩니다. 여기까지 하셨으면 이미지를 제외한 나머지 TextView가 정상적으로 출력되는 것을 확인할 수 있을겁니다. 다음 글에서는 상세화면으로 넘어가서 디테일한 정보를 보여주는 연습을 해보도록 하겠습니다.

Comments