Unit 2: Layouts (4)

quokka·2021년 11월 8일
0

Android Basics in Kotlin

목록 보기
8/25
post-thumbnail

RecyclerView와 List를 이용해서 Affirmation 어플을 만든다.

패키지

코드를 파일 및 패키지로 구성해 코드를 논리적이고, 이해하기 쉽고, 유지관리하고, 확장할 수 있도록 구성할 수 있다.
사진 속 java 아래에 있는 세 폴더가 패키지이다.

패키지 이름 정하기

  • 패키지 이름은 일반적으로 general → specific, 소문자로 표기한다.
  • 각 부분을 마침표로 구분하는데 중요한 것은 마침표는 실제 코드 내 계층 구조나 폴더 구조를 나타내는 것은 아니다.
  • 인터넷 도메인은 전역적으로 고유하므로, 이름 첫 부분에 개발자의 도메인이나 비즈니스의 도메인을 사용하는 것이 규칙이다.
  • 패키지 이름을 선택하여 패키지에 포함된 내용 및 패키지 간의 관계를 표시할 수 있다.

Affirmation 데이터 클래스

com.example.affirmation 아래 model 패키지를 생성하고 그 아래 Affirmation.kt 파일을 생성했다. 개발자는 데이터를 모델링하거나 표현하는 클래스의 패키지 이름으로 model을 사용하는 경우가 많다.

Affirmation.kt의 내용을 다음과 같이 수정한다.

package com.example.affirmation.model
data class Affirmation(val stringResourceId: Int)

data 키워드를 추가해 데이터 클래스로 만든다.

속성은 하나 이상 정의되어 있어야 하기 때문에 생성자를 만든다. Affirmation 인스턴스를 만들 때 문자열 리소스 ID를 전달해야 한다.

Datasource 클래스

data 패키지를 추가하고 그 아래 Datasource 클래스를 추가한다.

package com.example.affirmation.data

import com.example.affirmation.R
import com.example.affirmation.model.Affirmation

class Datasource {
    fun loadAffirmation(): List<Affirmation>{
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1),
            Affirmation(R.string.affirmation2),
            . . .
            Affirmation(R.string.affirmation10)
        )
    }
}

RecyclerView

레이아웃에 RecyclerView 추가하기

  • item - 목록에 올라갈 단일 데이터 항목. 이 앱에서는 Affirmation 객체 하나를 나타낸다.
  • adapter - RecyclerView에 표시할 수 있도록 데이터를 가져와 준비한다.
  • ViewHolders - view들을 담는 풀. 리싸이클러뷰가 affirmation을 띄우기 위해 뷰를 사용하거나 재사용할 수 있는 pool
  • RecyclerView - Views on screen

    activity_main.xmlRecyclerView를 생성한다.
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager">

</androidx.recyclerview.widget.RecyclerView>
  • layoutManager 는 리사이클러뷰를 선형 목록이나 그리드와 같은 다양한 방식으로 표시할 수 있도록 지원한다.
  • scrollbars 속성도 설정해준다.

list_item.xml

각 항목의 레이아웃이 될 list_item.xml을 res>layout에 생성한다. 코드는 다음과 같다.

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

</TextView>

이 레이아웃은 RecyclerView 에 하위 요소로 추가되므로 ViewGroup 이 필요하지 않다.

ItemAdapter

  1. adapter 패키지를 추가하고 그 아래 ItemAdapter.kt 파일을 생성한다.

  2. ItemAdapter Affirmation 목록을 어댑터에 전달할 수 있도록 생성자에 매개변수를 추가한다.

    class ItemAdapter(private val dataset: List<Affirmation>){}
  1. ItemAdapterContext 매개변수를 추가한다. 앱에 대한 기타 정보는 ItemAdapter 인스턴스에 전달할 수 있는 Context 객체 인스턴스에 저장된다.
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>){}
  1. ViewHolder를 만든다.
    RecyclerView는 뷰와 직접 상호작용하는 것이 아니라 ViewHolders를 처리한다.
    ViewHolderRecyclerView의 목록 중 하나의 항목 뷰를 나타낸다.
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {
    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }
}
  • View타입 변수 view를 매개변수로 추가한다.
  • ItemViewHolder클래스는 RecyclerView.ViewHolder의 서브클래스가 된다. view 매개변수는 슈퍼클래스의 생성자에 전달한다.
  1. 추상 클래스인 RecyclerView.Adapter 에서 ItemAdapter를 확장한다.
    꺾쇠 괄호 안에는 뷰 홀더 유형을 작성한다.
class ItemAdapter(
    private val context: Context,
    private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }
}
  1. 추상 메소드를 구현해야 한다. (이전 강의에서 추상 클래스에 대해 다룰 때 하위 클래스 생성 시 추상 메소드를 전부 구현해줘야 하는 것을 배웠다.)

    ItemAdapter에 커서를 놓고 Control+I를 눌러 구현해야 하는 메소드 목록getItemCount()onCreateViewHolder()onBindViewHolder()을 확인할 수 있다.

    메소드를 하나씩 구현해보자.

  2. getItemCount() 
    데이터 세트의 크기를 반환한다.

