본문 바로가기
안드로이드

[안드로이드 1팀] Beginner Track Unit 2 : DIsplay a scrollable list

by Cha_Ji 2020. 12. 9.

간단한 코틀린의 문법 (Mutable List, Immutable List)

여러가지 레이아웃 (FramLayout : RecyclerView, LinearLayout, Material Card View)을 사용해 Scrollable list 만들기

Unit 2 : Display a scrollable list

  • 코틀린 문법 공부를 먼저 할 필요가 있다.
  • How to create and use lists in Kotlin
  • The difference between the List and MutableList, and when to use each one
  • How to iterate over all items of a list and perform an action on each item.

 

  • 위 kotlinplayground에서 코틀린 문법을 실습할 수 있다.

List

  • 선언과 출력
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)
val numbers = listOf(1, 2, 3, 4, 5, 6)

println("List: $numbers")
println("List: " + numbers)  //string + int 도 출력이 가능
  • str + int ??
fun main() {
	val num: Int = 1
	val word: String = "word"
//============================
    val mix = word + num
		val mix = num + word
//==============================
    println("${num::class.simpleName}")  //type을 물어보는 함수
    println("${word::class.simpleName}")
    println("${mix::class.simpleName}")
}
  • 다른 자료형 끼리 연산하면 앞에 있는 자료형을 따라간다.

  • word[0, 1, 2, 3] < = > [0, 1, 2, 3, word]

  • 리스트 내부 함수

println("Size: ${numbers.size}")  //배열의 크기를 알아보는 함수

numbers[0] == numbers.get(0)  //get 함수로 원하는 인덱스를 얻을 수 있다.
numbers.first()
numbers.last()
numbers.reversed()
numbers.sorted()
  • 출력문 안에서 객체 안의 함수를 사용할 때엔 $뒤에 {}를 포함하는 모습을 보인다.

Mutable List

//val entrees = mutableListOf()
val entrees = mutableListOf<String>()
val entrees: MutableList<String> = mutableListOf()

println("Add noodles: ${entrees.add("noodles")}")    // 리스트도 넣을 수 있다.
println("Entrees: $entrees")
  • mutable list에서만 사용 가능한 함수들
entrees.remove("삭제할 요소")
entrees.removeAt("삭제할 인덱스")
entrees.clear()  //리스트의 요소를 모두 비운다.
entrees.isEmpty() //리스트가 비어있는가?
  • var : 가변적
  • val : 불변적

반복문

for (item in list) print(item) // Iterate over items in a list

for (item in 'b'..'g') print(item) // Range of characters in an alphabet

for (item in 1..5) print(item) // Range of numbers

for (item in 5 downTo 1) print(item) // Going backward

for (item in 3..6 step 2) print(item) // Prints: 35

CodeLab Solution Code

  • 코틀린에서 출력과 클래스의 상속 등 기본적인 문법을 사용해 보는 실습
open class Item(val name: String, val price: Int)  //open : 부모 클래스 앞에 붙이는 키워드

class Noodles : Item("Noodles", 10) {
    override fun toString(): String {
        return name
    }
}

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
    override fun toString(): String {
        if (toppings.isEmpty()) {
            return "$name Chef's Choice"
        } else {
            return name + " " + toppings.joinToString()
        }
    }
}

class Order(val orderNumber: Int) {
    private val itemList = mutableListOf<Item>()

    fun addItem(newItem: Item): Order {
        itemList.add(newItem)
        return this
    }

    fun addAll(newItems: List<Item>): Order {
        itemList.addAll(newItems)
        return this
    }

    fun print() {
        println("Order #${orderNumber}")
        var total = 0
        for (item in itemList) {
            println("${item}: $${item.price}")
            total += item.price
        }
        println("Total: $${total}")
    }
}

