본문 바로가기
혼공노트

안드로이드 리스트뷰(ListView)와 어댑터(Adapter)

by 김비거 2024. 4. 17.



* 이 글은 인프런 강의 <[왕초보편] 앱 8개를 만들면서 배우는 안드로이드 코틀린>을 보고 정리한 글입니다.

 


폰트 적용하기

 

자신이 원하는 폰트를 텍스트에 적용하자.

 

1. 폰트를 다운로드 한 후,  파일명의 대문자를 소문자로 변경한다.

 

파일명을 소문자로

 

 

2. 리소스 폴더에 font 폴더를 생성하여 폰트 파일을 넣는다.

 

 

 

3. android:fontFamily="@font/bmjua_ttf"

텍스트뷰에 적용할 폰트를 설정한다.

 

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    android:textSize="40sp"
    android:fontFamily="@font/bmjua_ttf"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

 

 

 

 

 

ListView, Adapter

 

데이터를 리스트뷰에 표현하기 위해 어댑터를 사용하는 방법을 알아보자.

 

 

 

1. activity_main.xml에 리스트뷰를 배치한다.

 

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

 

 

android:id="@+id/mainListview"

리스트뷰의 아이디값을 mainListview로 지정한다.

 

 

2. 리스트 뷰의 내용을 넣을 ListViewModel이라는 데이터 클래스를 만들어준다. 

 

data class ListViewModel (
    var title : String = "",
    var content : String = ""
)

 

var title : String = "",
var content : String = ""

title(제목)과 content(내용)을 String(문자열)로 받겠다.

 

 

3. listview_item.xml에서 리스트뷰에서 제목과 내용을 어떻게 표시할지 레이아웃을 배치한다.

 

<TextView
    android:id="@+id/listViewItem"
    android:textSize="30sp"
    android:layout_margin="5dp"
    android:text="리스트뷰 아이템"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

<TextView
    android:id="@+id/listViewItem2"
    android:textSize="20sp"
    android:layout_margin="5dp"
    android:text="리스트뷰 아이템2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

 

 

 

 

4. 데이터를 전달할 어댑터 클래스 ListViewAdapter를 만든다.

 

class ListViewAdapter(val List : MutableList<ListViewModel>) : BaseAdapter() {
    override fun getCount(): Int { // 리스트의 사이즈 가져오기
        return List.size
    }

    override fun getItem(position: Int): Any { // 리스트 요소 가져오기
        return List[position]
    }

    override fun getItemId(position: Int): Long { // 리스트 요소의 아이디 가져오기
        return position.toLong()
    }

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { // 리스트 뷰를 생성하는 함수
        var converView = convertView

        if (converView == null) {
            converView = LayoutInflater.from(parent?.context).inflate(R.layout.listview_item, parent, false)
        }

        val title = converView!!.findViewById<TextView>(R.id.listViewItem)
        val content = converView!!.findViewById<TextView>(R.id.listViewItem2)
        title.text = List[position].title
        content.text = List[position].content

        return converView!!
    }
}

 

class ListViewAdapter(val List : MutableList<ListViewModel>) : BaseAdapter() {

BaseAdapter 형식으로 List를 가져오는 어댑터 클래스

 

override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {

어댑터를 통해 데이터를 activity_main.xml의 리스트뷰에 전달하기 위한 함수

 

var converView = convertView

뷰를 converView 변수로 지정

 

if (converView == null) {
            converView = LayoutInflater.from(parent?.context).inflate(R.layout.listview_item, parent, false)
        }

listview_item.xml의 레이아웃을 가져옴

 

val title = converView!!.findViewById<TextView>(R.id.listViewItem)

val content = converView!!.findViewById<TextView>(R.id.listViewItem2)

listview_item.xml의 텍스트뷰의 아이디를 통해 listViewItem(제목 표시), listViewItem2(내용 표시)을 각각 title과 content 변수로 선언

 

title.text = List[position].title
content.text = List[position].content

리스트의 title부분(ListViewModel 데이터 클래스의 변수)을 title(위에 변수로 선언한 listview_item.xml의 listViewItem 부분)의 텍스트로 지정.

content도 마찬가지.

 

return converView!!

뷰를 반환함. (어댑터를 통해 listview_item.xml에 title과 content를 전달함)

 

 

 

5. MainActivity에 리스트뷰에 넣을 데이터를 만들고 어댑터에 전달한다. 그 후 어댑터의 반환값을 activity_main.xml의 리스트뷰에 전달한다.

 

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val list_item = mutableListOf<ListViewModel>()

        list_item.add(ListViewModel("a", "b"))
        list_item.add(ListViewModel("c", "d"))
        list_item.add(ListViewModel("e", "f"))

        val listview = findViewById<ListView>(R.id.mainListview)

        val listAdapter = ListViewAdapter(list_item)
        listview.adapter = listAdapter

    }
}

 

