Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

프래그먼트   Part of Android Jetpack.

FragmentFragmentActivity 내의 어떤 동작 또는 사용자 인터페이스의 일부를 나타냅니다. 여러 개의 프래그먼트를 하나의 액티비티에 결합하여 창이 여러 개인 UI를 빌드할 수 있으며, 하나의 프래그먼트를 여러 액티비티에서 재사용할 수 있습니다. 프래그먼트는 액티비티의 모듈식 섹션이라고 생각하면 됩니다. 이는 자체적인 수명 주기를 가지고, 자체 입력 이벤트를 수신하고, 액티비티 실행 중에 추가 및 삭제가 가능합니다(다른 액티비티에 재사용할 수 있는 "하위 액티비티"와 같은 개념).

프래그먼트는 항상 액티비티 내에서 호스팅되어야 하며 해당 프래그먼트의 수명 주기는 호스트 액티비티의 수명 주기에 직접적으로 영향을 받습니다. 예를 들어 액티비티가 일시정지되는 경우, 그 안의 모든 프래그먼트도 일시정지되며 액티비티가 소멸되면 모든 프래그먼트도 마찬가지로 소멸됩니다. 그러나 액티비티가 실행 중인 동안(재개됨 수명 주기 상태에 있을 경우)에는 각 프래그먼트를 추가 또는 제거하는 등 개별적으로 조작할 수 있습니다. 그와 같은 프래그먼트 트랜잭션을 수행할 때는 이를 액티비티가 관리하는 백 스택에도 추가할 수 있습니다. 각 백 스택 항목이 발생한 프래그먼트 트랜잭션의 기록이 됩니다. 이 백 스택을 사용하면 사용자가 프래그먼트 트랜잭션을 거꾸로 돌릴 수 있습니다(뒤로 이동). 이때 Back 버튼을 누르면 됩니다.

프래그먼트를 액티비티 레이아웃에 추가하면, 해당 프래그먼트는 액티비티의 뷰 계층 내에서 ViewGroup에 들어가고 자체적인 뷰 레이아웃을 정의합니다. 액티비티의 레이아웃 파일에서 <fragment> 요소로 프래그먼트를 선언하거나 기존 ViewGroup에 추가하는 방법으로 애플리케이션 코드에서 프래그먼트를 선언하면 액티비티 레이아웃에 프래그먼트를 삽입할 수 있습니다.

이 문서에서는 프래그먼트를 사용하도록 애플리케이션을 빌드하는 방법을 설명합니다. 여기에는 프래그먼트를 액티비티의 백 스택에 추가했을 때 프래그먼트가 자신의 상태를 유지하는 방법, 액티비티 및 액티비티 내의 다른 프래그먼트와 이벤트를 공유하는 방법, 액티비티의 앱 바에 참가하는 방법 등 여러 가지가 포함됩니다.

모범 사례 가이드를 포함한 수명 주기 처리에 대한 자세한 내용은 다음 참고 자료를 참조하세요.

디자인 철학

Android가 프래그먼트를 처음 도입한 것은 Android 3.0(API 레벨 11)부터입니다. 기본적으로 태블릿과 같은 큰 화면에서 보다 역동적이고 유연한 UI 디자인을 지원하는 것이 목적이었습니다. 태블릿의 화면은 핸드셋 화면보다 훨씬 크기 때문에 UI 구성 요소를 조합하고 상호 교환할 공간이 더 많습니다. 프래그먼트는 개발자가 뷰 계층에 복잡한 변경 내용을 관리하지 않아도 이러한 디자인을 사용할 수 있도록 해줍니다. 액티비티의 레이아웃을 여러 프래그먼트로 나누면 런타임에서 액티비티의 외관을 수정할 수도 있고, 그러한 변경 내용을 해당 액티비티가 관리하는 백 스택에 보존할 수도 있습니다. 이제 프래그먼트는 프래그먼트 지원 라이브러리를 통해 폭넓게 제공됩니다.

예를 들어 뉴스 애플리케이션이라면 하나의 프래그먼트를 사용하여 왼쪽에 기사 목록을 표시하고, 또 다른 프래그먼트로 오른쪽에 기사 내용을 표시할 수 있습니다. 두 프래그먼트 모두 한 가지 액티비티에서 양쪽으로 나란히 나타나며, 각 프래그먼트에 나름의 수명 주기 콜백 메서드가 있고 각자 사용자 입력 이벤트를 따로 처리하게 됩니다. 따라서 사용자는 기사를 선택하는 데 하나의 액티비티를 쓰고 기사를 읽는 데 다른 액티비티를 선택하는 대신, 같은 액티비티 안에서 기사를 선택하고 읽는 과정을 모두 끝낼 수 있습니다. 이는 그림 1의 태블릿 레이아웃과 같습니다.

각 프래그먼트는 모듈식이고 재사용 가능한 액티비티 구성 요소로 디자인해야 합니다. 다시 말해, 각 프래그먼트가 자체적인 수명 주기 콜백으로 레이아웃과 동작을 정의하기 때문에 한 프래그먼트를 여러 액티비티에 포함할 수 있습니다. 그러므로 다시 사용할 것을 염두에 두고 디자인하고 하나의 프래그먼트를 다른 프래그먼트에서 직접 조작하는 것은 삼가야 합니다. 이것이 특히 중요한 이유는 모듈식 프래그먼트를 사용하면 프래그먼트 조합을 여러 가지 화면 크기에 맞춰 변경할 수 있기 때문입니다. 태블릿과 핸드셋을 모두 지원하는 애플리케이션을 디자인하는 경우, 사용 가능한 화면 공간을 토대로 사용자 환경을 최적화하도록 프래그먼트를 여러 레이아웃 구성에 재사용할 수 있습니다. 예를 들어 핸드셋의 경우 같은 액티비티 안에 프래그먼트가 둘 이상 들어가지 않을 때 프래그먼트를 분리해서 단일 창 UI를 제공해야 할 수도 있습니다.

그림 1. 프래그먼트가 정의한 두 가지 UI 모듈이 태블릿 디자인에서는 하나의 액티비티로 조합될 수 있는 반면, 핸드셋 디자인에서는 분리될 수 있다는 것을 나타낸 예시.