fun main() {
    val ordersList = mutableListOf<Order>()

    // Add an item to an order
    val order1 = Order(1)
    order1.addItem(Noodles())
    ordersList.add(order1)

    // Add multiple items individually
    val order2 = Order(2)
    order2.addItem(Noodles())
    order2.addItem(Vegetables())
    ordersList.add(order2)

    // Add a list of items at one time
    val order3 = Order(3)
    val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
    order3.addAll(items)
    ordersList.add(order3)

    // Use builder pattern
    val order4 = Order(4)
        .addItem(Noodles())
        .addItem(Vegetables("Cabbage", "Onion"))
    ordersList.add(order4)

    // Create and add order directly
    ordersList.add(
        Order(5)
            .addItem(Noodles())
            .addItem(Noodles())
            .addItem(Vegetables("Spinach"))
    )

    // Print out each order
    for (order in ordersList) {
        order.print()
        println()
    }
}
  • vararg : " 가변 인자 " [variable argument]

    ⇒ 매개변수의 개수를 동적으로 지정할 수 있는 방법

  • Learn More

What you'll learn

  • How to use a RecyclerView to display a list of data.
  • How to use organize your code into packages
  • How to use adapters with RecyclerView to customize how an individual list item looks.

사전 준비하기!

implementation 'androidx.appcompat:appcompat:1.2.0'

// and change it to

implementation 'com.google.android.material:material:1.2.0'
  • string.xml 추가
<resources>
    <string name="app_name">Affirmations</string>
    <string name="affirmation1">I am strong.</string>
    <string name="affirmation2">I believe in myself.</string>
    <string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
    <string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
    <string name="affirmation5">I have so much to be grateful for.</string>
    <string name="affirmation6">Good things are always coming into my life.</string>
    <string name="affirmation7">New opportunities await me at every turn.</string>
    <string name="affirmation8">I have the courage to follow my heart.</string>
    <string name="affirmation9">Things will unfold at precisely the right time.</string>
    <string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>
  • 패키지를 생성하고, kt 파일을 추가하는 과정을 거친다.
  • com.example.affirmations.model : affirmation.kt
  • data class : 데이터만 갖고 기능이 없는 클래스
package com.example.affirmations.model

data class Affirmation(val stringResourceId: Int)
  • com.example.affirmations.data : datasource.kt
package com.example.affirmations.data

import com.example.affirmations.R
import com.example.affirmations.model.Affirmation


class Datasource {

    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1),
            Affirmation(R.string.affirmation2),
            Affirmation(R.string.affirmation3),
            Affirmation(R.string.affirmation4),
            Affirmation(R.string.affirmation5),
            Affirmation(R.string.affirmation6),
            Affirmation(R.string.affirmation7),
            Affirmation(R.string.affirmation8),
            Affirmation(R.string.affirmation9),
            Affirmation(R.string.affirmation10)
        )
    }
}
  • Affirmation객체로 반환한다.

Recycler View

  • 리스트뷰(ListView)의 경우, 리스트 항목이 갱신될 때마다 매번 아이템 뷰를 새로 구성해야 한다

  • 이는 많은 수의 데이터 집합을 표시하는데 있어서, 성능 저하를 야기할 수 있는 요인이 된다.

  • item - One data item of the list to display. Represents one Affirmation object in your app.

  • Adapter - Takes data and prepares it for RecyclerView to display.

  • ViewHolders - A pool of views for RecyclerView to use and reuse to display affirmations.

  • RecyclerView on Screen

  • 사용자가 관리하는 많은 수의 데이터 집합(Data Set)을 개별 아이템 단위로 구성하여 화면에 출력하는 뷰그룹(ViewGroup)이며, 한 화면에 표시되기 힘든 많은 수의 데이터를 스크롤 가능한 리스트로 표시해주는 위젯

  • 어댑터 : 리사이클러뷰에 표시될 아이템 뷰를 생성한다.

  • 뷰홀더 : 화면에 표시될 아이템 뷰를 저장하는 객체이다.

  • 레이아웃 매니저 : 리사이클러뷰가 아이템을 화면에 표시할 때, 아이템 뷰들이 리사이클러뷰 내부에서 배치되는 형태를 관리하는 요소

