추상 클래스: 완전히 구현되어 있지 않아 인스턴스화 할 수 없는 클래스
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
}
}
Android 제공 UI 요소
EditText
: 텍스트를 입력하고 수정합니다.TextView
: 서비스 질문, 팁 금액과 같은 텍스트를 표시합니다.RadioButton
: 각 팁 옵션의 선택 가능한 라디오 버튼입니다.RadioGroup
: 라디오 버튼 옵션을 그룹화합니다.Switch
: 팁을 반올림할지 여부를 선택하는 켜기/끄기 전환 버튼입니다.
이번에는 Layout Editor를 대신해 XML을 수정하여 앱 레이아웃을 빌드해 보겠습니다. XML은 확장성 마크업 언어(eXtensible Markup Language) 를 의미하며 텍스트 기반 문서를 사용하여 데이터를 설명하는 방법입니다. XML은 확장 가능하고 매우 유연하므로 Android 앱의 UI 레이아웃 정의를 비롯하여 다양한 용도로 사용됩니다. 앱의 문자열과 같은 다른 리소스도 strings.xml
이라는 XML 파일에 정의된다고 이전 Codelab에서 알아봤습니다.
ConstraintLayout은 ViewGroup의 서브클래스입니다.
<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
를 설정할 수 없습니다.
Gradle
: 스튜디오에서 사용하는 자동화된 빌드 시스템뷰 결합
: https://developer.android.com/topic/libraries/view-bindinglateinit
: 변수가 초기화 된후에 사용되어야 함을 알려주는 키워드, 해당 변수를 초기화 하지 않으면 앱이 비정상 종료됨.참고: 결합 클래스의 이름은 XML 파일의 이름을 카멜 표기법으로 변환하고 이름 끝에 'Binding'을 추가하여 생성됩니다. 마찬가지로 각 뷰를 위한 참조는 밑줄을 삭제하고 뷰 이름을 카멜 표기법으로 변환하여 생성됩니다. 예를 들어
activity_main.xml
은ActivityMainBinding
이 되고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
앞서 말했듯 MutableList
나 List
를 만들때 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
를 만들고 사용하는 데는 많은 부분이 관련됩니다. 이러한 부분을 분업이라고 생각할 수 있습니다. 개요를 보여주는 아래 다이어그램에서 구현하는 각 부분에 관해 자세히 알아볼 수 있습니다.
Affirmation
객체 하나를 나타냅니다.RecyclerView
에서 표시할 수 있도록 데이터를 가져와 준비합니다.RecyclerView
용 뷰의 풀입니다.앱에는
Datasource
에서 데이터를 가져와 각Affirmation
을RecyclerView
에 항목으로 표시할 수 있도록 형식을 지정하는 방법이 필요합니다.어댑터는 데이터를
RecyclerView
에서 사용할 수 있는 형식으로 조정하는 설계 패턴입니다. 이 경우에는RecyclerView.
에 표시할 수 있도록loadAffirmations()
에서 반환된 목록에서Affirmation
인스턴스를 가져와 목록 항목 뷰로 전환하는 어댑터가 필요합니다.앱을 실행하면
RecyclerView
가 어댑터를 사용하여 화면에 데이터를 표시하는 방법을 파악합니다.RecyclerView
는 목록의 첫 번째 데이터 항목을 위한 새 목록 항목 뷰를 만들도록 어댑터에 요청합니다. 뷰가 생성된 후에는 항목을 그리기 위한 데이터를 제공하도록 어댑터에 요청합니다. 이 프로세스는RecyclerView
에 화면을 채울 뷰가 더 이상 필요하지 않을 때까지 반복됩니다. 한 번에 목록 항목 뷰 세 개만 화면에 들어가는 경우RecyclerView
는 전체 목록 항목 뷰 10개가 아닌 3개만 준비하도록 어댑터에 요청합니다.이 단계에서는
RecyclerView
에 표시될 수 있도록Affirmation
객체 인스턴스를 조정하는 어댑터를 빌드합니다.