[번역] The Android 3.0 Fragments API 안드로이드

2011/02/08 22:58

복사 http://huewu.blog.me/110102599243

The Android 3.0 Fragments API

[이 포스트는 안드로이드 개발에 핵심적인 역할을 담당하는, 소프트웨어 엔지니어 Dianne Hackborn 에 의해 작성되었습니다. - Tim Bray]

원문: http://android-developers.blogspot.com/2011/02/android-30-fragments-api.html


 안드로이드 3.0 의 주요 목표 중 하나는, 개발자들이 보다 쉽게 다양한 해상도를 지원하는 어플리케이션을 작성할 수 있도록 만들어 주는 것 입니다. 다양한 해상도를 지원하는 어플리케이션을 개발 할 수 있도록, 지금까지 안드로이드 플랫폼에서는 다음과 같은 기능을 지원해 주고 있었습니다.

  • 애초부터, 안드로이드 UI 프레임워크는 레이아웃 매니저 계층을 사용하도록 구성되었습니다. 레이아웃 매니저를 통해, UI 구성 요소들은 현재 활용 가능한 화면 크기에 맞추어 적절하게 자신의 크기와 위치를 조절할 수 있습니다. 한 가지 예로 ListView 의 높이는 디바이스의 화면 크기가 QVGA, HVGA, WVGA 이건 무관하게, 또한 화면이 가로 세로 비율이 어떻든간에 화면에 꼭 맞추어 알아서 크기가 조절됩니다. 
  • 안드로이드 1.6 에서는 Screen Density (DIP) 라는 새로운 개념이 소개되었습니다. 이를 통해, 개발자들은 실재 물리적인 화면 크기가 유사한 경우, 화면 해상도에대하여 별다른 고민없이 다양한 해상도의 휴대 단말을 지원하는  UI 를 구성할 수 있게 되었습니다. 덕분에, 1.6 발표 이 후 출시된 모토로이 드로이드와 같은 다양한 고해상도 휴대폰등에서 별다른 호환성 문제 없이 기존의 어플리케이션이 동작할 수 있었습니다.
  • 또한, 안드로이드 1.6 에서는 화면의 크기를 몇 가지 카테고리로 나누어 분류 하였습니다. QVGA 급 화면은 "small" , HVGA 나 WVGA 급 화면은 "normal", 그 이상의 크기를 갖는 경우에는 "large" 로 말이지요. 어플리케이션 개발자는 안드로이드에서 제공하는 리소스 시스템을 활용하여, 자신의 어플에서 사용되는 리소스와 화면 레이아웃을 서로 다른 해상도에 맞추어 구분하여 구성할 수 있게 되었습니다.

 레이아웃 메니저와 화면 크기에 따라 유연하게 동작하는 리소스 선택 시스템은 '개발자들이 다양한 크기의 안드로이드 디바이스에서 자연스럽게 동작하는 어플리케이션을 만들 수 있게 하자' 는 원대한 목표하에서 구현되었습니다. 그리고 결과적으로, 다행스럽게도 기존에 개발된 많은 어플리케이션이 최근 새롭게 발표된 10인치 크기의 타블렛용 OS 인 허니콤 상에서 별다른 문제 없이 '잘' 동작하는 것으로 확인되고 있습니다. 하지만, 허니콤이 공개되고 타블렛을 위해 UI 요소에 많은 변화가 생김에 따라, 안드로이드 개발팀은 '만일 기존 어플리케이션이 10인치의 타블렛을 위해 고안된 새로운 UI 요소들을 손 쉽게 활용 할 수 있다면 여러가지 추가적인 장점을 누릴 수 있지 않을까' 고민하게 되었습니다. 그 결과, Fragment 클래스가 추가되었습니다.

