+ 항공대학교 김철기 교수님의 객체 지향 프로그래밍 과목 내용를 정리한 글입니다.
build.gradle.kts 수정하기
android 블록 안에 코드를 추가한다.
- XML 레이아웃 파일과 Kotlin간의 상호작용을 향상시켜주는 라이브러리를 활성화한다.
- 자동으로 binding 클래스가 생성되어 XML 레이아웃과 상호작용이 가능하다.
ex) MainActicity -> ActivityMainBinding 인스턴스 생성
ex) InputFragment -> FragmentInputBinding 인스턴스 생성
viewBinding{
enable = true
}
dependencies 블록에 코드를 추가한다.
- dependency(종속성): 프로젝트에서 사용하려는 외부 코드를 프로젝트로 가져오는 방법 (다른 개발자가 만든 라이브러리 사용)
- AndroidX 라이브러리의 일부인 fragment-ktx 라이브러리를 프로젝트에 추가한다.
- implementaion: 종속성을 추가할 때 사용되는 함수
dependencies {
implementation("androidx.fragment:fragment-ktx:1.6.1") // 추가
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.9.0") // 버전 수정 (1.10.0 -> 1.9.0)
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
=> gradle 파일을 수정하고 좌측 상단의 Sync now 버튼을 눌러 적용시킨다.
레이아웃 설정하기
+ 레이아웃을 설정할 때는 Palette에 있는 요소를 Component Tree에 드래그하여 설정한다.
기본적으로 가장 바깥쪽 레이아웃은 ConstraintLayout으로 설정되어있다. (변경 불가)
+ LinearLayout: 구성요소를 나열하기 위한 레이아웃
+ FrameLayout: 구성요소를 꽉 채우기 위한 레이아웃 (하나의 구성요소만 가능)
+ ConstraintLayout: 자유도가 높아 위치 선정 등을 쉽게 할 수 있다. (체인 이용)
ConstraintLayout 위에 LinearLayout (vertical)을 추가한다.- 레이아웃 내용을 위아래로 쌓기 위해서이다.
LinearLayout (vertical) 위에 LinearLayout (horizontal)을 추가한다.
- LinearLayout (vertical) 위에 버튼 2개를 가로로 담아야 하기 때문이다.
LinearLayout (horizontal) 위에 Button 2개를 추가한다.
- 왼쪽 버튼의 id를 'btn_input', text'를 '입력'으로 설정한다.
- 오른쪽 버튼의 id를 'btn_result', text'를 '결과'로 설정한다.
=> 설정 후 Refactor를 이용해 적용해야한다.
LinearLayout (horizontal)의 layout_width, layout_height를 변경한다.
- 변경 전, 화면 전체를 차지하고 있기 때문이다. (버튼 아래에는 컨텐트를 넣어야한다.)
- match_parent: 자신을 포함하고 있는 부모 컴포넌트 크기에 맞춘다. (초기 설정)
- wrap_content: 자신을 포함하고 있는 내부(자식) 컴포넌트 크기에 맞춘다.
=> layout_height를 wrap_content로 변경한다.
LinearLayout (vertical) 위에 FrameLayout을 추가한다.
Activity vs Fragment
Activity
- 화면 전체를 나타내는 객체이다.
- 실행 단위를 나타내는 Context 클래스의 하위 클래스이다.
- 독립적으로 자원을 분리하여 관리한다. 즉 Activity 끼리는 자원(메모리 등)을 공유하지 않는다.
-
Fragment
- 화면 일부를 나타내는 객체이다.
- 기본적으로 Activity 안에 존재한다.
- 자신을 포함한 Activity나 Fragment끼리 자원 공유가 가능하다.
- Fragment를 생성할 때는 클래스이기 때문에 이름을 대문자로 시작하는 것이 좋다.
InputFragment 생성하기
MainActivity 파일이 있는 곳에 Fragment(Blank)를 추가한다. (마우스 우측 클릭 -> New -> Fragment)
- 이름은 'InputFragment'로 설정한다.
fragment_input.xml의 Component Tree에서 frameLayout을 FrameLayout -> ConstraintLayout으로 변경한다.
- 내용의 위치를 자유롭게 하기 위해서 변경한다.
- Layout 이름과 Layout 종류가 일치하지 않아 헷갈리므로 id를 지운다. (이름: ConstraintLayout)
ConstraintLayout 위에 Plain Text를 추가한다.
- 상하좌우 체인을 모두 연결하여 위치를 가운데로 배치한다. (Constraint Layout의 강점)
- layout_width를 match_parent로 변경한다.
- text ('Name')을 지우고 id를 edit_text로 설정한다.
Fragment 코드 작성하기
inflate
- XML 레이아웃 파일을 메모리에 load한다.
- 레이아웃에 정의된 뷰 요소를 실제 뷰 객체로 만드는 프로세스이다.
(View = 사용자 UI의 구성요소 = Component)
=> XML에 있는 Component들을 실제 프로퍼티로 접근할 수 있다.
onCreate
- Fragment 객체가 처음 생성될 때 호출되는 함수이다. (초기 설정)
- UI 구성요소들을 생성하거나 레이아웃을 화면에 추가하는 작업은 수행하지 않는다.
- UI 관련 작업은 onCreateView 함수 또는 이후 단계에서 수행한다.
onCreateView
- Fragment가 화면에 표시될 때 호출되는 함수이다.
- 해당 Fragment의 사용자 인터페이스를 만들고 반환한다.
Fragment를 생성할 때, String 파라미터의 내용으로 text의 내용을 채우도록 InputFragment.kt를 수정한다.
- Fragment 레이아웃을 inflate하고 Binding 객체를 생성한다.
package com.example.mymultifragapplication
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.mymultifragapplication.databinding.FragmentInputBinding
class InputFragment(val str: String) : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentInputBinding.inflate(inflater, container, false)
// binding: InputFragment의 바인딩 객체
binding.editText.setText(str)
// InputFragment의 'editText'의 텍스트를 str로 설정
return binding.root
// binding의 최상위(루트) 뷰 반환
}
}
MainActivity 코드 작성하기
onCreate
- Activity가 화면에 표시될 때 호출되는 함수이다.
- 해당 Activity의 사용자 인터페이스를 만들고 반환한다.
Transaction
- 하나의 꾸러미(작업 단위)를 생성하여 (begin Transaction) 여러 작업을 넣고, 동시에 실행 (commit) 한다.
MainActivity의 바인딩 객체를 생성하고, Transaction을 이용하여 Fragment를 배치한다.
package com.example.mymultifragapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.mymultifragapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
// binding: MainActivity의 바인딩 객체
val tr = supportFragmentManager.beginTransaction()
// Fragment를 관리하는 FragmentManager를 사용하여 Fragment 트랜잭션 시작
tr.replace(binding.frmFrag.id, InputFragment("KAU"))
// frmFrag id를 가진 fragment를 InputFragment로 교체
tr.commit()
// Fragment 트랜잭션을 확정하고, 변경 사항 적용
setContentView(binding.root)
// Activity 화면을 binding의 최상위(루트) 뷰로 설정
}
}
영역 함수를 이용하여 MainActivity 코드 작성하기
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
// binding: MainActivity의 바인딩 객체
// Fragment 설정
supportFragmentManager.beginTransaction().run{
replace(binding.frmFrag.id, InputFragment("KAU"))
commit()
}
setContentView(binding.root)
}
}
=> 그러나 Android Studio에서는 Fragment를 생성할 때, 생성자 파라미터에 아무것도 받지 않도록 권고한다.
예를 들어, 핸드폰을 가로로 돌렸다가 다시 세로로 돌리는 과정에서 자동으로 생성자 함수가 호출이 된다.
이 때, 파라미터에는 아무것도 없이 호출이 되기 때문에 문제가 발생한다.
=> Factoty Pattern을 사용하여 문제를 해결하자!
Factory Pattern을 이용하여 Fragment 작성하기
Factory Pattern
- 생성자를 이용하지 않고 Factory 객체의 함수를 통해 간접적으로 객체를 생성하는 방법
Bundle
- [key : value]의 쌍을 담는 바구니
- key: 문자열
- value: 어떤 객체든 가능
- putString("key", value) 함수를 통해 담을 수 있다.
- getString("key") 함수를 통해 value를 꺼낼 수 있다. (value 반환)
arguments
- 모든 Fragment 객체가 갖는 Bundle 프로퍼티이다.
< InputFragment.kt >
package com.example.mymultifragapplication
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.mymultifragapplication.databinding.FragmentInputBinding
class InputFragment() : Fragment() {
var text: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
text = arguments?.getString("text")
// argument 안의 "text" 키에 해당하는 value값을 text에 저장
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentInputBinding.inflate(inflater, container, false)
// binding: InputFragment의 바인딩 객체
binding.editText.setText(text)
// InputFragment의 'editText'의 text값을 str로 설정
return binding.root
// binding의 최상위(루트) 뷰 반환
}
companion object {
@JvmStatic
fun newInstance(str: String): InputFragment {
val frag = InputFragment()
frag.arguments = Bundle()
frag.arguments?.putString("text", str)
return frag
}
/*
@JvmStatic
fun newInstance(str: String) =
InputFragment().apply {
arguments = Bundle().apply {
putString("text", str)
}
}
*/
}
}
< MainActivity.kt >
package com.example.mymultifragapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.mymultifragapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
// binding: MainActivity의 바인딩 객체
// Fragment 설정
supportFragmentManager.beginTransaction().run{
replace(binding.frmFrag.id, InputFragment.newInstance("KAU"))
commit()
}
setContentView(binding.root)
}
}
영역 함수를 이용하여 Fragment 작성하기
여러 흐름에서 프로퍼티를 null로 설정할 수 있기 때문에 프로퍼티는 스마트 캐스트가 되지 않는다.
=> let을 이용하여 널 안전성을 처리해야한다.
< InputFragment.kt >
package com.example.mymultifragapplication
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.mymultifragapplication.databinding.FragmentInputBinding
class InputFragment() : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentInputBinding.inflate(inflater, container, false)
// binding: InputFragment의 바인딩 객체
arguments?.let {
binding.editText.setText(arguments?.getString("text"))
// InputFragment의 'editText'의 text값을 argument의 "text" key를 가진 value로 설정
}
// arguments가 널이 아닐 때만 실행
return binding.root
// binding의 최상위(루트) 뷰 반환
}
companion object {
@JvmStatic
fun newInstance(str: String? = null) =
InputFragment().apply {
arguments = Bundle().apply {
str?.let {
putString("text", it)
}
}
}
}
}
< MainActivity.kt >
package com.example.mymultifragapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.mymultifragapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
// binding: MainActivity의 바인딩 객체
// Fragment 설정
supportFragmentManager.beginTransaction().run{
replace(binding.frmFrag.id, InputFragment.newInstance())
commit()
}
setContentView(binding.root)
}
}
ResultFragment 생성하기
MainActivity 파일이 있는 곳에 Fragment(Blank)를 추가한다. (마우스 우측 클릭 -> New -> Fragment)
- 이름은 'ResultFragment'로 설정한다.
fragment_Result.xml의 Component Tree에서 frameLayout을 FrameLayout -> ConstraintLayout으로 변경한다.
ConstraintLayout 위에 TextView를 추가한다. (텍스트를 보여주기만 한다.)
- 상하좌우 체인을 모두 연결하여 위치를 가운데로 배치한다.
- text ('textview')을 지우고 id를 txt_result로 설정한다.
- Declared Attribute의 +버튼을 눌러서 size를 30sp로 설정한다.
< ResultFragment.kt >
package com.example.mymultifragapplication
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.mymultifragapplication.databinding.FragmentResultBinding
class ResultFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentResultBinding.inflate(inflater, container, false).apply {
arguments?.let {
txtResult.text = it.getInt("kor_count").toString() // String형으로 변환해야 볼 수 있다.
// ResultFragment의 'txtResult'의 text값을 argument의 "kor_count" key를 가진 value로 설정
}
}
return binding.root
}
companion object {
@JvmStatic
fun newInstance(count: Int) =
ResultFragment().apply {
arguments = Bundle().apply {
putInt("kor_count", count )
}
}
}
}
화면 전환 버튼 설정하기
< InputFragment.kt >
package com.example.mymultifragapplication
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.setFragmentResult
import com.example.mymultifragapplication.databinding.FragmentInputBinding
class InputFragment() : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentInputBinding.inflate(inflater, container, false)
// binding: InputFragment의 바인딩 객체
// arguments가 널이 아닐 때만 실행
arguments?.let {
binding.editText.setText(it.getString("text"))
// InputFragment의 'editText'의 text값을 argument의 "text" key를 가진 value로 설정
}
// editText에 텍스트가 입력될 때 발생하는 변경사항을 감지 -> 코드 실행
binding.editText.addTextChangedListener {
val resBundle = Bundle().apply {
putString("input", binding.editText.text.toString())
}
// Fragment 간 데이터 송신 -> MainActivity에서 setFragmentResultListener를 통해 수신
setFragmentResult("input_text", resBundle)
}
return binding.root
// binding의 최상위(루트) 뷰 반환
}
companion object {
@JvmStatic
fun newInstance(str: String? = null) =
InputFragment().apply {
arguments = Bundle().apply {
str?.let {
putString("text", it)
}
}
}
}
}
< MainActivity.kt >
package com.example.mymultifragapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.Fragment
import com.example.mymultifragapplication.databinding.ActivityMainBinding
// String의 확장 프로퍼티: 한글 개수
val String.numOfKoreanCharacters: Int
get() {
var count = 0
for (i in 0 until length) {
if (this[i] >= 0xAC00.toChar() && this[i] <= 0xD7AF.toChar()){
count += 1
}
}
return count
}
class MainActivity : AppCompatActivity() {
var text: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// MainActivity의 바인딩 객체
val binding = ActivityMainBinding.inflate(layoutInflater)
// Fragment 설정 함수
fun replaceFragment(frag: Fragment) {
supportFragmentManager.beginTransaction().run{
replace(binding.frmFrag.id, frag)
commit()
}
}
binding.run {
// btnInput 버튼이 눌렸을 때 실행
btnInput.setOnClickListener {
replaceFragment(InputFragment.newInstance())
}
// btnResult 버튼이 눌렸을 때 실행
btnResult.setOnClickListener {
replaceFragment(ResultFragment.newInstance(text?.numOfKoreanCharacters ?: 0))
}
}
// // Fragment 간 데이터 수신
supportFragmentManager.setFragmentResultListener("input_text", this) { _, bundle ->
text = bundle.getString("input")
}
replaceFragment(InputFragment.newInstance())
setContentView(binding.root)
}
}
< ResultFragment.kt >
package com.example.mymultifragapplication
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.mymultifragapplication.databinding.FragmentResultBinding
class ResultFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentResultBinding.inflate(inflater, container, false).apply {
arguments?.let {
txtResult.text = it.getInt("kor_count").toString() // String형으로 변환해야 볼 수 있다.
// ResultFragment의 'txtResult'의 text값을 argument의 "kor_count" key를 가진 value로 설정
}
}
return binding.root
}
companion object {
@JvmStatic
fun newInstance(count: Int) =
ResultFragment().apply {
arguments = Bundle().apply {
putInt("kor_count", count )
}
}
}
}
'Study > Kotlin Study' 카테고리의 다른 글
[Kotlin] 하위 클래스 초기화 (0) | 2023.10.23 |
---|---|
[Kotlin] 하위 클래스 선언 (2) | 2023.10.23 |
[Kotlin] 영역 함수 (0) | 2023.10.09 |
[Kotlin] 확장 (0) | 2023.10.09 |
[Kotlin] 함수형 프로그래밍 (0) | 2023.09.22 |