본문 바로가기
깡샘 코틀린

11-3 프래그먼트 - 액티비티처럼 동작하는 뷰

by 농농씨 2023. 6. 29.

androidx에서 제공하는 라이브러리 가운데 프래그먼트뷰 페이저2는 중요도와 사용 빈도가 높다. 프래그먼트는 플랫폼 API에서도 android.app.Fragment로 제공하지만 대부분 androidx.fragment 라이브러리를 이용해 구현한다. 따라서 이 책에는 androidx 라이브러리로 프래그먼트를 사용하는 방법을 설명한다. 참고로 11-5 절에서 설명할 뷰 페이저2는 플랫폼 API에서 제공하지 않는 라이브러리이다.

 

 

프래그먼트 소개

프래그먼트는 텍스트 뷰나 버튼처럼 액티비티 화면을 구성하는 뷰인데, 그 자체만으로는 화면에 아무것도 출력되지 않는다. 프래그먼트가 다른 뷰와 다른 점은 액티비티처럼 동작한다는 것이다. 즉, 액티비티에 작성할 수 있는 모든 코드는 프래그먼트에도 사용할 수 있다.

 

프래그먼트는 태블릿처럼 화면이 넓은 기기에서 동작하는 앱을 개발할 수 있도록 제공되었다. 한 화면은 하나의 액티비티 클래스에서 작성해야 하는데, 화면이 크면 액티비티 클래스에 너무 많은 코드를 작성해야 하는 문제가 있다.

왼쪽에 목록 있고 오른쪽 화면에 콘텐츠 나온다 할 때 이걸 액티비티 클래스에 모두 구현하려면 부담이어서, 왼쪽 화면과 오른쪽 화면을 각각 클래스로 분리해서 작성하고 액티비티는 두 클래스를 조합 하겠다는 의도이다.

이때 두 클래스가 액티비티 화면에 출력되어야 하므로 각각의 클래스를 뷰 클래스로 만들어야 한다. 문제는 뷰 클래스가 액티비티가 아니므로 액티비티에 작성된 모든 내용을 뷰 클래스에 담을 수가 없다. 이때 프래그먼트가 필요하다!!

프래그먼트는 액티비티처럼 동작하는 뷰 클래스이므로 액티비티에 구현되는 모든 내용을 담을 수 있다. 왼쪽 오른쪽을 각각의 프래그먼트 클래스로 작성하면 액티비티 클래스 하나가 너무 길어지지 않는다. good~

 

🤔그렇다면 프래그먼트는 태블릿처럼 넓은 화면을 목적으로 하는 앱에서만 사용할 수 있느냐? no no~

예를 들어 항목을 선택하고 내용을 보여주는 앱이 있다고 치자. 액티비티를 이용하면 각 화면을 각각의 액티비티로 만들어서 화면전환으로 보여줘야 한다. 그런데 프래그먼트를 이용하면 액티비티는 하나만! 만든 다음 각 화면을 프래그먼트로 구현할 수도 있다~

 

프래그먼트 대표 예시는 탭과 뷰 페이저 화면이 있다. 둘 다 여러 화면을 제공하는 것이 목적인데, 이를 모두 하나의 클래스에 구현하기는 부담스러우므로 탭과 뷰 페이저가 제공하는 한 화면을 하나의 프래그먼트로 개발한다.

예를 들어 탭버튼이 4개 있는 화면을 만들려면 각 버튼을 클릭할 때 나와야 하는 화면도 4개 필요한데, 이를 각각 프래그먼트로 개발하고 액티비티에서는 탭 버튼만 제공하여 사용자가 각 탭 버튼을 클릭하면 프래그먼트만 교체해서 출력하는 방식으로 개발한다.

 

 

프래그먼트 구현

더보기

(개정판 삭제 내용)

프래그먼트는 androidx.fragment 라이브러리에서 제공한다.