뉴스 애플리케이션 예시를 계속 사용하겠습니다. 이 애플리케이션을 태블릿 크기의 기기에서 실행하는 경우, 애플리케이션 내의 액티비티 A 안에 두 개의 프래그먼트를 포함할 수 있습니다. 그러나 핸드셋 크기의 화면은 두 프래그먼트를 모두 쓸 만큼 공간이 충분치 않습니다. 따라서 액티비티 A에는 기사 목록에 해당되는 프래그먼트만 포함되고, 사용자가 기사를 하나 선택하면 액티비티 B가 시작됩니다. 여기에 기사를 읽을 두 번째 프래그먼트가 포함되어 있습니다. 따라서 이 애플리케이션은 서로 다른 조합으로 프래그먼트를 재사용함으로써 태블릿과 핸드셋을 둘 다 지원합니다(그림 1 참조).

여러 가지 화면 구성에 맞게 여러 가지 프래그먼트 조합으로 애플리케이션을 디자인하는 법에 대한 자세한 내용은 화면 호환성 개요에 대한 가이드를 참조하세요.

프래그먼트 생성

그림 2. 프래그먼트의 수명 주기(소속 액티비티가 실행 중일 때).

프래그먼트를 생성하려면 Fragment의 하위 클래스(또는 이것의 기존 하위 클래스)를 생성해야 합니다. Fragment 클래스에는 Activity와 아주 유사해 보이는 코드가 있습니다. 여기에는 액티비티와 비슷한 콜백 메서드가 들어 있습니다. 예를 들어 onCreate(), onStart(), onPause()onStop() 등입니다. 사실, 기존 Android 애플리케이션을 변환하여 프래그먼트를 사용하게 하려면 단순히 액티비티의 콜백 메서드에서 프래그먼트에 해당되는 콜백 메서드로 코드를 옮기기만 하면 될 수도 있습니다.

보통은 최소한 다음과 같은 수명 주기 메서드를 구현해야 합니다.

onCreate()
프래그먼트를 생성할 때 시스템에서 이것을 호출합니다. 구현 내에서 프래그먼트의 기본 구성 요소 중 프래그먼트가 일시정지되거나 중단되었다가 재개되었을 때 유지하고자 하는 것을 초기화해야 합니다.
onCreateView()
시스템은 프래그먼트가 자신의 사용자 인터페이스를 처음으로 그릴 시간이 되면 이것을 호출합니다. 프래그먼트에 맞는 UI를 그리려면 메서드에서 View를 반환해야 합니다. 이 메서드는 프래그먼트 레이아웃의 루트입니다. 프래그먼트가 UI를 제공하지 않는 경우 null을 반환하면 됩니다.
onPause()
시스템이 이 메서드를 호출하는 것은 사용자가 프래그먼트를 떠난다는 것을 나타내는 첫 번째 신호입니다(다만 항상 프래그먼트가 소멸 중이라는 것을 의미하지는 않습니다). 일반적으로 여기에서 현재 사용자 세션을 넘어서 지속되어야 하는 변경 사항을 커밋합니다(사용자가 돌아오지 않을 수 있기 때문입니다).

대부분의 애플리케이션은 각각의 프래그먼트에 이와 같은 메서드를 최소한 세 개씩 구현해야 하지만, 프래그먼트 수명 주기의 여러 단계를 처리하는 데 사용해야 하는 다른 콜백 메서드도 많이 있습니다. 모든 수명 주기 콜백 메서드는 나중에 프래그먼트 수명 주기 처리 섹션에서 더욱 상세히 논의할 것입니다.

다만 종속적인 구성 요소의 코드 구현 수명 주기는 프래그먼트 콜백 구현에 직접 넣지 않고 구성 요소 자체에 두어야 합니다. 종속적인 구성 요소 수명 주기를 인식하게 하는 방법은 수명 주기 인식 구성 요소로 수명 주기 처리를 참조하세요.

이외에도 기본적인 Fragment 클래스 대신 확장하고자 하는 하위 클래스도 몇 개 있을 수 있습니다.

DialogFragment
부동 대화상자를 표시합니다. 이 클래스를 사용하여 대화상자를 생성하면 Activity 클래스의 대화상자 도우미 메서드를 사용하는 것의 좋은 대체 수단이 됩니다. 이렇게 하면 프래그먼트 대화상자를 액티비티가 관리하는 프래그먼트의 백 스택에 통합시킬 수 있어, 사용자가 닫힌 프래그먼트로 돌아갈 수 있기 때문입니다.
ListFragment
어댑터가 관리하는 항목의 목록(예: SimpleCursorAdapter)을 표시하며 ListActivity와 비슷합니다. 이 클래스는 목록 뷰를 관리하는 데 쓰는 몇 가지 메서드를 제공합니다. 예를 들어 onListItemClick() 콜백을 제공하여 클릭 이벤트를 처리합니다. (목록을 표시할 때는 ListView가 아니라 RecyclerView를 사용하는 방법이 선호됩니다.) 이 경우에는 레이아웃에 RecyclerView를 포함한 프래그먼트를 만들어야 합니다. 자세한 방법은 RecyclerView로 목록 만들기를 참조하세요.)
PreferenceFragmentCompat
Preference 객체의 계층을 목록으로 표시합니다. 이는 애플리케이션에서 설정 화면을 만드는 데 사용됩니다.

사용자 인터페이스 추가

프래그먼트는 일반적으로 액티비티의 사용자 인터페이스의 일부로 사용되며 자체 레이아웃으로 액티비티에 기여합니다.

프래그먼트에 대해 레이아웃을 제공하려면 반드시 onCreateView() 콜백 메서드를 구현해야 합니다. 프래그먼트가 자신의 레이아웃을 그릴 때가 되면 Android 시스템이 이를 호출합니다. 이 메서드의 구현은 반드시 프래그먼트 레이아웃의 루트인 View를 반환해야 합니다.

참고: 프래그먼트가 ListFragment의 하위 클래스인 경우, 기본 구현이 onCreateView()로부터 ListView를 반환하므로 이를 구현하지 않아도 됩니다.

