Android 공부 Unit 2 Layouts

Park Choong Ho·2021년 11월 9일
0

Unit 2 Layouts

클래스 및 상속

추상 클래스: 완전히 구현되어 있지 않아 인스턴스화 할 수 없는 클래스
abstract 키워드 활용

abstract class Dwelling(private var residents: Int) {
	abstract val buildingMaterial: String
    abstract val capacity: Int
fun hasRoom(): Boolean {
    return residents < capacity
}

}


- 클래스 property는 초기화 되거나 abstract되어야 한다.

> **기본 클래스**: 코틀린 상에서 클래스는 기본적으로 최종 클래스이며 서브 클래스로 분류하거나 상속할 수 없습니다. 
> `abstract` 클래스 또는 `open` 키워드로 표시된 클래스만 상속할 수 있습니다.
> `override` 키워드를 사용하여 서브 클래스의 속성과 함수를 재정의 가능합니다.

```kotlin
open class RoundHut(residents: Int) : Dwelling(residents) {
    override val buildingMaterial = "Straw"
    override val capacity = 4
}
  • 추상 클래스에서 정의된 모든 추상 메서드는 추상 클래스의 서브클래스에서 구현되어야 합니다.
abstract class Dwelling(private var residents: Int) {
	abstract val buildingMaterial: String
    abstract val capacity: Int
    
    fun hasRoom(): Boolean {
        return residents < capacity
    }
    
    abstract fun floorArea(): Double
}

super 키워드는 상위 클래스에 정의된 함수를 호출할 수 있습니다.

import kotlin.math.PI
import kotlin.math.sqrt

fun main() {
	val squareCabin = SquareCabin(6, 50.0)
	val roundHut = RoundHut(3, 10.0)
    val roundTower = RoundTower(4, 15.5)
    
    with (squareCabin) {
        println("\nSquare Cabin\n============")
        println("Material: ${buildingMaterial}")
       	println("Capacity: ${capacity}")
    	println("Has room? ${hasRoom()}")
        println("Floor area: %.2f".format(floorArea()))
    }
    
    with (roundHut) {
        println("\nRound Hut\n============")
        println("Material: ${buildingMaterial}")
       	println("Capacity: ${capacity}")
    	println("Has room? ${hasRoom()}")
        getRoom()
        getRoom()
        println("Floor area: %.2f".format(floorArea()))
        println("Carpet size: ${calculateMaxCarpetSize()}")
    }
    
    with (roundTower) {
        println("\nRound Tower\n==========")
    	println("Material: ${buildingMaterial}")
    	println("Capacity: ${capacity}")
    	println("Has room? ${hasRoom()}")
        println("Floor area: %.2f".format(floorArea()))
        println("Carpet size: ${calculateMaxCarpetSize()}")
    }
}

abstract class Dwelling(private var residents: Int) {
	abstract val buildingMaterial: String
    abstract val capacity: Int
    
    fun hasRoom(): Boolean {
        return residents < capacity
    }
    
    abstract fun floorArea(): Double
    
    fun getRoom() {
        if(capacity > residents) {
            residents++
            println("You got a room!")
        } else {
            println("Sorry, at capacity and no rooms left.")
        }
    }
}

class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {
	override val buildingMaterial = "Wood"
    override val capacity = 6
    
    override fun floorArea(): Double {
        return length * length
    }
}

open class RoundHut(residents: Int, val radius: Double) : Dwelling(residents) {
    override val buildingMaterial = "Straw"
    override val capacity = 4
    
    override fun floorArea(): Double {
        return PI * radius * radius
    }
    
    fun calculateMaxCarpetSize(): Double {
        val diameter = 2 * radius
        return sqrt(diameter * diameter / 2)
    }
}

class RoundTower(
    residents: Int,
    radius: Double,
	val floors: Int = 2) : RoundHut(residents, radius) {
    override val buildingMaterial = "Stone"
    override val capacity = 4 * floors
    
    override fun floorArea(): Double {
    	return super.floorArea() * floors
	}
}

XML 레이아웃

Android 제공 UI 요소

  • EditText: 텍스트를 입력하고 수정합니다.
  • TextView: 서비스 질문, 팁 금액과 같은 텍스트를 표시합니다.
  • RadioButton: 각 팁 옵션의 선택 가능한 라디오 버튼입니다.
  • RadioGroup: 라디오 버튼 옵션을 그룹화합니다.
  • Switch: 팁을 반올림할지 여부를 선택하는 켜기/끄기 전환 버튼입니다.

이번에는 Layout Editor를 대신해 XML을 수정하여 앱 레이아웃을 빌드해 보겠습니다. XML은 확장성 마크업 언어(eXtensible Markup Language) 를 의미하며 텍스트 기반 문서를 사용하여 데이터를 설명하는 방법입니다. XML은 확장 가능하고 매우 유연하므로 Android 앱의 UI 레이아웃 정의를 비롯하여 다양한 용도로 사용됩니다. 앱의 문자열과 같은 다른 리소스도 strings.xml이라는 XML 파일에 정의된다고 이전 Codelab에서 알아봤습니다.

ConstraintLayoutViewGroup의 서브클래스입니다.

<ConstraintLayout>
	<TextView
		text="Hello World!">
	</TextView>
</ConstraintLayout>

<?xml version="1.0" encoding="utf-8"?>  
<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"  
	tools:context=".MainActivity">  
  
 	<TextView android:layout_width="wrap_content"  
 		android:layout_height="wrap_content"  
 		android:text="Hello World!"  
 		app:layout_constraintBottom_toBottomOf="parent"  
 		app:layout_constraintLeft_toLeftOf="parent"  
 		app:layout_constraintRight_toRightOf="parent"  
 		app:layout_constraintTop_toTopOf="parent" />  
  
</androidx.constraintlayout.widget.ConstraintLayout>

androidx.constraintlayout.widget.ConstraintLayout로 표시되는 것은 ConstraintLayout이 핵심 안드로이드 플랫폼외에도 추가 기능을 제공하는 코드 라이브러리가 포함된 Android Jetpack의 일부이기 때문에 그렇습니다.

xmlns는 XML 네임스페이스를 나타내고 각줄은 스키마나 이러한 단어 관련 속성 어휘를 정의합니다.

ConstraintLayout 하부 UI 요소에는 match_parent를 설정할 수 없습니다.

  • 너비를 0dp 입력시 너비 계산을 하지않고 뷰에 적용된 제약조건을 만족시키기만 한다. 다른 요소에도 해당
  • Gradle: 스튜디오에서 사용하는 자동화된 빌드 시스템
  • 뷰 결합: https://developer.android.com/topic/libraries/view-binding
  • lateinit: 변수가 초기화 된후에 사용되어야 함을 알려주는 키워드, 해당 변수를 초기화 하지 않으면 앱이 비정상 종료됨.

참고: 결합 클래스의 이름은 XML 파일의 이름을 카멜 표기법으로 변환하고 이름 끝에 'Binding'을 추가하여 생성됩니다. 마찬가지로 각 뷰를 위한 참조는 밑줄을 삭제하고 뷰 이름을 카멜 표기법으로 변환하여 생성됩니다. 예를 들어 activity_main.xmlActivityMainBinding이 되고 binding.textView@id/text_view에 액세스할 수 있습니다.

앱 테마 변경

알파값을 포함해서 4개의 16진수 숫자로 표현할 수 있습니다. 알파가 포함되지 않은 경우에는 알파 값을 #FF=100% (불투명)으로 간주됩니다.

앱 아이콘

앱아이콘을 2개의 레이어로 구성

  • 포그라운드 레이어
  • 백그라운드 레이어

포그라운드 레이어는 백그라운드 레이어 위에 쌓인다.

벡터 드로어블 vs 비트맵 이미지

비트맵 이미지는 각 픽셀의 색상 정보를 제외하고 보유한 이미지에 관해 잘 알지 못합니다. 반면에 벡터 그래픽은 이미지를 정의하는 모양을 그리는 방법을 알고 있습니다. 이러한 지침은 색상 정보와 함께 일련의 점과 선, 곡선으로 구성됩니다. 벡터 그래픽은 화질 저하 없이 모든 화면 밀도의 어떤 캔버스 크기로도 조정할 수 있다는 것이 장점입니다.

벡터 드로어블은 Android의 벡터 그래픽 구현으로, 휴대기기에서 충분히 유연하도록 만들어졌습니다. 이러한 가능한 요소를 사용하여 XML로 정의할 수 있습니다. 모든 밀도 버킷에 비트맵 애셋 버전을 제공하는 대신 이미지를 한 번만 정의하면 됩니다. 따라서 앱의 크기가 줄어 유지하기가 쉬워집니다.

참고: 비트맵 이미지와 비교해 벡터 드로어블을 사용하는 데는 단점이 있습니다. 예를 들어 아이콘은 단순한 모양으로 구성되기 때문에 벡터 드로어블로 적합할 수 있지만 사진은 일련의 모양으로 설명하기 어려울 수 있습니다. 이 경우 비트맵 애셋을 사용하는 것이 더 효율적입니다.

 코틀린에서 목록 사용

Kotlin에는 2가지 목록 유형을 가지고 있습니다.

  • 읽기 전용 목록: List는 만든 후 수정할 수 없습니다.
fun main() {
    val numbers = listOf(1, 2, 3, 4, 5, 6)
    println("List: $numbers")
    println("Size: ${numbers.size}")
    
    // Access elements of the list
    println("First element: ${numbers[0]}")
    println("Second element: ${numbers[1]}")
    println("Last index: ${numbers.size - 1}")
    println("Last element: ${numbers[numbers.size - 1]}")
    println("First: ${numbers.first()}")
    println("Last: ${numbers.last()}")

    // Use the contains() method
    println("Contains 4? ${numbers.contains(4)}")
    println("Contains 7? ${numbers.contains(7)}")
}
List: [1, 2, 3, 4, 5, 6]
Size: 6
First element: 1
Second element: 2
Last index: 5
Last element: 6
First: 1
Last: 6
Contains 4? true
Contains 7? false

읽기 전용 List에서는 요소를 추가하거나 변경할 수 없습니다. 코드를 실행하면 오류 메시지가 여러 개 표시됩니다. 이 오류 메시지들은 add() 메서드가 List에 없고 요소의 값을 변경할 수 없음을 나타냅니다.

읽기 전용 목록을 변경할 수 없는 것을 직접 확인했습니다. 그러나 목록을 변경하지는 않지만 새 목록을 반환하는 여러 작업이 목록에 있습니다. 이 중 두 개가 reversed()sorted()입니다. reversed() 함수는 요소가 역순으로 있는 새 목록을 반환하고 sorted()는 요소가 오름차순으로 정렬된 새 목록을 반환합니다.

fun main() {
	val colors = listOf("green", "orange", "blue")
    println("Reversed List: ${colors.reversed()}")
    println("List: $colors")
}
Reversed list: [blue, orange, green]
List: [green, orange, blue]
fun main() {
	val colors = listOf("green", "orange", "blue")
	println("Sorted list: ${colors.sorted()}")
}
Sorted list: [blue, green, orange]
fun main() {
	val oddNumbers = listOf(5, 3, 7, 1)
	println("List: $oddNumbers")
	println("Sorted list: ${oddNumbers.sorted()}")
}
List: [5, 3, 7, 1]
Sorted list: [1, 3, 5, 7]
  • 변경 가능한 목록: MutableList는 만든 후 수정할 수 있습니다. 즉, 요소를 추가하거나 삭제, 업데이트할 수 있습니다.
fun main() {
	val entrees = mutableListOf()
}

위 코드를 실행하면 아래와 같은 에러가 발생합니다.

Not enough information to infer type variable T

앞서 말했듯 MutableListList를 만들때 Kotlin은 전달된 인수에서 목록에 포함된 인수들을 바탕으로 유형을 추론합니다. 요소 없이 빈 목록을 초기화 할때는 유형을 추론할 수 없기에 명시적으로 표시해 주어야합니다.

val entrees = mutableListOf<String>()
fun main() {
	val entrees = mutableListOf<String>()
    println("Entrees: $entrees")
}
Entrees: []

변경 가능한 목록은 요소를 추가하고 삭제, 업데이트할 때 흥미로워집니다.
1. entrees.add("noodles").를 사용하여 목록에 "noodles"를 추가합니다. add() 함수는 목록에 요소가 성공적으로 추가되면 true를 반환하고 추가되지 않으면 false를 반환합니다.
2. 목록을 출력하여 "noodles"가 실제로 추가되었는지 확인합니다.

println("Add noodles:${entrees.add("noodles")}")
println("Entrees: $entrees")
Add noodles: true
Entrees: [noodles]

add()를 사용하여 요소를 하나씩 추가하는 대신 addAll()을 사용하여 한 번에 여러 요소를 추가하고 목록을 전달할 수 있습니다.

val moreItems = listOf("ravioli", "lasagna", "fettuccine")

println("Add list: ${entrees.addAll(moreItems)}")
println("Entrees: $entrees")
Add list: true
Entrees: [noodles, spaghetti, ravioli, lasagna, fettuccine]

이제 이 목록에 숫자를 추가해보세요.

entrees.add(10)

다음 오류가 표시되면서 실패합니다.

The integer literal does not conform to the expected type String

entrees 목록에서는 String 유형 요소를 예상하는데 개발자는 Int를 추가하려고 하기 때문입니다. 올바른 데이터 유형의 요소만 목록에 추가해야 합니다. 그러지 않으면 컴파일 오류가 발생합니다. 이는 Kotlin이 유형 안전성으로 코드를 더 안전하게 보호하는 한 가지 방법입니다.

fun main() {    
	val entrees = mutableListOf<String>()    
	println("Entrees: $entrees")    
	
	// Add individual items using add()    
	
	println("Add noodles: ${entrees.add("noodles")}")    
	println("Entrees: $entrees")    
	println("Add spaghetti: ${entrees.add("spaghetti")}")    	
	println("Entrees: $entrees")    
	
	// Add a list of items using addAll()    
	
	val moreItems = listOf("ravioli", "lasagna", "fettuccine")  
	println("Add list: ${entrees.addAll(moreItems)}")    
	println("Entrees: $entrees")    
	
	// Remove an item using remove()    
	
	println("Remove spaghetti: ${entrees.remove("spaghetti")}")
	println("Entrees: $entrees")    
	println("Remove item that doesn't exist: ${entrees.remove("rice")}")    
	println("Entrees: $entrees")    
	
	// Remove an item using removeAt() with an index    
	
	println("Remove first element: ${entrees.removeAt(0)}")    
	println("Entrees: $entrees")    
	
	// Clear out the list    
	
	entrees.clear()    
	println("Entrees: $entrees")    
	
	// Check if the list is empty    
	
	println("Empty? ${entrees.isEmpty()}")

참고: 다음은 특정 단계의 범위(매번 1씩 증분하는 대신)와 함께 사용하는 등 for 루프로 할 수 있는 다른 작업입니다.
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

RecyclerView

RecyclerView를 만들고 사용하는 데는 많은 부분이 관련됩니다. 이러한 부분을 분업이라고 생각할 수 있습니다. 개요를 보여주는 아래 다이어그램에서 구현하는 각 부분에 관해 자세히 알아볼 수 있습니다.

  • 항목 - 표시할 목록의 단일 데이터 항목입니다. 앱의 Affirmation 객체 하나를 나타냅니다.
  • 어댑터 - RecyclerView에서 표시할 수 있도록 데이터를 가져와 준비합니다.
  • ViewHolder - 확인을 표시하기 위해 사용하거나 재사용할 RecyclerView용 뷰의 풀입니다.
  • RecyclerView - 화면에 표시되는 뷰입니다.

앱에는 Datasource에서 데이터를 가져와 각 AffirmationRecyclerView에 항목으로 표시할 수 있도록 형식을 지정하는 방법이 필요합니다.

어댑터는 데이터를 RecyclerView에서 사용할 수 있는 형식으로 조정하는 설계 패턴입니다. 이 경우에는 RecyclerView.에 표시할 수 있도록 loadAffirmations()에서 반환된 목록에서 Affirmation 인스턴스를 가져와 목록 항목 뷰로 전환하는 어댑터가 필요합니다.

앱을 실행하면 RecyclerView가 어댑터를 사용하여 화면에 데이터를 표시하는 방법을 파악합니다. RecyclerView는 목록의 첫 번째 데이터 항목을 위한 새 목록 항목 뷰를 만들도록 어댑터에 요청합니다. 뷰가 생성된 후에는 항목을 그리기 위한 데이터를 제공하도록 어댑터에 요청합니다. 이 프로세스는 RecyclerView에 화면을 채울 뷰가 더 이상 필요하지 않을 때까지 반복됩니다. 한 번에 목록 항목 뷰 세 개만 화면에 들어가는 경우 RecyclerView는 전체 목록 항목 뷰 10개가 아닌 3개만 준비하도록 어댑터에 요청합니다.

이 단계에서는 RecyclerView에 표시될 수 있도록 Affirmation 객체 인스턴스를 조정하는 어댑터를 빌드합니다.

profile
백엔드 개발자 디디라고합니다.

0개의 댓글