val list_item = mutableListOf<ListViewModel>()

ListViewModel 형식(데이터클래스)의 리스트를 만든다.

 

list_item.add(ListViewModel("a", "b"))

"a"와 "b"는 각각 ListViewModel 형식의 title, content이다. 이것을 리스트에 넣는다. (데이터 만듦)

 

val listview = findViewById<ListView>(R.id.mainListview)

activity_main.xml의 리스트뷰 아이디 값이 mainListview이다. 이것을 listview라는 변수에 넣는다.

 

val listAdapter = ListViewAdapter(list_item)

만든 데이터 list_item을 어댑터(ListViewAdpater)에 넣는다. 어댑터의 반환값은 listAdpater라는 변수가 갖는다.

 

listview.adapter = listAdapter

activity_main.xml의 리스트뷰(listview)가 어댑터를 통한 반환값 listAdpater를 받는다.

 

 

 

 

 

뒤로가기 버튼 이벤트

 

안드로이드에서 뒤로가기 버튼을 두 번 눌렀을 때 앱이 종료되도록 하자.

 

class MainActivity : AppCompatActivity() {
    private var isDouble = false
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onBackPressed() {
        Log.d("MainActivity", "backbutton")
        if (isDouble == true) {
            super.onBackPressed()
        }
        isDouble = true
        Toast.makeText(this, "종료하실려면 더블클릭", Toast.LENGTH_LONG).show()

        Handler().postDelayed({
            isDouble = false
        }, 2000)
    }   
}

 

private var isDouble = false

두번 눌렀는지의 변수로 false 초기값 형성

 