onCreateView()로부터 레이아웃을 반환하려면 이를 XML에서 정의된 레이아웃 리소스로부터 팽창시키면 됩니다. 이를 돕기 위해 onCreateView()LayoutInflater 객체를 제공합니다.

예를 들어 다음은 Fragment의 하위 클래스입니다. 이것이 example_fragment.xml 파일로부터 레이아웃을 로드합니다.

Kotlin

class ExampleFragment : Fragment() {

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.example_fragment, container, false)
    }
}

Java

public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}

참고: 위의 샘플에서 R.layout.example_fragment는 애플리케이션 리소스에 저장된 example_fragment.xml이라는 레이아웃 리소스에 대한 참조입니다. 레이아웃을 XML로 생성하는 방법에 대한 내용은 사용자 인터페이스 문서를 참조하세요.

onCreateView()로 전달된 container 매개변수가 상위 ViewGroup이며(액티비티 레이아웃으로부터), 이 안에 프래그먼트 레이아웃이 삽입됩니다. savedInstanceState 매개변수는 일종의 Bundle로, 이것은 프래그먼트가 재개되는 중인 경우 프래그먼트의 이전 인스턴스에 대한 데이터를 제공합니다(상태를 복원하는 것은 프래그먼트 수명 주기 처리에서 더욱 자세히 설명합니다).

inflate() 메서드는 다음과 같은 세 개의 인수를 취합니다.

  • 팽창시키고자 하는 레이아웃의 리소스 ID.
  • 팽창된 레이아웃의 상위가 될 ViewGroup. container를 전달해야 (실행 중인 상위 뷰에서 지정하였듯이) 시스템이 레이아웃 매개변수를 팽창된 레이아웃의 루트 뷰에 적용할 수 있으므로 중요한 과정입니다.
  • 팽창된 레이아웃이 팽창 중에 ViewGroup(두 번째 매개변수)에 첨부되어야 하는지를 나타내는 부울 값. (이 경우, 이 값은 false입니다. 시스템이 이미 팽창된 레이아웃을 container 안에 삽입하기 때문입니다. true를 전달하면 최종 레이아웃에 중복된 뷰 그룹을 생성하게 됩니다.)

이제 레이아웃을 제공하는 프래그먼트를 생성하는 방법을 알게 되셨습니다. 다음은 프래그먼트를 액티비티에 추가해야 합니다.

액티비티에 프래그먼트 추가

일반적으로 프래그먼트는 UI의 일부분으로 호스트 액티비티에 참가하고, 호스트 액티비티는 해당 액티비티의 전체적인 뷰 계층의 일부분으로 포함되게 됩니다. 프래그먼트를 액티비티 레이아웃에 추가하는 데는 두 가지 방법이 있습니다.

  • 프래그먼트를 액티비티의 레이아웃 파일 안에서 선언합니다.

    이 경우, 프래그먼트에 대한 레이아웃 속성을 마치 뷰인 것처럼 나타낼 수 있습니다. 예를 들어 다음은 프래그먼트가 두 개 있는 액티비티에 대한 레이아웃 파일입니다.

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <fragment android:name="com.example.news.ArticleListFragment"
                android:id="@+id/list"
                android:layout_weight="1"
                android:layout_width="0dp"
                android:layout_height="match_parent" />
        <fragment android:name="com.example.news.ArticleReaderFragment"
                android:id="@+id/viewer"
                android:layout_weight="2"
                android:layout_width="0dp"
                android:layout_height="match_parent" />
    </LinearLayout>
    

    <fragment> 안의 android:name 특성은 레이아웃 안에서 인스턴스화할 Fragment 클래스를 지정합니다.

    시스템은 이 액티비티 레이아웃을 생성할 때 레이아웃에서 지정된 각 프래그먼트를 인스턴스화하며 각각에 대해 onCreateView() 메서드를 호출하여 각 프래그먼트의 레이아웃을 검색합니다. 시스템은 프래그먼트가 반환한 View<fragment> 요소 자리에 곧바로 삽입합니다.

    참고: 각 프래그먼트에는 액티비티가 다시 시작되는 경우 프래그먼트를 복구하기 위해 시스템이 사용할 수 있는 고유한 식별자가 필요합니다(그리고 개발자는 이를 사용하여 프래그먼트를 캡처하고 이를 제거하는 등과 같은 여러 가지 트랜잭션을 수행할 수 있습니다). 프래그먼트에 ID를 제공하는 데는 다음과 같은 세 가지 방법이 있습니다.

    • 고유한 ID와 함께 android:id 특성을 제공합니다.
    • 고유한 문자열과 함께 android:tag 특성을 제공합니다.
  • 또는 프로그래밍 방식으로 프래그먼트를 기존의 ViewGroup에 추가합니다.

    액티비티가 실행 중인 동안에는 언제든 액티비티 레이아웃에 프래그먼트를 추가할 수 있습니다. 그저 프래그먼트를 배치할 ViewGroup을 지정하기만 하면 됩니다.

    액티비티 내에서 프래그먼트 트랜잭션을 수행하려면(예: 프래그먼트 추가, 제거, 교체) FragmentTransaction에서 가져온 API를 사용해야 합니다. FragmentTransaction의 인스턴스를 FragmentActivity에서 가져오는 방법은 다음과 같습니다.

    Kotlin

    val fragmentManager = supportFragmentManager
    val fragmentTransaction = fragmentManager.beginTransaction()
    

    Java

    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    

    그런 다음, add() 메서드를 사용하여 프래그먼트를 추가하고, 추가할 프래그먼트와 이를 삽입할 뷰를 지정하면 됩니다. 예를 들면 다음과 같습니다.

    Kotlin

    val fragment = ExampleFragment()
    fragmentTransaction.add(R.id.fragment_container, fragment)
    fragmentTransaction.commit()
    

    Java

    ExampleFragment fragment = new ExampleFragment();
    fragmentTransaction.add(R.id.fragment_container, fragment);
    fragmentTransaction.commit();
    

    add()에 전달되는 첫 인수가 ViewGroup이고 여기에 프래그먼트는 리소스 ID가 지정한 대로 배치되어야 합니다. 두 번째 매개변수는 추가할 프래그먼트입니다.

    FragmentTransaction을 변경하고 나면, 반드시 commit()을 호출해야 변경 내용이 적용됩니다.

