유연한 UI 구축

다양한 화면 크기를 지원하는 애플리케이션을 디자인하는 경우, 사용 가능한 화면 공간을 토대로 사용자 환경을 최적화하도록 프래그먼트를 여러 레이아웃 구성에 재사용할 수 있습니다.

예를 들어, 핸드셋 기기에서는 단일 창 사용자 인터페이스에 한 번에 하나의 프래그먼트만 표시하는 것이 적합할 수 있습니다. 반대로 화면이 커서 사용자에게 더 많은 정보를 표시하는 태블릿에서는 프래그먼트를 나란히 배치할 수 있습니다.

그림 1. 크기가 다른 화면에 동일한 액티비티에 대해 각기 다른 구성으로 표시된 두 개의 프래그먼트. 큰 화면에서는 두 프래그먼트가 나란히 표시되며, 핸드셋 기기에서는 프래그먼트가 한 번에 하나만 표시되고 사용자가 탐색할 때 프래그먼트가 서로 교체됩니다.

FragmentManager 클래스는 동적 환경을 생성하기 위해 런타임에 액티비티에 프래그먼트를 추가, 제거 및 교체할 수 있는 메서드를 제공합니다.

런타임에 액티비티에 프래그먼트 추가

<fragment> 요소를 사용하여 이전 과정에서 소개한 것처럼 레이아웃 파일에 액티비티의 프래그먼트를 정의하기보다는 액티비티 런타임 중 프래그먼트를 액티비티에 추가할 수 있습니다. 이는 액티비티의 수명 도중 프래그먼트를 변경하려는 경우 필요합니다.

프래그먼트 추가 또는 제거와 같은 트랜잭션을 수행하려면 FragmentManager를 사용하여 FragmentTransaction을 생성해야 합니다. FragmentTransaction은 프래그먼트를 추가, 제거 및 교체하고 기타 프래그먼트 트랜잭션을 수행하는 API를 제공합니다.

액티비티에서 프래그먼트의 교체와 제거를 허용하는 경우, 액티비티의 onCreate() 메서드 동안 초기 프래그먼트를 액티비티에 추가해야 합니다.

프래그먼트 작업 시, 특히 런타임에 프래그먼트를 추가할 때 유념해야 할 중요한 규칙은 프래그먼트를 삽입할 수 있는 컨테이너 View를 액티비티 레이아웃에 포함하는 것입니다.

다음은 이전 레슨에서 소개한, 한 번에 하나의 프래그먼트만 표시하는 레이아웃에 대한 대체 레이아웃입니다. 프래그먼트를 다른 프래그먼트와 교체하기 위해 액티비티 레이아웃은 프래그먼트 컨테이너로 작용하는 빈 FrameLayout을 포함합니다.

파일 이름이 이전 과정의 레이아웃 파일과 같지만, 레이아웃 디렉터리에 large 한정자가 없음을 주의하세요. 이 레이아웃은 기기 화면이 대형보다 작아서 두 개의 프래그먼트를 동시에 표시할 수 없을 때 사용합니다.

res/layout/news_articles.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" />

액티비티에서 Support Library API를 통해 getSupportFragmentManager()를 호출하여 FragmentManager를 가져옵니다. 그런 다음 beginTransaction()을 호출하여 FragmentTransaction을 생성하고 add()를 호출하여 프래그먼트를 추가합니다.

동일한 FragmentTransaction을 사용하여 액티비티의 여러 프래그먼트 트랜잭션을 수행할 수 있습니다. 변경할 준비가 되면 commit()을 호출해야 합니다.

다음은 이전 레이아웃에 프래그먼트를 추가하는 방법에 대한 예입니다.

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.news_articles);

        // Check that the activity is using the layout version with
        // the fragment_container FrameLayout
        if (findViewById(R.id.fragment_container) != null) {

            // However, if we're being restored from a previous state,
            // then we don't need to do anything and should return or else
            // we could end up with overlapping fragments.
            if (savedInstanceState != null) {
                return;
            }

            // Create a new Fragment to be placed in the activity layout
            HeadlinesFragment firstFragment = new HeadlinesFragment();

            // In case this activity was started with special instructions from an
            // Intent, pass the Intent's extras to the fragment as arguments
            firstFragment.setArguments(getIntent().getExtras());

            // Add the fragment to the 'fragment_container' FrameLayout
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.fragment_container, firstFragment).commit();
        }
    }
}

프래그먼트가 <fragment> 요소로 액티비티 레이아웃에 정의되는 대신 런타임에 FrameLayout 컨테이너에 추가되었기 때문에, 액티비티가 이 프래그먼트를 제거하고 다른 프래그먼트로 교체할 수 있습니다.

다른 프래그먼트와 교체

프래그먼트 교체 절차는 추가 절차와 비슷하지만, add() 대신 replace() 메서드를 사용해야 합니다.

프래그먼트 교체 또는 제거와 같은 프래그먼트 트랜잭션 수행 시, 사용자가 뒤로 돌아가서 변경 사항을 "실행취소"할 수 있도록 허용하는 것이 필요할 때도 있습니다. 프래그먼트 트랜잭션에서 사용자가 뒤로 돌아갈 수 있게 하려면, FragmentTransaction을 커밋하기 전에 addToBackStack()을 호출해야 합니다.

참고: 프래그먼트를 제거 또는 교체하고 해당 트랜잭션을 백 스택에 추가하면 제거된 프래그먼트가 파기되지 않고 중단됩니다. 사용자가 되돌아가서 프래그먼트를 복원하면 프래그먼트가 재시작됩니다. 트랜잭션을 백 스택에 추가하지 않으면, 프래그먼트가 제거되거나 교체될 때 파기됩니다.

다른 프래그먼트와의 교체 예:

// Create fragment and give it an argument specifying the article it should show
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

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

// Commit the transaction
transaction.commit();

addToBackStack() 메서드는 트랜잭션의 고유한 이름을 지정하는 선택적인 문자열 매개변수를 취합니다. FragmentManager.BackStackEntry API를 사용하여 고급 프래그먼트 작업을 수행하려는 경우 외에는 이 이름이 필요하지 않습니다.