override fun onBackPressed() {

뒤로가기 버튼을 눌렀을 때의 처리 함수

 

Log.d("MainActivity", "backbutton")

뒤로가기 버튼을 눌렀을 때 "MainActivity" 이름으로 "backbutton" 로그가 잡히는지 확인 (디버깅)

 

if (isDouble == true) {
            super.onBackPressed()
        }

isDouble이 true일 때 앱이 종료됨.

 

isDouble = true

뒤로가기 버튼을 눌렀을 때 isDouble을 true로 설정

 

Toast.makeText(this, "종료하실려면 더블클릭", Toast.LENGTH_LONG).show()

"종료하실려면 더블클릭" 토스트 메시지 띄움

 

Handler().postDelayed({
            isDouble = false
        }, 2000)

2000밀리초(2초) 후 isDouble이 false가 됨.

즉, 2초 안에 뒤로가기를 누르지 않으면 앱이 종료가 되지 않는다.

 

 

 


목표

 

첫 화면에는 랜덤으로 명언 한 줄과 전체 명언 보기 버튼이 있다.

전체 명언 보기 버튼을 누르면 리스트뷰로 명언들의 리스트들이 적혀져 있다.

 

 

1. build.gradle.kts

 

dataBinding을 사용하겠다고 작성한다.

 

android {
    ...
    dataBinding{
        enable = true
    }
}

 

 

 

2. activity_main.xml

 

첫 화면인 MainActivity의 레이아웃을 구성한다.

명언이 한 줄 나오고 전체명언을 보기위한 버튼을 만든다.

 

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <androidx.constraintlayout.widget.ConstraintLayout
        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:background="@color/black"
        tools:context=".MainActivity">

        <Button
            android:id="@+id/showAllSentenceBtn"
            android:layout_width="150dp"
            android:layout_height="50dp"
            android:text="전체명언보기"
            android:layout_margin="20dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/goodWordTextArea"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="명언이 들어갈 위치입니다"
            android:fontFamily="@font/bmjua_ttf"
            android:layout_margin="20dp"
            android:gravity="center"
            android:textColor="@color/white"
            android:textSize="30sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 

 

<layout>

데이터 바인딩이 layout을 관리한다.

 

android:background="@color/black"

배경색은 검은색으로 한다.

 

<Button
            android:id="@+id/showAllSentenceBtn"

버튼의 아이디값은 showAllSentenceBtn으로 한다.

 

app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"

버튼이 오른쪽(End) 위(Top)에 위치하도록 한다.

 

<TextView
            android:id="@+id/goodWordTextArea"

텍스트뷰의 아이디값은 goodWordTextArea로 한다.

 

android:fontFamily="@font/bmjua_ttf"

폰트는 bmjua_ttf으로 설정

 

 

 

3. MainActivity

 

데이터 바인딩을 이용하여 layout을 조정한다.

또한 인텐트(Intent)를 이용하여 버튼을 클릭하면 SentenceActivity 화면으로 전환하도록 한다.

 

class MainActivity : AppCompatActivity() {

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

        val sentenceList = mutableListOf<String>()

        sentenceList.add("검정화면에 대충 흰글씨 쓰면 명언같다.")
        sentenceList.add("삶이 있는 한 희망은 있다 -키케로")
        sentenceList.add("하루에 3시간을 걸으면 7년 후에 지구를 한바퀴 돌 수 있다. -사무엘존슨")
        sentenceList.add("언제나 현재에 집중할수 있다면 행복할것이다. -파울로 코엘료")
        sentenceList.add("산다는것 그것은 치열한 전투이다. -로망로랑")
        sentenceList.add("피할수 없으면 즐겨라 – 로버트 엘리엇")
        sentenceList.add("진정으로 웃으려면 고통을 참아야하며 , 나아가 고통을 즐길 줄 알아야 해 -찰리 채플린")
        sentenceList.add("직업에서 행복을 찾아라. 아니면 행복이 무엇인지 절대 모를 것이다 -엘버트 허버드")

        Log.d("MainActivity", sentenceList.random())


        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.showAllSentenceBtn.setOnClickListener {
            val intent = Intent(this, SentenceActivity::class.java)
            startActivity(intent)
        }

        binding.goodWordTextArea.setText(sentenceList.random())

    }
}

 

val sentenceList = mutableListOf<String>()

명언들을 담을 String 형태의 리스트를 변수를 선언한다.

 

sentenceList.add("검정화면에 대충 흰글씨 쓰면 명언같다.")

리스트에 명언들을 추가한다.

 

Log.d("MainActivity", sentenceList.random())

MainActivity라는 이름으로 명언이 랜덤하게 나오는지 로그를 본다. (디버깅)

 

binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

데이터 바인딩을 이용하여 layout을 관리한다.

 

binding.showAllSentenceBtn.setOnClickListener {
            val intent = Intent(this, SentenceActivity::class.java)
            startActivity(intent)
        }

showAllSentenceBtn(버튼)을 클릭했을 때 SentenceActivity 화면으로 전환시킨다.

 

binding.goodWordTextArea.setText(sentenceList.random())

goodWordTextArea(명언이 보일 위치)에 명언이 랜덤하게 나오도록 한다.

 

 

 

4. activity_sentence.xml

 

SentenceActivity의 레이아웃에 리스트뷰를 구성한다.

 

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

 

 

<ListView
        android:id="@+id/sentenceListView"

리스트뷰의 아이디 값은 sentenceListView

 

 

 