// 프래그먼트 선언
dependencies {
	(...생략...)
    implementation 'androidx.fragment:fragment-ktx:1.3.6'
}

 

❓프래그먼트의 라이브러리는 androidx.fragment:fragment 아닌가요? androidx..fragment:fragment-ktx는 어떤 라이브러리죠?

❗️라이브러리 이름에 '-ktx'가 붙으면 코틀린으로 작성했다는 뜻이다. 없으면 자바로 작성한건데, 자바에서 작성한건 코틀린에서도 이용할 수 있다. 하지만 코틀린으로 작성한 걸 이용하면 더 편리하다.

 

프래그먼트는 뷰이지만 그 자체로는 화면에 아무것도 출력되지 않는다. 따라서 먼저 프래그먼트 화면을 구성하는 레이아웃 XML 파일을 작성해야 한다. 액티비티 만들때랑 똑같은 부분이다.

 

프래그먼트는 Fragment를 상속받아 작성하는 클래스이다. 프래그먼트 클래스에는 onCreateView() 함수를 최소한으로 작성해야 한다.(왜?) 이 함수가 자동 호출되며 반환한 View 객체가 화면에 출력된다.

// 프래그먼트 구현
(...생략...)
import androidx.fragment.app.Fragment
class OneFragment : Fragment() { // Fragment를 상속받았음
	lateinit var binding: FragmentOneBinding // 늦은 초기화: 미리 변수를 선언해놓고 나중에 초기값을 할당함
    override fun onCreateView( // 최소로 써야 하는 함수
    	inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
    	binding = FragmentOnebinding.inflate(inflater, container, false)
        return binding.root
    }
}

 

액티비티의 레이아웃 XML에 등록하여 프래그먼트 출력

프래그먼트도 이므로 액티비티의 화면을 구성하는 레이아웃 XML에 등록하여 액티비티 화면에 나오게 할 수 있다. 다음 코드는 액티비티의 화면을 구성하는 XML 내용이다. <fragment> 태그로 액티비티 화면에 프래그먼트를 출력한다. <fragment> 태그의 name 속성에 프래그먼트 클래스를 지정하면 된다.

// 프래그먼트 출력
<fragment
	android:name="com.example.test11.OneFragment"
    android:id="@+id/fragmentView"
    android:layout_width="match_parent"
    androdi:layout_height="match_parent">

 

액티비티 코드에서 프래그먼트 출력

액티비티의 레이아웃 XML 에서 프래그먼트를 출력할 수도 있지만 코드에서 직접 프래그먼트 객체를 생성하여 화면에 출력해야 할 수도 있다. 이럴 때는 우선 액티비티의 레이아웃 XML 파일에 프래그먼트가 출력될 뷰를 하나 준비한다. 

// 프래그먼트를 출력할 뷰 준비
<LinearLayout
	android:id="@+id/fragment_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
</LinearLayout>

이 LinearLayout에 코드에서 동적으로 생성한 프래그먼트를 출력할 수 있다. 물론 LinearLayout이 아닌 다른 레이아웃 클래스를 이용해도 된다.

 

그리고 코드에서 프래그먼트를 동적으로 제어(추가, 제거 등)하려면 FragmentManager로 만든 FragmentTransaction 클래스의 함수를 이용한다.

// 프래그먼트 동적 제어
val fragmentmanager: FragmentManager = supportFragmentManager
val transaction: FragmentTransaction = fragmentManager.beginTransaction()
val fragment = OneFragment()
transaction.add(R.id.fragment_content, fragment) // 새로운 프래그먼트를 화면에 추가
transaction.commit() // 화면에 적용

