이 글은 이것이 안드로이드다 with 코틀린(개정판)를 참고하여 작성하였습니다.
작성자 : 김예은
개발환경은 Windows, Android Studio입니다.
1. 프래그먼트란? (Fragment)
- 프래그먼트는 앱의 전체 UI에서 어딘가에 반복적으로 재사용 가능한 부분을 말한다.(프래그먼트의 존재 이유)
- Activity : 앱 전체적인 사용자 인터페이스(UI)에 포함될 요소들을 배치하는 곳
- Fragment : 단일 화면이나 화면 일부에 관한 사용자 인터페이스를 정의하는데 적합
- 프래그먼트는 자체 레이아웃(xml파일 정의 가능)을 가질 수 있으며 자체 생명 주기를 보유한다. 또한 자체 입력 이벤트를 받으며 처리할 수 있다. (자체 레이아웃을 갖는 것은 선택 사항, 자체 UI가 없는 프래그먼트도 만들 수 있다)
- 프래그먼트는 독립적으로 존재할 수 없고, 반드시 Activity나 다른 프래그먼트에 호스팅 되어야 한다.
- hosting = 주인 역할을 한다
- 프래그먼트의 뷰 계층 구조는 호스트 뷰 계층 구조의 일부가 된다.
- Fragment를 Activity의 하위 모듈이라고도 생각할 수 있으므로, 자체적인 생명 주기와 입력 이벤트 수신을 할 수 있다. 하지만, Activity의 생명 주기에 영향을 받기 때문에, Activity가 destroy되면 Fragment도 destroy된다.
- Fragment를 추가 또는 삭제하는 트랜잭션 수행 시 Activity가 관리하는 백 스택에 추가할 수 있다. 이때, 백 스택은 트랜잭션 기록되며 백스택을 이용하여 트랜잭션을 거꾸로 수행할 수 있다 (ex. 뒤로 가기)
- Android Jetpack 라이브러리 중 Navigation, BottomNavigationView, ViewPager2등은 프래그먼트와 호환되도록 설계되어 있어서 프래그먼트가 해당 라이브러리와 함께 자주 사용된다.
- ex) 카카오톡 또는 인스타그램 하단 메뉴 바 (BottomNavigationView)
- ex) Youtube 웹 페이지를 안드로이드 어플리케이션으로 가정할 경우 왼쪽 navigation bar를 Fragment라고 나눌 수 있다.
- 한 Activity에서 여러 Fragment를 갖거나 한 Fragment가 여러 Activity에서 사용되는 것은 가능하나, 하나의 Fragment를 다른 Fragment에서 직접 조작하는 경우는 불가능해야한다.
Fragment의 장점
- 앱의 단일 화면이나 부분 화면을 프래그먼트로 구현하면 런타임 시 UI모습을 사용자와 상호작용하면서 실시간으로 수정할 수 있다.
- 사용자가 앱을 실행하여 사용하는 도중(Activity is running)에 Activity의 모양을 수정할 수 있다는 의미.
- 예를 들어, BottomNavigationView가 존재하는 앱에서는 사용자가 '홈 탭'을 클릭하면 홈 화면이 나오고, '마이페이지 탭'을 클릭하면 마이페이지 화면이 나온다. 이러한 현상이 런타임동안 사용자와 상호작용을 하면서 앱의 UI가 실시간으로 바뀌는 것이라 할 수 있다. (프래그먼트 추가/교체/삭제 등의 작업이 실행됨으로써 화면이 바뀌는 것처럼 보인다)
- 런타임 동안 프래그먼트의 변경 사항(추가/교체/삭제)이 발생하면 FragmentManager라는 것이 관리하는 프래그먼트 백 스택(Activity 백 스택과 다름)에 변경 사항 히스토리를 저장하여 기록할 수 있다.
- 프래그먼트 백 스택에 저장된 변경 사항들에 한에서 사용자가 '뒤로'버튼을 눌렀을 경우 '변경 사항 취소'가 되어 '되돌리기'작업을 진행할 수 있다.
Fragment 생명주기
- Fragment 생성을 위해서는 Fragment() 클래스를 상속받아야 한다.
- Fragment() 클래스에는 Activity와 유사하게 생명 주기를 나타내는 Callback Method가 존재한다.
- onAttach()
- Fragment가 Activity에 Attach될 때 호출
- Activity의 자원(context)을 사용하는 코드
- onCreate()
- Fragment생성 시 반드시 있어야 하는 Callback Method
- Fragment 생성 시 호출
- 해당 Fragment가 사용할 Resource를 초기화한다.
- UI 초기화는 아니다.
- onCreateView()
- Fragment생성 시 반드시 있어야 하는 Callback Method
- UI초기화
- Fragment에서 UI를 처음으로 그릴 때 호출한다
- Activity - onCreate()를 호출하고 실행된다.
- onActivityCreated( )
- Activity - onCreate( )가 종료되면 실행
- Activity의 View를 바꾸려면 여기서 바꿀 수 있다.
- onStart()
- Activity Method와 유사하다
- Fragment가 화면에 표시되기 시작할 때 호출한다.
- onResume()
- Activity Method와 유사하다
- Fragment 로딩이 끝나고 완전히 보일 때 호출한다
- onPause()
- Fragment생성 시 반드시 있어야 하는 Callback Method
- 화면 중지 시 호출한다
- onStop()
- Acitivy Method와 유사하다
- Fragment가 종료되는 과정에서 호출한다.
- onDestroyView()
- 다른 Fragment로 대체될 때 호출한다. 이때, Fragment의 View를 제거한다.
- onDestroy()
- Activity Method와 유사하다
- Fragment Resource를 해제한다.
- onDetach()
- Activity로부터 분리될 때 실행한다.
2. Fragment 사용해보기
1. Fragment 생성하기
액티비티 폴더 마우스 우클릭 → New → Fragment → Fragment(Blank)
Fragment를 생성하고 나면 Fragment.kt파일과 layout을 설정하는 xml파일이 생성된다.
2. 만든 Fragment를 Activity에 놓기
팔레트에서 FragmentContainerView를 화면에 끌어다 놓으면, 내가 만들어둔 Fragment목록이 뜬다. 사용할 Fragment를 선택해서 넣고 위치를 잡아주면 된다.
이와 같은 방법은 사용할 Fragment가 1개인 경우일때만 사용하고, 만일 Fragment간 화면 전환을 해야하는 경우, FrameLayout을 사용한다.
3. FrameLayout을 사용해 Frame간 화면 전환하기
package com.example.fragmenttest
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.fragmenttest.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
val binding by lazy {ActivityMainBinding.inflate(layoutInflater)}
val listFragment by lazy {ListFragment()}
val detailFragment by lazy {Detailragment()}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setFragment()
}
fun goDetail(){
val transaction = supportFragmentManager.beginTransaction()
transaction.add(R.id.frameLayout, detailFragment)
transaction.addToBackStack("detail")
//뒤로가기 기능을 구현하기 위해 삽입한 코드임.
//프래그먼트의 동작을 스택에 넣는다.
transaction.commit()
}
fun goBack(){
onBackPressed()
//스택에 담긴게 빠진다.
}
fun setFragment(){
//1.사용할 프래그먼트 생성
// val listFragment = ListFragment()
//2. 트랜잭션 생성
val transaction = supportFragmentManager.beginTransaction()
//3. 트랜잭션을 통해 프래그먼트 생성
transaction.add(R.id.frameLayout, listFragment)
transaction.commit()
}
}
위 코드를 통해 List Fragment에 있는 Next버튼을 누르면 Detail Fragment로 화면이 전환된다.
*ListFragment코드
package com.example.fragmenttest
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.fragmenttest.databinding.FragmentListBinding
class ListFragment : Fragment() {
lateinit var binding:FragmentListBinding
lateinit var mainActivity: MainActivity
override fun onAttach(context: Context) {
//context엔 나를 삽입한 Activity가 담겨있다
super.onAttach(context)
//이걸 해주는 이유는 mainActivity에 있는 goDetail함수를 쓰기 위해서
if(context is MainActivity) mainActivity = context
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.btnNext.setOnClickListener{
mainActivity.goDetail()
}
}
}
*DetailFragment 코드
package com.example.fragmenttest
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.fragmenttest.databinding.FragmentDetailragmentBinding
class Detailragment : Fragment() {
lateinit var binding: FragmentDetailragmentBinding
lateinit var mainActivity: MainActivity
override fun onAttach(context: Context) {
//context엔 나를 삽입한 Activity가 담겨있다
super.onAttach(context)
//이걸 해주는 이유는 mainActivity에 있는 goDetail함수를 쓰기 위해서
if(context is MainActivity) mainActivity = context
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentDetailragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.btnBack.setOnClickListener{
mainActivity.goBack()
}
}
}
- onAttach로 DetailFragment가 붙는 순간 mainActivity를 context에 담아둔다.
- onCreatedView가 되고 나면, fragmentDetailBinding으로 binding을 생성해서 담아두고, binding의 root인 view는 안드로이드한테 전달한다.
- onViewCreated가 실행되면 btnBack에 리스너를 단다.
3. Fragment 의 값 전달
1. activity→fragment에 값 전달해보기 (FragmentManager에 Bundle로 Data를 담아 전달)
*mainActivity (아래 코드를 사용해 mainActivity에서 listFragment로 값을 보낸다)
package com.example.fragmenttest
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.fragmenttest.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
val binding by lazy {ActivityMainBinding.inflate(layoutInflater)}
val listFragment by lazy {ListFragment()}
val detailFragment by lazy {Detailragment()}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setFragment()
}
fun goDetail(){
val transaction = supportFragmentManager.beginTransaction()
transaction.add(R.id.frameLayout, detailFragment)
transaction.addToBackStack("detail")
//뒤로가기 기능을 구현하기 위해 삽입한 코드임.
//프래그먼트의 동작을 스택에 넣는다.
transaction.commit()
}
fun goBack(){
onBackPressed()
//스택에 담긴게 빠진다.
}
fun setFragment(){
val bundle = Bundle()
bundle.putString("key 1", "List Fragment")
bundle.putInt("key 2", 20211031)
listFragment.arguments = bundle
val transaction = supportFragmentManager.beginTransaction()
transaction.add(R.id.frameLayout, listFragment)
transaction.commit()
}
}
- Activity에서 Fragment로 값을 보낼 경우, Bundle을 사용한다.
- Bundle에 보내고 싶은 데이터를 put자료형(키, 데이터) 형식으로 저장한다.
- 저장한 Bundle을 값을 보내고싶은 fragment의 argument에 넣어준다.
*listFragment코드 (아래 코드를 통해 mainActivity로부터 받은 값을 textView에 띄운다.
package com.example.fragmenttest
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.fragmenttest.databinding.FragmentListBinding
class ListFragment : Fragment() {
lateinit var binding:FragmentListBinding
lateinit var mainActivity: MainActivity
override fun onAttach(context: Context) {
//context엔 나를 삽입한 Activity가 담겨있다
super.onAttach(context)
//이걸 해주는 이유는 mainActivity에 있는 goDetail함수를 쓰기 위해서
if(context is MainActivity) mainActivity = context
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(binding) {
arguments?.apply{
textTitle.text = getString("key 1")
textValue.text = "${getInt("key 2")}"
}
btnNext.setOnClickListener {
mainActivity.goDetail()
}
}
}
}
fragment에서 activity로부터 온 데이터를 받을 때는, fragment의 argument를 사용하면 된다. 값을 꺼낼 때는 키값을 사용해 꺼내면 된다.
2. fragment→fragment 값 전달해보기 (Fragment Result API를 사용하여 Data 전달)
Fragment간 데이터 전달의 경우, FragmentManager를 사용한다.
이와 같은 FragmentManager를 활용하기 위핸, build.gradle(app)에 코틀린 설정을 추가해주어야 한다.
*senderFragment 코드 (값을 보내는 프래그먼트)
package com.example.fragmenttest
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
import com.example.fragmenttest.databinding.FragmentSenderBinding
class SenderFragment : Fragment() {
lateinit var binding: FragmentSenderBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentSenderBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(binding){
btnYes.setOnClickListener {
val bundle = bundleOf("senderKey" : "Yes")
setFragmentResult("request", bundle)
}
btnNo.setOnClickListener {
val bundle = bundleOf("senderKey" to "No")
setFragmentResult("request", bundle)
}
}
}
}
- activity 에서 fragment로 값을 보낼 때와 마찬가지로 bundle을 사용한다. 이때 bundleOf 함수를 사용해 (키, value)형태로 bundle에 담는다.
- "requestKey"는 사용하는 Fragment에서 어떤 listener에게 데이터를 전달할지 결정하기 위한 식별자로 사용된다.
- 다른 Fragment에서 같은 "requestKey"로 setFragmentResultListener를 적용할 경우, 마지막에 적용한 listener가 호출된다.
- Fragment Result API를 사용하여 Data를 전달한다.
*receiverFragment 코드 (값을 받는 프래그먼트)
package com.example.fragmenttest
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResultListener
import com.example.fragmenttest.databinding.FragmentReceiverBinding
class ReceiverFragment : Fragment() {
lateinit var binding:FragmentReceiverBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentReceiverBinding.inflate(inflater, container, false)
return binding.root
//return inflater.inflate(R.layout.fragment_receiver, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setFragmentResultListener("request"){ key, bundle ->
bundle.getString("senderKey").let{value ->
binding.textView.text = value
}
}
}
}
'GDSC HUFS 3기 > Android with Kotlin Team 1' 카테고리의 다른 글
[1팀] 14-9. 화면 구성하기: 탭메뉴 뷰페이저와 프래그먼트 (0) | 2021.11.16 |
---|---|
[1팀] 14-7~8. 화면 구성하기: Custom View & Widget (0) | 2021.11.16 |
[1팀] 14-4. 화면 구성하기: 리사이클러뷰 (0) | 2021.11.16 |
[1팀] 14-2. 화면 구성하기 : Spinner (1) | 2021.11.16 |
[1팀] 14-1. 화면 구성하기: 액티비티 값 주고받기 뷰바인딩 (0) | 2021.11.16 |