프래그먼트 관리

액티비티 내의 프래그먼트를 관리하려면 FragmentManager를 사용해야 합니다. 이것을 가져오려면 액티비티에서 getSupportFragmentManager()를 호출하세요.

가령 FragmentManager를 가지고 할 수 있는 여러 가지 일은 다음과 같습니다.

  • 액티비티 내에 존재하는 프래그먼트를 findFragmentById()로 가져오거나(액티비티 레이아웃 내에서 UI를 제공하는 프래그먼트의 경우) 또는 findFragmentByTag()로 가져옵니다(UI를 제공하거나 하지 않는 프래그먼트의 경우).
  • popBackStack()을 사용하여 프래그먼트를 백 스택에서 꺼냅니다(사용자가 Back 명령을 시뮬레이션).
  • 백 스택에 변경 내용이 있는지 알아보기 위해 addOnBackStackChangedListener()로 리스너를 등록합니다.

이 메서드 및 그 외에 다른 메서드에 대한 자세한 내용은 FragmentManager 클래스 문서를 참조하세요.

이전 섹션에서 설명한 바와 같이 FragmentManager를 사용해도 FragmentTransaction을 열 수 있습니다. 이렇게 하면 프래그먼트 추가, 제거 등의 트랜잭션을 수행할 수 있습니다.

프래그먼트 트랜잭션 수행

액티비티에서 프래그먼트를 사용하는 경우에는 특히 사용자 상호작용에 응답하여 추가, 제거, 교체 및 다른 작업을 수행할 수 있다는 점이 유용합니다. 액티비티에 커밋한 변경사항의 집합을 트랜잭션이라고 하며, 이것을 수행하려면 FragmentTransaction 내의 API를 사용하면 됩니다. 해당 액티비티가 관리하는 백 스택에 행해진 각 트랜잭션을 저장할 수도 있습니다. 이렇게 하면 사용자가 프래그먼트 변경사항에서 역으로 탐색할 수 있습니다(액티비티를 역으로 탐색하는 것과 비슷합니다).

FragmentTransaction의 인스턴스를 FragmentManager로부터 가져오는 방법은 다음과 같습니다.

Kotlin

val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()

Java

FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

각 트랜잭션은 동시에 수행하고자 하는 변경사항의 집합입니다. 주어진 트랜잭션에 대해 수행하고자 하는 모든 변경사항을 설정하려면 add(), remove()replace()와 같은 메서드를 사용하면 됩니다. 그런 다음, 트랜잭션을 액티비티에 적용하려면 반드시 commit()을 호출해야 합니다.

하지만 commit()을 호출하기 전에 먼저 addToBackStack()를 호출해야 합니다. 이렇게 해야 트랜잭션을 프래그먼트 트랜잭션의 백 스택에 추가할 수 있습니다. 이 백 스택은 액티비티가 관리하며, 사용자가 Back 버튼을 누르면 이를 통해 이전 프래그먼트 상태로 되돌아갈 수 있습니다.

예를 들어 다음은 한 프래그먼트를 다른 프래그먼트로 교체하고 이전 상태를 백 스택에 보존하는 방법을 보여줍니다.

Kotlin

val newFragment = ExampleFragment()
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, newFragment)
transaction.addToBackStack(null)
transaction.commit()

Java

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

이 예시에서는 newFragment가 현재 레이아웃 컨테이너에서 식별된 프래그먼트(있는 경우)를 R.id.fragment_container ID로 교체합니다. addToBackStack()를 호출하면 교체 트랜잭션이 백 스택에 저장되므로, 사용자가 Back 버튼을 눌러 트랜잭션을 되돌리고 이전 프래그먼트를 다시 가져올 수 있습니다.

그러면 FragmentActivityonBackPressed()를 통해 백 스택에서 프래그먼트를 자동 검색합니다.

트랜잭션에 여러 개의 변경을 추가하고(예: 다른 add() 또는 remove()) addToBackStack()을 호출하면, commit()을 호출하기 전에 적용된 모든 변경사항이 백 스택에 하나의 트랜잭션으로 추가되며, Back 버튼을 누르면 모두 한꺼번에 되돌려집니다.

FragmentTransaction에 변경사항을 추가하는 순서는 중요하지 않습니다. 다만 다음과 같은 예외가 있습니다.

  • commit()을 마지막으로 호출해야 합니다.
  • 같은 컨테이너에 여러 개의 프래그먼트를 추가하는 경우, 이를 추가하는 순서에 따라 이들이 뷰 계층에 나타나는 순서가 결정됩니다.

프래그먼트를 제거하는 트랜잭션을 수행하면서 addToBackStack()을 호출하지 않는 경우, 해당 프래그먼트는 트랜잭션이 적용되면 소멸되고 사용자가 이를 되짚어 탐색할 수 없게 됩니다. 반면에 프래그먼트를 제거하면서 addToBackStack()을 호출하면, 해당 프래그먼트는 중단되고 사용자가 뒤로 탐색할 때 다시 시작됩니다.

팁: 각 프래그먼트 트랜잭션에 전환 애니메이션을 적용하려면 커밋하기 전에 setTransition()을 호출하면 됩니다.

commit()을 호출하더라도 즉시 트랜잭션이 실행되지는 않습니다. 그보다는 스레드가 준비되는 즉시 최대한 빨리 액티비티의 UI 스레드("기본" 스레드)에서 이 트랜잭션이 수행되도록 일정을 예약하는 것에 가깝습니다. 하지만 필요한 경우 UI 스레드에서 executePendingTransactions()를 호출하면 commit()이 제출한 트랜잭션을 즉시 실행할 수 있습니다. 트랜잭션이 다른 스레드의 작업에 대한 종속성이 아니라면 굳이 이렇게 해야 할 필요는 없습니다.

주의: 트랜잭션을 커밋할 때 commit()을 사용할 수 있는 것은 (사용자가 액티비티를 떠날 때) 액티비티가 그 상태를 저장하기 전뿐입니다. 그 시점 이후에 커밋하려고 하면 예외가 발생합니다. 이것은 액티비티를 복원해야 하는 경우 커밋 이후의 상태가 손실될 수 있기 때문입니다. 커밋이 손실되어도 괜찮은 상황이라면, commitAllowingStateLoss()를 사용하세요.