실습 진행하기

  1. 레이아웃 추가

  1. 어댑터 추가

  • list_item.xml
<?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" />

  1. 아이템어댑터 클래스 추가
package com.example.affirmations.adapter

import android.content.Context
import com.example.affirmations.model.Affirmation

class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

}
  1. 뷰홀더 추가
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)
    }
}
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)
    }
}
  • 위처럼 코드를 적는다면, 오류가 발생한다.
  • 빨간줄 위에서 ALT + ENTER를 누르면 다음과 같이 해결 방안을 보여준다.

  • 위 오류는 필요한 추상 메서드를 구현하지 않았기 때문에 발생한 오류이다.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        TODO("Not yet implemented")
    }

    override fun getItemCount(): Int {
        TODO("Not yet implemented")
    }

    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        TODO("Not yet implemented")
    }
  • 필요한 override를 진행하면 빨간줄이 사라진다.

  • 결과 코드

package com.example.affirmations.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

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)
    }

    //뷰 생성
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.list_item, parent, false)

        return ItemViewHolder(adapterLayout)
    }

    //데이터집합 크기 반환
    override fun getItemCount() = dataset.size

    //뷰 내용 변경
    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val item = dataset[position]
        holder.textView.text =  context.resources.getString(item.stringResourceId)
    }
}
  • Summary
  • Learn More

Display a list of images using cards

What you'll learn

  • How to add images to the list of displayed affirmations in a RecyclerView.
  • How to use MaterialCardView in a RecyclerView item layout.
  • How to make visual changes in the UI to make the app look more polished.

시작하기 전에

images.zip

 

  • 다운로드 받은 이미지를 app/src/main/res/drawable 에 복사한다. ⇒ R.drawable.image1

Affirmation.kt

package com.example.affirmations.model

import androidx.annotation.DrawableRes
import androidx.annotation.StringRes

data class Affirmation(
    @StringRes val stringResourceId: Int,
    @DrawableRes val imageResourceId: Int
)
  • annotation을 추가한다.
  • annotation ?

Dataresource.kt

  • affirmation 객체의 인자가 두개가 되었다. 오류가 발생하지 않게 고쳐주자
package com.example.affirmations.data

import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

class Datasource() {

    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1, R.drawable.image1),
            Affirmation(R.string.affirmation2, R.drawable.image2),
            Affirmation(R.string.affirmation4, R.drawable.image4),
            Affirmation(R.string.affirmation5, R.drawable.image5),
            Affirmation(R.string.affirmation6, R.drawable.image6),
            Affirmation(R.string.affirmation7, R.drawable.image7),
            Affirmation(R.string.affirmation8, R.drawable.image8),
            Affirmation(R.string.affirmation9, R.drawable.image9),
            Affirmation(R.string.affirmation10, R.drawable.image10)
        )
    }
}

list_item.xml

  • 레이아웃을 Linear로 변경하고 수직 방향으로 바꾼다.
  • 이미지도 적절한 높이와 함께 추가한다.
<?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">

    <TextView
        android:id="@+id/item_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

		<ImageView
        android:layout_width="match_parent"
        android:layout_height="194dp"
        android:id="@+id/item_image"
        android:importantForAccessibility="no"
        android:scaleType="centerCrop" />

</LinearLayout>

 

ItemAdapter.kt

  • 이미지뷰를 추가한다.
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)
    }

예쁜 사진과 글귀가 추가되었다!

Polishing the UI : 더 예쁘게 앱을 다듬어 보자!

  • padding 조절

  • 글씨 변경

  • 색깔 변경 ( app/res/values : colors.xml)

  • 테마 추가

  • 앱 아이콘 변경

     

 

결과 화면!

 

  • Summary
  • Learn More

퀴즈!

 

  • 가변적인 배열로 선언해야 요소를 추가하거나 제거할 수 있다.