Introducing the Fragment
 어플리케이션 개발자는 안드로이드 3.0 에서 새롭게 추가된 Fragment 클래스를 이용하여 어플리케이션의 UI 를 보다 상황에 따라 변경하거나 조절할 수 있습니다. Fragment 는 별도의 UI 와 생명주기를 갖는 독립된 어플리케이션 컴포넌트입니다. 따라서, 여러분은 특정 디바이스 혹은 스크린 해상도에 따라서, 여러 개의 Fragment 를 조합하거나 재배치 하는 방식으로 원하는 UI 를 손쉽게 구성할 수 있습니다.

 다시말해, Fragment 는 미니 엑티비티라고 생각하실 수 있습니다. 일반적인 엑티비티와는 다르게, 반드시 실재 엑티비티에 포함되어 동작해야 되긴 하지만 말이지요. 안드로이드 개발팀은 Fragment 관련 기능을 추가하면서, 어플리케이션 개발자들이 엑티비티를 사용할 때 겪게 되는 일반적인 불편함에 대해 다시 살펴보고 이를 해결할 수 있는 방안을 검토했으며, 그 결과 개발된 Fragment API 는 그저 다양한 해상도나 화면 모드에 따라 UI 를 변경하는 기능 이상의 역할을 수행 할 수 있습니다.

  • 이전 버전의 안드로이드 프레임워크 상에서도, 엑티비티 그룹(ActivityGroup)을 이용하여 복 수의 엑티비티를 하나의 엑티비티에 담을 수 있습니다. (주> 대표적으로 TabActivity) 하지만, 기본적으로 엑티비티는 독립적으로 동작하도록 설계되었기 때문에, 엑티비티 그룹을 적절하게 활용하는 것은 결코 쉬운일이 아니였습니다. Fragment API 를 활용하면 이런 일을 훨씬 더 잘 처리할 수 있으며, 따라서 안드로이드 개발팀은 Fragment 컴포넌트를 이용하면 기존의 임베디드 엑티비티를 완전히 대체할 수 있지 않을까 생각하고 있습니다.
  •  지금까지 화면이 회전되거나 설정 값이 변경됨에 현재 엑티비티가 Destroy 되고 뒤이어 새로운 엑티비티가 생성되는 경우, 두 엑티비티 인스턴스간에 데이타를 유지하기 위해서는, Activity.onRetainNonConfigurationInstance() 메서드를 활용해야 했습니다. (주> 많은 분들이 단순하게 아예 엑티비티가 새로 생성되지 않도록 막는 경우가 있지만, 화면 모드에 따라 전혀 다른 UI 를 제공하고자 한다면 별 수 없이 새로운 엑티비티 인스턴스가 생성되어야 합니다.) 뭐, 크게 어려운 일은 아닐지라도 좀 투박하고 불명확한 솔루션이었지요. Fragment API 를 활용하면, 특정 Fragment 인스턴스에 적절한 플래그만 설정해 주시면, 프레임워크 내에서 해당 인스턴스를 보존해 줍니다.
  • DialogFragment 클래스가 제공됩니다. 이 새로운 클래스를 활용하면, 엑티비티 라이프 사이클에 잘 부합되도록 다이얼로그 박스를 표시하는게 훨씬 수월해 줍니다. (주> 예를 들어, 화면이 회전되는 등의 이유로 엑티비티가 종료되고 재 시작될 때, 기존 다이얼로그 박스에서 표시된 정보를 똑같이 복원하기 위해서는 꽤나 골치아픈 작업이 필요했지요....) 엑티비티가 기존에 엑티비티에서 사용되던 "managed dialog API" 를 대채할 수 있습니다.
  • 또 한가지 ListFragment 클래스가 제공됩니다. 이 클래스를 활용하면, 데이타 목록을 손쉽게 표시할 수 있습니다. 몇 가지 기능이 추가된 점을 제외하면, 기존 ListActivity 와 유사하지만, Fragment 특성 상, 임의의 엑티비티에 손쉽게 포함될 수 있기 때문에, 흔한 질문 중 하나인, '데이타 목록과 동시에 여러가지 추가 정보를 표시하려면 어떻게 하나요?' 같은 문제를 손쉽게 해결 할 수 있게 도와줄 것 입니다.
  • 현재 실행중인 엑티비티에 포함되어 있는 모든 Fragment 인스턴스 정보는 해당 엑티비티의 'saved instance state' 로 자동으로 저장되며, 엑티비티가 잠시 종료되었다가 재 시작될 때 자동으로 복원됩니다. 따라서 여러분이 UI의 상태 정보를 저장, 관리하기 위해 들이는 귀찮은 작업을 대폭 감소시킬 수 있습니다.
  • 안드로이드 프레임워크는 Fragment  인스턴스의 'back-stack'(이전 상태를 저장해 두는 스택) 을 관리합니다. 따라서 여러분은 엑티비티 내부에 뒤로 가기 버튼등의 기능을 손쉽게 구현 할 수 있습니다.