// 방법1: 간결
override fun getItemCount() = dataset.size
// 방법2
override fun getItemCount(): Int {
    return dataset.size
}
  1. onCreateViewHolder() 

    새 뷰 홀더를 만들기 위해 레이아웃 관리자가 호출한다. (재사용할 수 있는 기존 뷰 홀더가 없는 경우)

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {

    val adapterLayout = LayoutInflater.from(parent.context)
        .inflate(R.layout.list_item, parent, false)

    return ItemViewHolder(adapterLayout)
}
  • parent 매개변수는 상위 요소 즉 RecyclerView
  • viewType은 리싸이클러뷰에 여러 뷰 유형이 있을 때 이를 구분하기 위해 필요하다.
  • parentcontext에서 LayoutInflater 인스턴스를 가져온다. LayoutInflater는 XML 레이아웃을 뷰 객체의 계층 구조롤 확장하는 방법을 알고 있다.
  • 가져온 LayoutInflater 객체 인스턴스에 마침표를 추가하고 그 뒤에 다른 메서드 호출을 배치하여 실제 목록 항목 뷰를 확장한다.
  1. onBindViewHolder()

    목록 항목 뷰의 콘텐츠를 바꾸기 위해 레이아웃 관리자가 호출한다.

override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
    val item = dataset[position]
    holder.textView.text =  context.resources.getString(item.stringResourceId)
}
  • position으로 데이터셋에서 올바른 Affirmation 객체를 찾는다.
  • 뷰 홀더가 참조하는 모든 뷰를 업데이트해야 하는데 이 경우 textView 하나만 업데이트하면 된다.
  • item.stringResourceId`를 호출해 문자열 리소스 ID를 찾을 수 있는데 이 ID 값은 정수이므로 매핑된 실제 문자열을 찾아야 한다.

MainActivity에 RecyclerView 올리기

  1. Datasource클래스의 인스턴스를 생성하고 loadAffirmations() 메소드를 호출해 데이터셋을 가져온다.
    val myDataset = Datasource().loadAffirmations()
  2. recyclerView 변수
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = ItemAdapter(this, myDataset)
recyclerView.setHasFixedSize(true)
  • findViewById로 레이아웃 내에있는 리싸이클러뷰(id=recycler_view)를 찾는다.
  • recyclerView.adapter로 어댑터를 ItemAdapter로 설정한다.
  • 리싸이클러뷰의 레이아웃 크기가 고정되어 있으므로 setHasFixedSize를 true로 설정했다.
    성능을 개선하기 위한 설정. 콘텐츠를 변경해도 RecyclerView의 레이아웃 크기가 변경되지 않는다는 것을 아는 경우 이 설정을 사용한다.
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val myDataset = Datasource().loadAffirmation()
        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
        recyclerView.adapter = ItemAdapter(this, myDataset)
        recyclerView.setHasFixedSize(true)
    }
}

Affirmation 앱 완성하기

이미지 추가

drawable에 image1 ~ image10 이미지 10장을 추가했다.

리소스 주석

Affirmation 클래스 생성자의 매개변수로 이미지 리소스 ID를 추가해야 한다.

stringResourceId와 imageResourceId는 모두 정숫값이기 때문에 잘못된 순서로 인지를 전달 할 수 있다. 이를 방지하기 위해 리소스 주석을 사용한다.

@StringRes 주석을 문자열 리소스 ID 속성에, @DrawableRes 주석을 드로어블 리소스 ID 속성에 추가한다. 그러면 잘못된 유형의 리소스 ID를 제공하는 경우 경고가 표시된다.

data class Affirmation(
   @StringRes val stringResourceId: Int,
   @DrawableRes val imageResourceId: Int
)

Datasource.kt 수정

Datasource도 두개의 리소스 ID를 전달하도록 수정한다.

Affirmation(R.string.affirmation1, R.drawable.image1),

list_item.xml 수정

목록의 단일 항목에 이미지와 텍스트가 들어가야 하므로 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="wrap_content"
    android:orientation="vertical">
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="194dp"
        android:id="@+id/item_image"
        android:importantForAccessibility="no"
        android:scaleType="centerCrop"/>
    <TextView
        android:id="@+id/item_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </TextView>
</LinearLayout>

ItemAdapter 수정

다음과 같이 ItemViewHolderonBindViewHolder를 수정한다.

ItemViewHolder가 레이아웃에서 참조할 item_image를 작성해준다.

onBinderViewHolder에서는 imageResourceId에 해당하는 이미지를 찾아 setImageResource한다.

class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view){
    val textView: TextView = view.findViewById(R.id.item_title)
    val imageView: ImageView = view.findViewById(R.id.item_image)
}

override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
    val item = dataset[position]
    holder.textView.text = context.resources.getString(item.stringResourceId)
    holder.imageView.setImageResource(item.imageResourceId)
}

UI 수정

  • MaterialCardView 를 사용해 카드뷰를 만든다.
  • text에 padding과 스타일을 적용한다. android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" 처럼 스타일을 가져와 사용할 수 있다.
  • 앱 테마 색상 변경 지난 시간에 배운 color tool을 이용해 색상을 고르고, colors.xmlthemes.xml을 수정한다.

0개의 댓글