반응형
Android Fragment 동적 UI 구축과 화면 크기별 최적화 방법
다양한 화면 크기를 지원하도록 애플리케이션을 설계할 때 Fragment를 유연하게 구축하는 것은 매우 중요합니다. Fragment를 다양한 레이아웃 구성에서 재사용하여 사용 가능한 화면 공간을 기준으로 사용자 환경을 최적화할 수 있습니다. 예를 들어 핸드셋 기기에서는 Fragment를 한 번에 하나씩만 표시하고, 태블릿에서는 Fragment를 나란히 배치하여 더 많은 정보를 표시할 수 있습니다. 이 글에서는 FragmentManager와 FragmentTransaction을 활용한 동적 Fragment 관리 방법을 실전 코드와 함께 상세히 알아보겠습니다.
목차
1. 유연한 Fragment UI의 필요성
2. FragmentManager와 FragmentTransaction
3. 런타임에 Fragment 추가하기
4. Fragment 교체와 백스택 관리
5. 화면 크기별 레이아웃 최적화
#1. 유연한 Fragment UI의 필요성
다양한 화면 크기와 해상도를 가진 안드로이드 기기에서 최적의 사용자 경험을 제공하기 위해서는 유연한 Fragment 구조가 필수적입니다.
1) 화면 크기별 UI 전략
(1) 핸드셋(스마트폰) UI
핸드셋 기기인 경우 단일 창 사용자 인터페이스에 Fragment를 한 번에 하나씩만 표시하는 것이 적합합니다.
① 화면이 작아 한 번에 하나의 Fragment만 표시
② Fragment 간 전환은 트랜잭션으로 처리
③ 백 버튼으로 이전 Fragment로 돌아갈 수 있도록 구현
(2) 태블릿 UI
화면 너비가 큰 태블릿에서는 Fragment를 나란히 설정하여 사용자에게 더 많은 정보를 표시할 수 있습니다.
① 마스터-디테일 패턴으로 두 개의 Fragment 동시 표시
② 왼쪽에 목록, 오른쪽에 상세 정보 배치
③ 화면 공간을 효율적으로 활용
. . . . .
2) 동적 Fragment 관리의 장점
| 장점 | 설명 | 활용 예 |
|---|---|---|
| 재사용성 | 동일한 Fragment를 여러 Activity에서 사용 | 상세 화면 Fragment |
| 유연성 | 런타임에 UI를 동적으로 변경 | 화면 크기에 따른 레이아웃 |
| 모듈성 | 독립적인 UI 구성 요소로 관리 | 탭, 네비게이션 |
| 생명주기 관리 | Activity 생명주기와 독립적 관리 | 화면 회전 시 상태 유지 |
#2. FragmentManager와 FragmentTransaction
FragmentManager 클래스는 동적 경험(dynamic experience)을 만들기 위해 런타임 시 Activity에 Fragment를 추가, 제거 및 대체할 수 있는 메서드를 제공합니다.
1) FragmentManager란?
FragmentManager는 Activity의 Fragment들을 관리하는 클래스입니다.
(1) FragmentManager의 주요 기능
① Fragment 트랜잭션 시작 - beginTransaction()으로 트랜잭션 생성
② Fragment 찾기 - findFragmentById(), findFragmentByTag()
③ 백스택 관리 - popBackStack() 등
④ Fragment 개수 확인 - getBackStackEntryCount()
(2) FragmentManager 가져오기
// Kotlin - Activity에서 FragmentManager 가져오기
val fragmentManager = supportFragmentManager
// Fragment 내에서 가져오기
val fragmentManager = parentFragmentManager
val fragmentManager = supportFragmentManager
// Fragment 내에서 가져오기
val fragmentManager = parentFragmentManager
. . . . .
2) FragmentTransaction이란?
FragmentTransaction은 Fragment 추가, 삭제, 교체 등의 작업을 수행하기 위한 API를 제공합니다.
(1) 주요 메서드
① add() - Fragment를 컨테이너에 추가
② replace() - 기존 Fragment를 새 Fragment로 교체
③ remove() - Fragment를 제거
④ addToBackStack() - 트랜잭션을 백스택에 추가
⑤ commit() - 트랜잭션 실행 (필수)
(2) 기본 사용 패턴
// Kotlin - FragmentTransaction 기본 패턴
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, MyFragment())
.addToBackStack(null)
.commit()
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, MyFragment())
.addToBackStack(null)
.commit()
#3. 런타임에 Fragment 추가하기
레이아웃 파일의 <fragment> 요소를 사용하는 대신 Activity 런타임 중에 Fragment를 추가할 수 있습니다. Activity 사용 기간 중 Fragment를 변경하려는 경우에는 이 과정이 필요합니다.
1) Fragment 컨테이너 준비
Fragment 작업 시, 특히 런타임에 Fragment를 추가할 때 유념해야 할 중요한 규칙은 Fragment를 삽입할 수 있는 컨테이너 View를 Activity 레이아웃에 포함해야 하는 것입니다.
(1) 기본 컨테이너 레이아웃
한 Fragment를 다른 Fragment로 교체할 수 있도록 Activity 레이아웃에는 Fragment 컨테이너 역할을 하는 빈 FrameLayout이 포함되어 있습니다.
<!-- activity_main.xml -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
파일 이름은 동일하지만, 레이아웃 디렉터리에 large 한정자가 없기 때문에, 이 레이아웃은 기기 화면이 대형보다 작아 두 Fragment를 동시에 표시할 수 없는 경우에 사용됩니다.
. . . . .
2) Activity에서 Fragment 추가하기
Activity에서 Fragment를 삭제하거나 교체할 수 있는 경우 Activity의 onCreate() 메서드 중에 Activity의 초기 Fragment를 추가해야 합니다.
// Kotlin - MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 처음 실행 시에만 Fragment 추가 (화면 회전 시 중복 방지)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, ListFragment())
.commit()
}
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 처음 실행 시에만 Fragment 추가 (화면 회전 시 중복 방지)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, ListFragment())
.commit()
}
}
}
savedInstanceState == null 체크는 매우 중요합니다. 화면 회전 시 Fragment가 자동으로 복원되므로 중복 추가를 방지해야 합니다.
. . . . .
3) Fragment 클래스 작성
// Kotlin - ListFragment.kt
class ListFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 리스트 항목 클릭 시 상세 Fragment로 전환
val button = view.findViewById<Button>(R.id.button_detail)
button.setOnClickListener {
showDetailFragment()
}
}
private fun showDetailFragment() {
parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container, DetailFragment())
.addToBackStack(null)
.commit()
}
}
class ListFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 리스트 항목 클릭 시 상세 Fragment로 전환
val button = view.findViewById<Button>(R.id.button_detail)
button.setOnClickListener {
showDetailFragment()
}
}
private fun showDetailFragment() {
parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container, DetailFragment())
.addToBackStack(null)
.commit()
}
}
#4. Fragment 교체와 백스택 관리
1) Fragment 교체하기
replace() 메서드는 기존 Fragment를 제거하고 새로운 Fragment를 추가합니다.
// Kotlin - Fragment 교체
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, DetailFragment())
.addToBackStack("detail") // 백스택에 추가
.commit()
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, DetailFragment())
.addToBackStack("detail") // 백스택에 추가
.commit()
. . . . .
2) 백스택 관리
addToBackStack()을 호출하면 트랜잭션이 백스택에 저장되어 백 버튼으로 이전 상태로 돌아갈 수 있습니다.
(1) 백스택에 추가
// 백스택에 이름을 지정하여 추가
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, MyFragment())
.addToBackStack("my_fragment")
.commit()
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, MyFragment())
.addToBackStack("my_fragment")
.commit()
(2) 백스택에서 제거
// 마지막 트랜잭션 되돌리기
supportFragmentManager.popBackStack()
// 특정 이름까지 되돌리기
supportFragmentManager.popBackStack(
"my_fragment",
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
supportFragmentManager.popBackStack()
// 특정 이름까지 되돌리기
supportFragmentManager.popBackStack(
"my_fragment",
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
. . . . .
3) commit() vs commitAllowingStateLoss()
| 메서드 | 특징 | 사용 시점 |
|---|---|---|
| commit() | 상태 저장 후 호출 시 예외 발생 | 일반적인 경우 |
| commitAllowingStateLoss() | 상태 손실 허용 | 백그라운드 작업 후 |
| commitNow() | 즉시 실행 (동기) | 바로 결과 필요 시 |
#5. 화면 크기별 레이아웃 최적화
1) 스마트폰용 레이아웃
<!-- res/layout/activity_main.xml (스마트폰) -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
. . . . .
2) 태블릿용 레이아웃
<!-- res/layout-sw600dp/activity_main.xml (태블릿) -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<!-- 왼쪽: 리스트 Fragment -->
<FrameLayout
android:id="@+id/list_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<!-- 오른쪽: 상세 Fragment -->
<FrameLayout
android:id="@+id/detail_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />
</LinearLayout>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<!-- 왼쪽: 리스트 Fragment -->
<FrameLayout
android:id="@+id/list_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<!-- 오른쪽: 상세 Fragment -->
<FrameLayout
android:id="@+id/detail_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />
</LinearLayout>
. . . . .
3) 코드에서 화면 크기 판단
// Kotlin - 태블릿 여부 확인
class MainActivity : AppCompatActivity() {
private val isTwoPane: Boolean
get() = findViewById<View>(R.id.detail_container) != null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.list_container ?: R.id.fragment_container, ListFragment())
.commit()
// 태블릿이면 상세 Fragment도 함께 표시
if (isTwoPane) {
supportFragmentManager.beginTransaction()
.add(R.id.detail_container, DetailFragment())
.commit()
}
}
}
}
class MainActivity : AppCompatActivity() {
private val isTwoPane: Boolean
get() = findViewById<View>(R.id.detail_container) != null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.list_container ?: R.id.fragment_container, ListFragment())
.commit()
// 태블릿이면 상세 Fragment도 함께 표시
if (isTwoPane) {
supportFragmentManager.beginTransaction()
.add(R.id.detail_container, DetailFragment())
.commit()
}
}
}
}
마무리
Fragment를 활용한 유연한 UI 구축은 다양한 화면 크기를 지원하는 현대적인 안드로이드 앱의 필수 요소입니다. FragmentManager와 FragmentTransaction을 제대로 이해하고 활용하면 사용자에게 최적화된 경험을 제공할 수 있습니다.
핵심 요약
① FragmentManager로 Fragment 생명주기를 관리합니다
② FragmentTransaction으로 Fragment를 추가, 교체, 제거합니다
③ addToBackStack()으로 백 버튼 동작을 구현합니다
④ savedInstanceState 체크로 중복 추가를 방지합니다
⑤ 화면 크기별 레이아웃으로 스마트폰과 태블릿을 최적화합니다
Fragment 컨테이너로 FrameLayout을 사용하고, onCreate()에서 초기 Fragment를 추가하며, replace()와 addToBackStack()을 활용하여 Fragment 전환을 구현하는 것이 기본 패턴입니다. 특히 savedInstanceState null 체크와 백스택 관리는 안정적인 Fragment 동작을 위해 필수적입니다.
긴 글 읽어주셔서 감사합니다.
끝.
끝.
반응형
'Development > Android' 카테고리의 다른 글
| [Android] Android 터치 이벤트 처리 방법: 발생 순서와 구현 완벽 분석 (0) | 2019.09.10 |
|---|---|
| [Android] Android Intent Filter 개념과 실전 구현 방법 (0) | 2019.09.10 |
| [Android] Android Fragment 테스트 핵심 방법 (0) | 2019.09.06 |
| [Android] Android Fragment의 생성 실전 구현 방법 (0) | 2019.09.06 |
| [Android] Android Fragment 핵심 개념과 활용법 (0) | 2019.09.06 |