액티비티와의 통신

FragmentFragmentActivity로부터 독립적인 객체로 구현되었고 여러 개의 액티비티 안에서 사용할 수 있는 것이 사실이지만, 프래그먼트의 주어진 인스턴스는 그것을 포함하고 있는 액티비티에 직접적으로 연결되어 있습니다.

구체적으로 말하면, 이 프래그먼트는 getActivity()를 사용하여 FragmentActivity 인스턴스에 액세스하고 액티비티 레이아웃에서 뷰를 찾는 등의 작업을 손쉽게 수행할 수 있습니다.

Kotlin

val listView: View? = activity?.findViewById(R.id.list)

Java

View listView = getActivity().findViewById(R.id.list);

이와 마찬가지로 액티비티도 프래그먼트 안의 메서드를 호출할 수 있습니다. 그러려면 FragmentManager에서 Fragment에 대한 참조를 가져와야 하는데, 이때 findFragmentById() 또는 findFragmentByTag()를 사용합니다. 예를 들면 다음과 같습니다.

Kotlin

val fragment = supportFragmentManager.findFragmentById(R.id.example_fragment) as ExampleFragment

Java

ExampleFragment fragment = (ExampleFragment) getSupportFragmentManager().findFragmentById(R.id.example_fragment);

액티비티에 대한 이벤트 콜백 생성

어떤 경우에는 액티비티 및/또는 액티비티가 호스팅하는 다른 프래그먼트로 이벤트 또는 데이터를 공유하는 데 프래그먼트가 필요할 수 있습니다. 데이터를 공유하려면 공유된 ViewModel을 생성합니다. 이 내용은 ViewModel 가이드의 프래그먼트 간 데이터 공유 섹션에서 간략히 설명합니다. ViewModel로 처리할 수 없는 이벤트를 전달해야 할 경우 프래그먼트 안에서 콜백 인터페이스를 정의하고 호스트 액티비티가 이를 구현하도록 요구할 수 있습니다. 액티비티가 인터페이스를 통해 콜백을 수신하면, 필요에 따라 그 정보를 레이아웃 내의 다른 프래그먼트와 공유할 수 있습니다.

예를 들어 어떤 뉴스 애플리케이션은 액티비티 하나에 프래그먼트가 두 개 있습니다. 하나는 기사 목록을 표시(프래그먼트 A)하고 다른 하나는 기사 하나를 표시(프래그먼트 B)하는 경우, 목록 항목이 선택되면 프래그먼트 A가 액티비티에 알려야 프래그먼트 B에 해당 기사를 표시하라고 알릴 수 있습니다. 이 경우, OnArticleSelectedListener 인터페이스는 프래그먼트 A 내부에 선언됩니다.

Kotlin

public class FragmentA : ListFragment() {
    ...
    // Container Activity must implement this interface
    interface OnArticleSelectedListener {
        fun onArticleSelected(articleUri: Uri)
    }
    ...
}

Java

public static class FragmentA extends ListFragment {
    ...
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}

그러면 프래그먼트를 호스팅하는 액티비티가 OnArticleSelectedListener 인터페이스를 구현하고 onArticleSelected()를 재정의하여 프래그먼트 A로부터 발생한 이벤트를 프래그먼트 B에 알립니다. 호스트 액티비티가 이 인터페이스를 구현하게 하려면 프래그먼트 A의 onAttach() 콜백 메서드(프래그먼트를 액티비티에 추가할 때 시스템이 호출하는 메서드)가 OnArticleSelectedListener의 인스턴스를 생성해야 합니다. 이때 Activity로 전달되는 onAttach()를 형변환하는 방법을 사용합니다.

Kotlin

public class FragmentA : ListFragment() {

    var listener: OnArticleSelectedListener? = null
    ...
    override fun onAttach(context: Context) {
        super.onAttach(context)
        listener = context as? OnArticleSelectedListener
        if (listener == null) {
            throw ClassCastException("$context must implement OnArticleSelectedListener")
        }

    }
    ...
}

Java

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener listener;
    ...
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try {
            listener = (OnArticleSelectedListener) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(context.toString() + " must implement OnArticleSelectedListener");
        }
    }
    ...
}

액티비티가 인터페이스를 구현하지 않았다면 프래그먼트가 ClassCastException를 발생시킵니다. 성공 시에는 mListener 멤버가 액티비티의 OnArticleSelectedListener 구현에 대한 참조를 보유하므로, 프래그먼트 A가 액티비티와 이벤트를 공유할 수 있습니다. 이때 OnArticleSelectedListener 인터페이스가 정의한 메서드를 호출하는 방법을 사용합니다. 예를 들어 프래그먼트 A가 ListFragment의 확장인 경우, 사용자가 목록 항목을 클릭할 때마다 시스템이 프래그먼트 안의 onListItemClick()을 호출하고, 그러면 이것이 onArticleSelected()를 호출하여 해당 이벤트를 액티비티와 공유합니다.

Kotlin

public class FragmentA : ListFragment() {

    var listener: OnArticleSelectedListener? = null
    ...
    override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) {
        // Append the clicked item's row ID with the content provider Uri
        val noteUri: Uri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id)
        // Send the event and Uri to the host activity
        listener?.onArticleSelected(noteUri)
    }
    ...
}

Java

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener listener;
    ...
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Append the clicked item's row ID with the content provider Uri
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        // Send the event and Uri to the host activity
        listener.onArticleSelected(noteUri);
    }
    ...
}

onListItemClick()에 전달된 id 매개변수는 클릭한 항목의 행 ID이며, 액티비티(또는 다른 프래그먼트)가 이것을 사용해 애플리케이션의 ContentProvider에서 기사를 가져옵니다.

콘텐츠 제공자 사용법에 대한 자세한 내용은 콘텐츠 제공자 문서를 참조하세요.

앱 바에 항목 추가

