What is Purpose?
Fragment는 안드로이드 3.0(허니컴)에서 공개된 UI 관련 클래스입니다. Fragment를 활용하면 한 Activity에서 여러 화면을 전환하며 UI를 구성하는 것이 가능합니다. Fragment가 공개되기 이전에는 이러한 화면을 구성하려면 Relative Layout 과 같은 View Group을 이용하여 View 들을 곂쳐놓고 Visible 속성을 이용하여 숨기거나 나타내는 방식을 주로 이용하였습니다. 이러한 구성은 서로 다른 형식의 화면을 한 Activity에서 모두 구성해야 했기 때문에 상당히 코드가 길어지고 복잡해지는 단점이 있었습니다.
허니컴에서 처음 공개된 클래스인 점을 생각해보면, 처음의 Fragment는 이러한 단점의 극복이라는 차원보다는 태블릿에서의 유기적인 UI 구성을 위해 사용되었다 생각합니다. 위의 이미지에서 볼 수 있듯이 스마트폰 환경에서는 리스트뷰 형식에서 아이템 클릭시 넘어가는 화면 구성이 태블릿에서는 한 화면에서 리스트뷰와 아이템에 대한 내용이 표시되는 것을 볼 수 있습니다. 스마트폰 환경에서는 이와 같이 리스트뷰를 활용한 UI가 보편적이었습니다. Fragment가 공개되기 이전에는 이러한 UI 구성을 하기 위해서는 주로 두개의 Activity를 생성하여 아이템 클릭시 Activity를 전환하는 방식을 주로 이용하였습니다. 때문에 태블릿에서 이러한 UI를 그대로 이용한다면 큰 화면에서 상당한 공간 낭비가 생김니다. 두개의 Activity를 한 화면에 구성하는 것이 불가능하기 때문에 위 이미지처럼 구현하기 위해서는 상당히 복잡하게 코드를 구성하여야 가능했습니다. 이러한 단점을 극복하기위해 Fragment가 처음 등장했다고 생각하시면 될 것 같습니다. 안드로이드 디벨로퍼 사이트의 Fragment 소개 문서(http://developer.android.com/guide/components/fragments.html)를 보면 디자인 목적이 대형 화면에서 유기적인 UI를 구성하기 위해 만들어졌다고 한 것을 보면 다양한 크기의 화면을 지원하기 위해 Fragment 라는 것을 만들었다고 한 것을 보면 등장 배경의 이해에 도움이 될 것같습니다.
하지만 ICS 이후 Fragment는 이러한 용도보다는 주로 스마트폰의 UI 구성에 이용되기 시작했습니다. ActionBar와 같은 UI들이 태블릿 뿐만아닌 스마트폰의 주요 UI 구성 요소로 변화해감에 따라 이에 맞춰 Fragment가 이용되기 시작했습니다. 대표적으로 기존의 TabActivity를 ActionBar Tab UI로 구성하게 되면서 다수의 Activity를 이용한 탭 구성이 아닌 Fragment를 이용한 탭 구성이 가능해졌습니다.
Fragment의 사용 목적을 정리하자면 복잡한 UI 구성을 유기적이고 간단하게 구성 할 수 있게 하기 위함이라고 할 수 있을 것 같습니다.
Fragment의 구성
위의 이미지는 Fragment의 생명주기입니다. Activity의 생명주기와 약간 다르지만 전체적인 흐름은 비슷하다고 느낄 수 있습니다. 이 것을 보면 마치 Fragment는 Activity 안의 Activity 라는 생각이 듭니다. 이러한 생명주기를 Activity에서 관리해 주면서 유기적인 UI 구성을 한다 생각하시면 됩니다. 각 주기 요소들의 동작 시점은 Activity와 크게 다르지 않기 때문에 안드로이드 디벨로퍼의 문서를 참조하시면 되기 때문에 생략하겠습니다.
이러한 Fragment를 Activity에서 Fragment Manager를 통해 관리합니다. 이전에 Layout을 곂치는 방식과 같이 단순히 화면 전환을 하는 것 뿐만 아니라, Activity와 마찬가지로 스택 형식으로 Fragment를 구성하여 이전의 Fragment로 돌아가게 하는 것도 가능합니다.
초기 Fragment의 등장 목적과 실제로 이용되는 목적이 차이가 있기 때문에 Fragment를 이해하기 위해서는 클래스에 대한 설명보다는 다양한 예제를 통해 Fragment를 접해보시는 것이 훨씬 도움이 될 것입니다.
Fragment의 이용
ICS 이후 안드로이드의 UI가 아이폰 수준으로 올라갔다고 생각합니다. 이러한 주요 요인 중 하나가 Fragment이고, 이와 관련해서 안드로이드 디벨로퍼 사이트에서 다양한 UI 예제들을 제공하고있습니다. 위의 링크로 가시면 몇가지 예제를 볼 수 있습니다. 이 중 가장 많이 이용하는 UI인 Navigation Drawer 에서 보면 각 화면의 전환이 Fragment로 구성되어 있습니다. 이러한 예제 중 몇 가지를 분석하면서 Fragment가 어떻게 이용되는지 살펴보겠습니다.
1. Navigation Drawer
http://developer.android.com/training/implementing-navigation/nav-drawer.html
Navigation Drawer 란 Facebook, Google Play 등의 어플리케이션에서 볼 수 있듯이 ActionBar에서 아이콘을 누르면 메뉴가 슬라이딩 되면서 나오는 UI 입니다. Menu Drawer, Sliding Menu 등 다양한 명칭으로 접할 수 있습니다. 위의 링크를 들어가시면 예제를 다운 받으실 수 있습니다.
activity_main.xml
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
... >
<FrameLayout
android:id="@+id/content_frame"
... />
<ListView android:id="@+id/left_drawer"
android:layout_width="240dp"
.../>
</android.support.v4.widget.DrawerLayout>
위의 xml을 보면 DrawerLayout의 밑에 FrameLayout과 ListView로 구성되어 있는 것을 볼 수 있습니다. ListView를 통해서 Drawer의 메뉴를 관리하고 FrameLayout을 통해 Fragment로 화면을 구성합니다.
public class MainActivity extends Activity {
private String[] mPlanetTitles;
private ListView mDrawerList;
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPlanetTitles = getResources().getStringArray(R.array.planets_array);
mDrawerList = (ListView) findViewById(R.id.left_drawer);
// Set the adapter for the list view
mDrawerList.setAdapter(new ArrayAdapter<String>(this,
R.layout.drawer_list_item, mPlanetTitles));
// Set the list's click listener
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
...
}
}
MainActivity 에서 보면 Drawer의 ListView를 통해 각 아이템들 및 클릭 이벤트를 관리하고 있는 것을 볼 수 있습니다. 일반적인 리스트뷰를 사용하는 것과 유사하기 때문에 매우 간편히 사용 할 수 있습니다. 또한 리스트뷰 adapter 를 통해서 리스트의 항목들을 단순 텍스트가 아닌 사용자 정의대로 구현 할 수 있습니다.
private class DrawerItemClickListener implements ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView parent, View view, int position, long id) {
selectItem(position);
}
}
/** Swaps fragments in the main content view */
private void selectItem(int position) {
// Create a new fragment and specify the planet to show based on position
Fragment fragment = new PlanetFragment();
Bundle args = new Bundle();
args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
fragment.setArguments(args);
// Insert the fragment by replacing any existing fragment
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragment)
.commit();
// Highlight the selected item, update the title, and close the drawer
mDrawer.setItemChecked(position, true);
setTitle(mPlanetTitles[position]);
mDrawerLayout.closeDrawer(mDrawer);
}
Drawer의 아이템 클릭 리스너 부분 입니다. 이 부분에서 Fragment를 이용한다는 것을 볼 수 있습니다. FragmentManager 클래스를 통해 Fragment의 콘텐츠를 전환시켜줍니다. 이 예제에서는 한 Fragment만을 이용하여 내용을 전환하지만 beginTransaction() 의 replace(…) 에 다른 Fragment를 매개변수로 사용하면 여러 Fragment 간의 화면 전환이 가능합니다.
예제 소스를 다운받아 보시면 Activity 안에 inner class로 Fragment가 정의되어 있는 것을 볼 수 있습니다. 예제에서는 한 가지의 Fragment만 이용하기 때문에 inner class로 구성하여도 복잡하지 않지만, 다수의 Fragment를 이용 할 때는 이러한 형식으로 하면 상당히 복잡해 질 수 있습니다. 따라서 inner class 보다는 Activity와 별도로 Fragment의 클래스를 만들어주는 것이 좋습니다. Fragment는 화면 구성과 관련된 것이기 때문에 Activity 처럼 manifest에 추가시켜 줄 필요는 없고, Activity와 다른 패키지에 존재하여도 문제가 되지 않습니다. 아래는 Fragment에 대해 적용해 보기 위해 만들었던 어플리케이션의 클래스 구성 중 일부분 입니다.
Drawer와 관련된 추가적인 부분이 더 있지만, 이 글에서는 Fragment 가 활용되는 부분을 보기 위함이었으므로 다른 부분은 생략하겠습니다. 예제 링크를 들어가시면 보다 자세한 설명이 나와있으니 참고해주세요.
2. Card Flip Animation Example
http://developer.android.com/training/animation/cardflip.html
위 링크에 들어가시면 어떠한 예제인지 자세한 설명이 나와있습니다. Fragment의 활용 부분은 위에서 다루었으므로, 이 예제에서는 위에서 다루지 않은 부분에 대해서만 이야기 하겠습니다. card flip 의 다른 부분은 제외하고 아래의 코드만 분석해 보겠습니다.
private void flipCard() {
if (mShowingBack) {
getFragmentManager().popBackStack();
return;
}
mShowingBack = true;
getFragmentManager()
.beginTransaction()
.setCustomAnimations(
R.animator.card_flip_right_in, R.animator.card_flip_right_out,
R.animator.card_flip_left_in, R.animator.card_flip_left_out)
.replace(R.id.container, new CardBackFragment())
.addToBackStack(null)
.commit();
}
이 예제의 전체 코드를 보면 두개의 Fragment가 사용되고 있습니다. 이러한 Fragment를전환 할 때마다 스택으로 쌓아서 Activity와 마찬가지로 Back Key 를 눌렀을 때 이전의 Fragment로 돌아가는 기능이 구현되어 있습니다. 또한 addToBackStack(null) 부분에서 볼 수 있듯이 어떤 Fragment의 이전 스택으로 지정할지 정할 수도 있습니다.
위 코드에서 보면 getFragmentManager().popBackStack() 부분이 있는데, 현재 표시된 Fragment 아래에 스택이 쌓여있을 경우 이전 스택으로 돌아가는 기능을 합니다. mShowingBack 변수는 boolean 형식으로 아래와 같이 정의되어 있습니다.
mShowingBack = (getFragmentManager().getBackStackEntryCount() > 0);
또한 Activity에서 FragmentManager를 이용하여 Fragment의 Stack이 쌓이는 것을 관리 할 수 있습니다. getFragmentManager().getBackStackEntryCount() 함수를 통해 현재 몇개의 스택이 쌓여있는지 확인 할 수 있고, 이 예제에서는 이를 활용하여 Back key 의 이벤트를 구현한 것을 볼 수 있습니다. 이 외에도 Fragment를 관리하는 함수들은 안드로이드 디벨로퍼 사이트의 FragmentManager 레퍼런스를 참고하시면 될 것 같습니다.