FragmentTransaction 객체의 add() 함수를 이용해 프래그먼트 객체를 화면에 출력하는 코드이다. add() 함수의 첫 번째 매개변수가 프래그먼트가 출력될 뷰의 id값이다. 그리고 commit() 함수를 호출하는 순간 화면에 적용된다. 이 외에도 replace(), remove() 함수들로 코드에서 동적으로 프래그먼트를 제어할 수 있다.

  • add(int containerViewId, Fragment fragment): 새로운 프래그먼트를 화면에 추가한다.
  • replace(int containeViewId, Fragment fragment): 추가된 프래그먼트를 대체한다.
  • remove(Fragment fragment): 추가된 프래그먼트를 제거한다.
  • commit(): 화면에 적용한다.

 

프래그먼트 생명주기

프래그먼트는 액티비티처럼 동작하는 뷰이다. 그래서 액티비티에 작성하는 코드를 프래그먼트에도 작성할 수 있다. 따라서 프래그먼트도 액티비티와 생명주기(life cycle)가 같다.

나중에 배울텐데 프래그먼트도 액티비티의 생명주기 함수들을 그대로 가지고 있으며 호출되는 시점도 액티비티와 같다. 즉, 액티비티의 onStart() 함수가 호출되는 순간 해당 액티비티가 출력하는 프래그먼트의 onStart() 함수도 호출된다.

프래그먼트의 생명 주기는 크게 5단계로 나뉜다. 초기화(initialized), 생성(created), 시작(started), 재개(resumed), 소멸(destroyed) 단계로 구분되며, 각 단계가 진행되면서 자동으로 호출되는 함수는 다음과 같다. 즉, 초기화 단계가 진행되면서 onAttach와 onCreate 함수가 호출된다.

프래그먼트의 생명 주기    
재개 재개: 프래그먼트에 포커스 재개~시작:
프래그먼트 표시
onResume.   ↑ ↓ onPause  
시작  
onStart
onViewCreated   ↑
onCreateView
↓ onStop
onDestroyView
   
생성    
onCreate
onAttach    ↑
↓ onDestroy
onDetach
   
초기화 소멸    

<초기화 단계>: 프래그먼트의 화면을 구성할 뷰가 준비되지 않은 상태. 다라서 onAttach 와 onCreate 함수에서는 뷰 객체를 이용하지 못하므로 뷰와 관련이 없는 프래그먼트의 초기화 로직을 수행한다.

<생성 단계>: 프래그먼트의 화면을 구성할 뷰를 준비한다. 특히 onCreateView 함수의 매개변수로 LayoutInflater 객체가 전달되므로 대부분은 onCreateView 함수에서 프래그먼트의 화면을 구성할 뷰 객체를 준비한다. 

<시작 단계>: 프래그먼트의 화면이 사용자에게 보인다.

<재개 단계>: 포커스를 가지고 사용자의 이벤트를 처리할 수 있다.

 

어떤 프래그먼트가 처음 화면에 보인다면 onAttach → onCreate → onCreateView → onViewCreated → onStart → onResume 함수가 순서대로 호출된다. 이후 다른 프래그먼트로 교체될 때는 백 스택을 사용하는지에 따라 생명주기가 다르게 동작한다. 백 스택(back stack)은 프래그먼트가 화면에 보이지 않는 순간 제거하지 않고 저장했다가 다시 이용할 수 있는 기능이다. 백 스택을 이용하면 사용자가 기기의 뒤로가기 버튼을 누를 때 이전 프래그먼트로 화면을 전환할 수 있다. 그러나 백 스택을 사용하지 않으면 프래그먼트가 교체될 때 기존의 프래그먼트는 onDestroy까지 호출되어 제거된다.

백 스택을 사용하면 프래그먼트가 제거되지 않고 onDestroyView 함수까지만 호출된다. 즉, onPause → onStop → onDestroyView 함수가 자동으로 호출된다. 이처럼 onDestroyView까지 호출된 프래그먼트가 다시 준비되어 화면에 출력된다면 onCreateView → onViewCreated → onStart → onResume 함수가 차례로 호출된다.

 

백 스택을 사용하려면 FragmentTransaction의 addToBackStack() 함수를 이용해야 한다.

// 백 스택 사용 설정
transaction.addToBackStack(null)