프래그먼트는 onCreateOptionsMenu()를 구현하여 액티비티의 옵션 메뉴에(결과적으로 앱 바에도) 메뉴 항목을 추가할 수 있습니다. 이 메서드가 호출을 수신하게 하려면, setHasOptionsMenu() 중에 onCreate()를 호출하여 프래그먼트가 옵션 메뉴에 항목을 추가하고자 한다는 것을 나타내야 합니다. 그렇지 않으면 해당 프래그먼트가 onCreateOptionsMenu()로의 호출을 받지 못하게 됩니다.

그런 다음, 프래그먼트로부터 옵션 메뉴에 추가하는 모든 항목이 기존의 메뉴 항목에 추가됩니다. 메뉴 항목을 선택하면 해당 프래그먼트는 onOptionsItemSelected() 콜백도 수신하게 됩니다.

또한 registerForContextMenu()를 호출하는 방법으로 프래그먼트 레이아웃에 뷰를 등록하여 컨텍스트 메뉴를 제공할 수도 있습니다. 사용자가 컨텍스트 메뉴를 열면, 해당 프래그먼트는 onCreateContextMenu() 호출을 수신합니다. 사용자가 항목을 선택하면, 해당 프래그먼트는 onContextItemSelected() 호출을 수신합니다.

참고: 프래그먼트는 추가한 각 메뉴 항목이 선택될 때 콜백을 수신하지만, 사용자가 메뉴 항목을 선택할 때 그에 상응하는 콜백을 가장 처음 받는 것은 액티비티입니다. 액티비티가 구현한 항목 선택 시 콜백이 선택된 항목을 처리하지 않는 경우, 해당 이벤트는 프래그먼트의 콜백으로 전달됩니다. 이는 옵션 메뉴와 컨텍스트 메뉴에 모두 해당됩니다.

메뉴에 대한 자세한 내용은 메뉴 개발자 가이드와 앱 바 교육 과정을 참조하세요.

프래그먼트 수명 주기 처리

그림 3. 액티비티 수명 주기가 프래그먼트 수명 주기에 미치는 영향.

프래그먼트의 수명 주기를 관리하는 것은 액티비티의 수명 주기를 관리하는 것과 매우 비슷합니다. 액티비티와 마찬가지로 프래그먼트는 세 가지 상태로 존재할 수 있습니다.

재개됨(Resumed)
프래그먼트가 실행 중인 액티비티에 표시됩니다.
일시정지됨(Paused)
다른 액티비티가 포그라운드에 있고 포커스를 갖고 있지만, 이 프래그먼트가 있는 액티비티도 여전히 표시됩니다(포그라운드의 액티비티가 부분적으로 투명하거나 전체 화면을 뒤덮지 않습니다).
정지됨(Stopped)
프래그먼트가 보이지 않습니다. 호스트 액티비티가 정지되었거나 프래그먼트가 액티비티에서 제거되었지만 백 스택에 추가되었습니다. 정지된 프래그먼트도 여전히 표시는 됩니다(모든 상태 및 멤버 정보를 시스템이 보존합니다). 하지만 사용자에게는 더 이상 표시되지 않으며 액티비티를 종료하면 이것도 종료됩니다.

액티비티와 마찬가지로 onSaveInstanceState(Bundle), ViewModel 및 영구 로컬 저장소를 결합하여 구성이 변경되고 프로세스가 종료되더라도 프래그먼트의 UI 상태를 보존할 수 있습니다. UI 상태 보존에 대한 자세한 내용은 UI 상태 저장을 참조하세요.

액티비티와 프래그먼트의 수명 주기에서 가장 중대한 차이점은 해당되는 백 스택에 저장되는 방법에 있습니다. 기본적으로 액티비티는 정지되면 시스템에서 관리하는 액티비티의 백 스택에 들어갑니다(작업 및 백 스택에서 설명하였듯이 사용자는 Back 버튼을 눌러서 액티비티로 돌아갈 수 있습니다). 하지만 프래그먼트는 이를 제거하는 트랜잭션에서 addToBackStack()을 호출하여 인스턴스를 저장하라고 명시적으로 요청할 경우에만 호스트 액티비티에서 관리하는 백 스택으로 들어갑니다.

이를 제외하고는 프래그먼트 수명 주기를 관리하는 것은 액티비티의 수명 주기를 관리하는 것과 아주 비슷합니다. 액티비티 수명 주기와 관리 방법에 대한 자세한 내용은 액티비티 수명 주기 가이드와 수명 주기 인식 구성 요소로 수명 주기 처리를 참조하세요.

주의: Fragment 내에 Context 객체가 필요한 경우, getContext()를 호출하면 됩니다. 하지만 getContext()를 호출하는 것은 프래그먼트가 액티비티에 첨부되어 있는 경우로 국한해야 한다는 점에 유의하세요. 프래그먼트가 아직 첨부되지 않았거나 수명 주기가 끝날 때 분리되었다면 getContext()는 null을 반환합니다.

액티비티 수명 주기와의 조화

프래그먼트가 있는 액티비티의 수명 주기는 해당 프래그먼트의 수명 주기에 직접적인 영향을 미칩니다. 따라서 액티비티에 대한 각 수명 주기 콜백이 각 프래그먼트에 대한 비슷한 콜백을 발생시킵니다. 예를 들어 액티비티가 onPause()를 받으면, 해당 액티비티 내의 각 프래그먼트가 onPause()를 받습니다.

하지만 프래그먼트에는 프래그먼트의 UI를 빌드하고 소멸시키는 등의 같은 작업을 수행하기 위해 액티비티와의 고유한 상호작용을 처리하는 몇 가지 수명 주기 콜백이 더 있습니다. 이러한 추가 콜백 메서드는 다음과 같습니다.

onAttach()
프래그먼트가 액티비티와 연결되어 있었던 경우 호출됩니다(여기에서 Activity가 전달됩니다).
onCreateView()
프래그먼트와 연결된 뷰 계층을 생성하기 위해 호출됩니다.
onActivityCreated()
액티비티의 onCreate() 메서드가 반환할 때 호출됩니다.
onDestroyView()
프래그먼트와 연결된 뷰 계층이 제거되는 중일 때 호출됩니다.
onDetach()
프래그먼트가 액티비티와 연결이 끊어지는 중일 때 호출됩니다.