Getting started
 Fragment 에 대한 여러분의 궁금증을 달래기 위해, 이를 활용하여 화면 모드에 따라서 UI 가 완전히 변화되는 어플리케이션을 만들기 위한 샘플 코드를 설명해 볼까 합니다. 우선, 화면이 랜드스케이프(가로) 모드일 때, 화면 왼쪽에 아이템 목록이 표시되고, 오른편에는 선택된 아이템의 상세 정보가 표시되는 레이아웃을 디자인 해 보도록 하겠습니다.

이런 엑티비티를 위한 코드는 별 다를게 없습니다. 그저, onCreate() 시점에 setContentView() 메서드를 호출하여, 아래의 레이아웃 코드를 설정해주면 됩니다.
<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.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" />
   
</LinearLayout>
 레이아웃 코드를 살펴 보시면, 새롭게 추가된 <fragment> 태그를 확인 할 수 있습니다. 이 태그를 사용하면 프레임워크에서 자동적으로 Fragment 인스턴스를 생성하고, 생성된 Fragment 인스턴스를 엑티비티 View 계층 구조 안에 삽입합니다. 여기서 사용된 Fragment 는 ListFragment 를 상속받아 작성되었으며 아이템 목록을 관리하고 화면상에 출력합니다. 
public static class TitlesFragment extends ListFragment {
   
boolean mDualPane;
   
int mCurCheckPosition = 0;

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

       
// Populate list with our static array of titles.
        setListAdapter
(new ArrayAdapter<String>(getActivity(),
                R
.layout.simple_list_item_checkable_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);
        mDualPane
= detailsFrame != null
               
&& detailsFrame.getVisibility() == View.VISIBLE;

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

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

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

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

   
/**
     * 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) {
        mCurCheckPosition
= index;

       
if (mDualPane) {
           
// We can display everything in-place with fragments.
           
// Have the list highlight this item and show the data.
            getListView
().setItemChecked(index, true);

           
// Check what fragment is shown, replace if needed.
           
DetailsFragment details = (DetailsFragment)
                    getFragmentManager
().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
                       
= getFragmentManager().beginTransaction();

                ft
.replace(R.id.details, 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);
       
}
   
}
}
 구현 내용을 살펴보시면, UI 레이아웃 상에, 선택된 아이템의 상세 정보를 표시할 수 있는 'details' 라는 ID 의 View 가 존재하는지 여부에 따라, 상세 정보를 현재 엑티비티 내에서 모두 표현할지, 아니면 별도의 엑티비티를 호출하여 표현할지 구분되어 구현되어있음을 확인 하실 수 있습니다. 또, Fragment 인스턴스의 정보는 프레임워크 내에서 자동으로 저장 관리되기 때문에, newInstance() 메서드를 통해 생성된 DetailsFragment 인스턴스를 별다른 관리 없이 FragmentManager 의 findFragmentById 메서드를 통해 재활용해 사용하고 있습니다.

 선택된 아이템 상세 정보를 표현하기 위한 Details첫 화면을 위해서 DetailsFragment 는 주어진 문자열을 TextView 로 출력하는 역할을 수행합니다. 다음과 같이 간단하게 구현할 수 있습니다.

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) {
           
// Currently in a layout without a container, so no
           
// reason to create our view.
           
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;
   
}
}
 이제, 추가로 화면이 포트레이트 (세로) 모드일 때를 위한 새로운 UI 를 구성해 보겠습니다. 새로 모드의 경우, 두 개의 Fragment 를 나란히 배치할 공간이 부족하니, 이 경우에는 오직 아이템 목록만을 보여주고, 사용자가 아이템을 클릭하면 별도의 엑티비티에서 상세 정보를 보여주고자 합니다.

 사실 이 작업은, 지금까지 작성해둔 코드를 활용하면, 포트레이트 모드를 위하여 아래와 같은 레이아웃을 선언하면 끝입니다.
<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.TitlesFragment"
           
android:id="@+id/titles"
           
android:layout_width="match_parent"
           
android:layout_height="match_parent" />
</FrameLayout>
위 구현내용을 살펴보시면 알 수 있듯이, TitleFragment 는 아이템 상세 정보를 표시하기 위한 'details' ID 를 갖는 View 가 없는 경우 단지 아이템 목록만을 표시하고, 여러분 하나의 아이템을 선택할 때, 해당 아이템의 상세 정보를 담은 새로운 엑티비티를 표시하게 될 것 입니다.

 이미 상세 정보를 출력하기 위해 DetailsFragment 를 구현하였기 때문에, 새로운 엑티비티도 손쉽게 작성할 수 있습니다.
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 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();
       
}
   
}
}
 작업이 완료되었습니다. 우리는 새로운 Fragment API 를 이용하여, 화면 모드가 변함에 따라 UI 플로우가 완전히 변경되는 온전한 어플리케이션 예제를 만들었습니다.

 지금까지 예제를 통해 Fragment 를 활용하여 UI 를 변경할 수 있는 한 가지 방법을 설명했습니다. 여러분의 어플리케이션 디자인에 따라서, 다양한 방식으로 이를 활용할 수도 있습니다. 예를 들어, 여러분은 전체 어플리케이션을 하나의 엑티비티에 담고, 엑티비티의 상태가 변함에 따라 내부 Fragment 의 구조만을 변경하는 방식으로 어플리케이션을 작성할 수도 있을 것 입니다. 이런경우, Fragment 가 자체적으로 'back-stack' 을 유지하고 있다는 사실이 큰 도움이 될 것 입니다.
 
 Fragment 와 FragmentManager API 에 관한 보다 상세한 정보는 안드로이드 3.0 SDK 문서에서 살펴보시거나, 리소스 항목 아래에 있는 ApiDemos 를 참조해 보셔도 좋습니다. 안드로이드 개발팀은 자유롭게 변환되는 UI 플로우, Fragment 다이얼로그, 리스트, 메뉴 생성, 엑티비티 인스턴스간에 Fragment 인스턴스를 유지하기, 'back-stack' 등등 다양한 예제 코드를 마련해 두었습니다.

Fragmentation for all!
 안드로이드 3.0 기반의 태블릿 용 어플리케이션 개발자들은 새로운 Fragment API 를 이용하여, 큰 화면에 따른 다양한 UI 설계 문제를 보다 잘 처리하실 수 있을 것 입니다. 거기에 덧붙여 만일에, Fragment 를 잘 활용한다면, 향후 등장할 새로운 안드로이드 디바이스 - 휴대폰, TV 등등 에 맞추어 여러분의 어플리케이션 UI 를 쉽게 변경할 수 있을 것 입니다.
 
 하지만, 바로 오늘 이 순간에는 많은 개발자들이 현재 존재하는 휴대폰 용 어플리케이션을 작성하고 있으며, 해당  어플리케이션이 타블렛 상에서는 보다 향상된 UI 요소를 활용할 수 있으면 얼마나 좋을까... 하고 생각하고 계실 것 입니다. 현재 Fragment API 가 오직 안드로이드 3.0 에서만 지원되고 있다는 사실을 생각해보면, 이는 참으로 안타까운 일 입니다.

 이런 문제를 해결하기 위하여, 안드로이드 개발팀은 이 포스트에서 이야기한 Fragment API 를 기존의 안드로이드 플랫폼에서도 활용 할 수 있도록 Static 라이브러리 형태로 제공할 예정입니다. 현재 계획으로는 1.6 이후의 모든 플랫폼을 지원하고자 하며, 사실 여러분이 이 포스트에서 사용된 코드는 바로 그러한 Static 라이브러리를 활용하여 작성되었습니다. 눈치가 빠른 분들이라면 스크린샷으로도 확인하실 수 있었겠지요. 현재, 우리의 목표는 Static 라이브러리에서 제공하는 API 와 안드로이드 3.0 플랫폼에서 제공하는 API 를 최대한 동일하게 만드는 것이며, 여러분이 지금 즉시 Fragment API 를 활용하고, 향후 안드로이드 3.0 을 사용하실 때도, 기존 어플리케이션 코드를 거의 변경하지 않고 적용할 수 있도록 만들고자 합니다.

 Fragment API 를 위한 Static 라이브러리를 제공하는 날짜가 아직 확정되지는 않았지만, 생각보다 이른 시간내에 공개할 것이라고는 말씀드릴 수 있습니다. 그 사이에 여러분은 안드로이드 3.0 SDK 를 이용하여 Fragment API 를 어떤식으로 활용할 수 있는지 테스트해보실 수 있으며, 그 노력은 결코 헛되지 않을 것 입니다.

+ Recent posts