5. listview_item.xml

 

리스트뷰의 각 요소가 어떻게 레이아웃 될지 구성한다. 텍스트뷰를 배치한다.

 

<TextView
    android:id="@+id/listViewTextArea"
    android:textSize="15sp"
    android:fontFamily="@font/bmjua_ttf"
    android:layout_margin="10dp"
    android:text="TextArea"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

 

 

<TextView
        android:id="@+id/listViewTextArea"

텍스트뷰의 아이디는 listViewTextArea

 

android:fontFamily="@font/bmjua_ttf"

폰트는 bmjua_ttf으로 설정하였다.

 

 

 

6. ListViewAdapter

 

데이터를 전달할 어댑터를 만든다.

 

class ListViewAdapter(var List :MutableList<String>) : BaseAdapter() {
	
    ...
    
    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        var convertView = convertView

        if(convertView == null) {
            convertView = LayoutInflater.from(parent?.context).inflate(R.layout.listview_item, parent, false)
        }

        val listViewText = convertView?.findViewById<TextView>(R.id.listViewTextArea)
        listViewText!!.text = List[position]

        return convertView!!
    }
}

 

if(convertView == null) {
            convertView = LayoutInflater.from(parent?.context).inflate(R.layout.listview_item, parent, false)
        }

listview_item 레이아웃을 가져온다.

 

val listViewText = convertView?.findViewById<TextView>(R.id.listViewTextArea)

listview_item.xml의 텍스트뷰의 아이디 값 listViewTextArea를 listViewText 변수에 넣는다.

 

listViewText!!.text = List[position]

데이터들을 listVewText(텍스트뷰)의 텍스트로 설정한다.

 

return convertView!!

listview_item 레이아웃에 대해 위와 같이 설정된 뷰를 반환한다.

 

 

 

7. SentenceActivity

 

데이터를 만든다. 그리고 그 데이터를 어댑터에 전달하여 어댑터의 반환값을 activity_sentence.xml의 리스트뷰에 적용한다.

 

class SentenceActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_sentence)

        val sentenceList = mutableListOf<String>()

        sentenceList.add("검정화면에 대충 흰글씨 쓰면 명언같다.")
        sentenceList.add("삶이 있는 한 희망은 있다 -키케로")
        sentenceList.add("하루에 3시간을 걸으면 7년 후에 지구를 한바퀴 돌 수 있다. -사무엘존슨")
        sentenceList.add("언제나 현재에 집중할수 있다면 행복할것이다. -파울로 코엘료")
        sentenceList.add("산다는것 그것은 치열한 전투이다. -로망로랑")
        sentenceList.add("피할수 없으면 즐겨라 – 로버트 엘리엇")
        sentenceList.add("진정으로 웃으려면 고통을 참아야하며 , 나아가 고통을 즐길 줄 알아야 해 -찰리 채플린")
        sentenceList.add("직업에서 행복을 찾아라. 아니면 행복이 무엇인지 절대 모를 것이다 -엘버트 허버드")


        val sentenceAdapter = ListViewAdapter(sentenceList)
        val listview = findViewById<ListView>(R.id.sentenceListView)

        listview.adapter = sentenceAdapter

    }
}

 

val sentenceList = mutableListOf<String>()

명언들(데이터)를 넣을 String 형태의 리스트 변수를 선언

 

sentenceList.add("검정화면에 대충 흰글씨 쓰면 명언같다.")

리스트 변수에 명언들(데이터)를 넣는다.

 

val sentenceAdapter = ListViewAdapter(sentenceList)

어댑터에 데이터를 넣는다. sentenceAdapter는 어댑터의 반환값이다.

 

val listview = findViewById<ListView>(R.id.sentenceListView)

activity_sentence.xml의 리스트뷰를 listview 변수에 넣는다.

 

listview.adapter = sentenceAdapter

어댑터의 반환값을 리스트뷰에 적용한다.

 

 

MainActivity

 

SentenceActivity

 

댓글