호스트 액티비티의 영향을 받는 동안 프래그먼트 수명 주기의 흐름은 그림 3과 같습니다. 이 그림은 액티비티의 각 연속된 상태에 따라 프래그먼트가 어떤 콜백 메서드를 수신할지 결정된다는 것을 보여줍니다. 예를 들어 액티비티가 자신의 onCreate() 콜백을 받은 경우, 해당 액티비티 안에 있는 프래그먼트는 onActivityCreated() 콜백을 받을 뿐입니다.

액티비티가 재개된 상태에 도달하면 자유자재로 프래그먼트를 액티비티에 추가하거나 액티비티에서 제거해도 됩니다. 따라서 액티비티가 재개된 상태에 있는 동안에만 프래그먼트의 수명 주기를 독립적으로 변경할 수 있습니다.

그러나 액티비티가 재개된 상태를 떠나면 액티비티는 다시 프래그먼트를 그 수명 주기 안으로 넣습니다.

예시

이 문서에서 설명한 내용을 한 번에 보여드리기 위해 두 개의 프래그먼트를 사용하여 창이 두 개인 레이아웃을 생성하는 액티비티를 다음과 같은 예시로 나타냈습니다. 아래의 액티비티에 포함된 한 프래그먼트는 셰익스피어 희곡 제목 목록을 표시하고, 다른 하나는 목록에서 선택했을 때 해당 희곡의 요약을 표시합니다. 또한 화면 구성에 기초해 프래그먼트를 여러 가지로 구성하여 제공하는 방법도 보여줍니다.

참고: 이 액티비티의 완전한 소스 코드는 예시 FragmentLayout 클래스의 사용 방법을 보여주는 샘플 앱에서 제공됩니다.

주요 액티비티는 onCreate() 중에 일반적인 방식으로 레이아웃을 적용합니다.

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.fragment_layout)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.fragment_layout);
}

적용된 레이아웃은 fragment_layout.xml입니다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent" />

    <FrameLayout android:id="@+id/details" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent"
            android:background="?android:attr/detailsElementBackground" />

</LinearLayout>

이 레이아웃을 사용할 경우, 시스템은 액티비티가 레이아웃을 로드하자마자 TitlesFragment(희곡 제목 나열)를 초기화합니다. 반면 FrameLayout(희곡 요약을 표시하는 프래그먼트가 배치될 곳)은 화면 오른쪽에 있는 공간을 차지하기는 하지만 처음에는 텅 빈 상태로 유지됩니다. 아래에서 볼 수 있듯이, 사용자가 해당 목록에서 항목을 하나 선택해야만 프래그먼트가 FrameLayout 안에 배치됩니다.

그러나 희곡 목록과 요약을 둘 다 나란히 표시할 만큼 화면 구성의 너비가 넓지 않을 수도 있습니다. 따라서 위의 레이아웃은 가로 방향 화면 구성에만 사용되며, 이를 res/layout-land/fragment_layout.xml에 저장합니다.

그러므로 화면이 세로 방향으로 구성된 경우, 시스템은 다음 레이아웃을 적용합니다. 이는 res/layout/fragment_layout.xml에 저장됩니다.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles"
            android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>

이 레이아웃에는 TitlesFragment만 포함됩니다. 다시 말해, 기기가 세로 방향인 경우에는 희곡 제목 목록만 표시된다는 뜻입니다. 따라서 사용자가 이 구성에서 목록 항목을 하나 클릭하면, 애플리케이션이 두 번째 프래그먼트를 로딩하는 대신 새 액티비티를 시작하여 요약을 표시합니다.

다음으로, 프래그먼트 클래스에서 이를 수행하는 방법을 보겠습니다. 첫 번째는 TitlesFragment이며, 셰익스피어 희곡 제목 목록을 표시합니다. 이 프래그먼트는 ListFragment를 확장하고, 여기에 의존해 목록 뷰 작업의 대부분을 처리합니다.

이 코드를 살펴볼 때는 사용자가 목록 항목을 클릭하면 일어날 수 있는 두 가지 동작이 있다는 점을 참고하세요. 두 레이아웃 중 어느 것이 활성화 상태인지에 따라 같은 액티비티 내에서 세부 사항을 표시하기 위해 새 프래그먼트를 생성하거나 표시할 수도 있고(프래그먼트를 FrameLayout에 추가), 새 액티비티를 시작할 수도 있습니다(프래그먼트를 표시할 수 있는 경우).

Kotlin

class TitlesFragment : ListFragment() {

    private var dualPane: Boolean = false
    private var curCheckPosition = 0

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        // Populate list with our static array of titles.
        listAdapter = ArrayAdapter<String>(
                activity,
                android.R.layout.simple_list_item_activated_1,
                Shakespeare.TITLES
        )

        // Check to see if we have a frame in which to embed the details
        // fragment directly in the containing UI.
        val detailsFrame: View? = activity?.findViewById(R.id.details)
        dualPane = detailsFrame?.visibility == View.VISIBLE

        curCheckPosition = savedInstanceState?.getInt("curChoice", 0) ?: 0

        if (dualPane) {
            // In dual-pane mode, the list view highlights the selected item.
            listView.choiceMode = ListView.CHOICE_MODE_SINGLE
            // Make sure our UI is in the correct state.
            showDetails(curCheckPosition)
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putInt("curChoice", curCheckPosition)
    }

    override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) {
        showDetails(position)
    }

    /**
     * Helper function to show the details of a selected item, either by
     * displaying a fragment in-place in the current UI, or starting a
     * whole new activity in which it is displayed.
     */
    private fun showDetails(index: Int) {
        curCheckPosition = index

        if (dualPane) {
            // We can display everything in-place with fragments, so update
            // the list to highlight the selected item and show the data.
            listView.setItemChecked(index, true)

            // Check what fragment is currently shown, replace if needed.
            var details = fragmentManager?.findFragmentById(R.id.details) as? DetailsFragment
            if (details?.shownIndex != index) {
                // Make new fragment to show this selection.
                details = DetailsFragment.newInstance(index)

                // Execute a transaction, replacing any existing fragment
                // with this one inside the frame.
                fragmentManager?.beginTransaction()?.apply {
                    if (index == 0) {
                        replace(R.id.details, details)
                    } else {
                        replace(R.id.a_item, details)
                    }
                    setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                    commit()
                }
            }

        } else {
            // Otherwise we need to launch a new activity to display
            // the dialog fragment with selected text.
            val intent = Intent().apply {
                setClass(activity, DetailsActivity::class.java)
                putExtra("index", index)
            }
            startActivity(intent)
        }
    }
}

Java

public static class TitlesFragment extends ListFragment {
    boolean dualPane;
    int curCheckPosition = 0;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Populate list with our static array of titles.
        setListAdapter(new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));

        // Check to see if we have a frame in which to embed the details
        // fragment directly in the containing UI.
        View detailsFrame = getActivity().findViewById(R.id.details);
        dualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;

        if (savedInstanceState != null) {
            // Restore last state for checked position.
            curCheckPosition = savedInstanceState.getInt("curChoice", 0);
        }

        if (dualPane) {
            // In dual-pane mode, the list view highlights the selected item.
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            // Make sure our UI is in the correct state.
            showDetails(curCheckPosition);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("curChoice", curCheckPosition);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        showDetails(position);
    }

    /**
     * Helper function to show the details of a selected item, either by
     * displaying a fragment in-place in the current UI, or starting a
     * whole new activity in which it is displayed.
     */
    void showDetails(int index) {
        curCheckPosition = index;

        if (dualPane) {
            // We can display everything in-place with fragments, so update
            // the list to highlight the selected item and show the data.
            getListView().setItemChecked(index, true);

            // Check what fragment is currently shown, replace if needed.
            DetailsFragment details = (DetailsFragment)
                    getSupportFragmentManager().findFragmentById(R.id.details);
            if (details == null || details.getShownIndex() != index) {
                // Make new fragment to show this selection.
                details = DetailsFragment.newInstance(index);

                // Execute a transaction, replacing any existing fragment
                // with this one inside the frame.
                FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
                if (index == 0) {
                    ft.replace(R.id.details, details);
                } else {
                    ft.replace(R.id.a_item, details);
                }
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }

        } else {
            // Otherwise we need to launch a new activity to display
            // the dialog fragment with selected text.
            Intent intent = new Intent();
            intent.setClass(getActivity(), DetailsActivity.class);
            intent.putExtra("index", index);
            startActivity(intent);
        }
    }
}

두 번째 프래그먼트인 DetailsFragmentTitlesFragment에서 가져온 목록에서 선택한 항목에 대한 희곡 요약을 표시합니다.

Kotlin

    class DetailsFragment : Fragment() {

        val shownIndex: Int by lazy {
            arguments?.getInt("index", 0) ?: 0
        }

        override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
        ): View? {
            if (container == null) {
                // We have different layouts, and in one of them this
                // fragment's containing frame doesn't exist. The fragment
                // may still be created from its saved state, but there is
                // no reason to try to create its view hierarchy because it
                // isn't displayed. Note this isn't needed -- we could just
                // run the code below, where we would create and return the
                // view hierarchy; it would just never be used.
                return null
            }

            val text = TextView(activity).apply {
                val padding: Int = TypedValue.applyDimension(
                        TypedValue.COMPLEX_UNIT_DIP,
                        4f,
                        activity?.resources?.displayMetrics
                ).toInt()
                setPadding(padding, padding, padding, padding)
                text = Shakespeare.DIALOGUE[shownIndex]
            }
            return ScrollView(activity).apply {
                addView(text)
            }
        }

        companion object {
            /**
             * Create a new instance of DetailsFragment, initialized to
             * show the text at 'index'.
             */
            fun newInstance(index: Int): DetailsFragment {
                val f = DetailsFragment()

                // Supply index input as an argument.
                val args = Bundle()
                args.putInt("index", index)
                f.arguments = args

                return f
            }
        }
    }
}

Java

public static class DetailsFragment extends Fragment {
    /**
     * Create a new instance of DetailsFragment, initialized to
     * show the text at 'index'.
     */
    public static DetailsFragment newInstance(int index) {
        DetailsFragment f = new DetailsFragment();

        // Supply index input as an argument.
        Bundle args = new Bundle();
        args.putInt("index", index);
        f.setArguments(args);

        return f;
    }

    public int getShownIndex() {
        return getArguments().getInt("index", 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (container == null) {
            // We have different layouts, and in one of them this
            // fragment's containing frame doesn't exist. The fragment
            // may still be created from its saved state, but there is
            // no reason to try to create its view hierarchy because it
            // isn't displayed. Note this isn't needed -- we could just
            // run the code below, where we would create and return the
            // view hierarchy; it would just never be used.
            return null;
        }

        ScrollView scroller = new ScrollView(getActivity());
        TextView text = new TextView(getActivity());
        int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                4, getActivity().getResources().getDisplayMetrics());
        text.setPadding(padding, padding, padding, padding);
        scroller.addView(text);
        text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
        return scroller;
    }
}

TitlesFragment 클래스에서 설명한 내용을 상기하면, 사용자가 목록 항목을 클릭하고 현재 레이아웃이 (DetailsFragment가 속한) R.id.details 뷰를 포함하지 않는 경우, 애플리케이션은 항목의 내용을 표시하기 위해 DetailsActivity 액티비티를 시작하게 됩니다.

다음은 DetailsActivity입니다. 이는 화면이 세로 방향으로 구성되어 있을 때 선택한 희곡의 요약을 표시하기 위해 단순히 DetailsFragment를 포함합니다.

Kotlin

class DetailsActivity : FragmentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish()
            return
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            val details = DetailsFragment().apply {
                arguments = intent.extras
            }
            supportFragmentManager.beginTransaction()
                    .add(android.R.id.content, details)
                    .commit()
        }
    }
}

Java

public static class DetailsActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getSupportFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    }
}

이 액티비티는 가로 구성일 때 자체 종료되므로 기본 액티비티가 나와서 TitlesFragment와 함께 DetailsFragment를 표시합니다. 사용자가 세로 방향일 때 DetailsActivity를 시작했지만 가로 방향으로 회전된 경우(현재 액티비티를 다시 시작), 이런 상황이 발생할 수 있습니다.

추가 리소스

Fragment는 레이아웃은 Sunflower 데모 앱에서 사용됩니다.