본문 바로가기

안드로이드

Fragments으로 개발하기



디자인 철학

Android가 프래그먼트를 처음 도입한 것은 Android 3.0(API 레벨 11)부터이다.

3.0 이상부터 큰화면의 태블릿과 스마트폰이 나오기 시작하면서 하나의 화면을 목표로 했던 Activity로 하기에는 무리가 있다.

한 화면에 두개의 Activity가 실행되게 하려면 Activity 두개를 포함하는 상위의 개념이 존재해야하고, 코드 하나로 3.x 미만과 이상버전을 동시에 지원하는 것이 불가능하기 때문에 결국 코드 하나가 더 추가로 만들어져야 한다.

Fragment는 기본적으로 태블릿과 같은 큰 화면에서 보다 역동적이고 유연한 UI 디자인을 지원하는 것이 목적이다.

개발자가 보기 계층에 복잡한 변경 내용을 관리하지 않아도 그러한 디자인을 사용할 수 있도록 해주고, 한 액티비티의 레이아웃을 여러 프래그먼트로 나누면 런타임에 액티비티의 외관을 수정할 수도 있고 그러한 변경 내용을 해당 액티비티가 관리하는 백 스택에 보존할 수도 있다.

프래그먼트를 디자인할 때에는 각 프래그먼트를 모듈식이며 재사용 가능한 액티비티 구성 요소로 만들어야 한다.

다시 말해, 각 프래그먼트가 나름의 레이아웃을 따로 정의하고 자기만의 수명 주기 콜백으로 자기 나름의 동작을 정의하기 때문에 한 프래그먼트를 여러 액티비티에 포함시킬 수 있다. 그러므로 재사용을 염두에 두고 디자인하며 한 프래그먼트를 또 다른 프래그먼트로부터 직접 조작하는 것은 삼가야 한다. 이것은 특히 모듈식 프래그먼트를 사용하면 프래그먼트 조합을 여러 가지 화면 크기에 맞춰 변경할 수 있도록 해주기 때문에 중요한 요점이다.


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


프래그먼트 생성

프래그먼트를 생성하려면 Fragment의 하위 클래스(또는 이의 기존 하위 클래스)를 생성해야 합니다. 

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

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




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


1. Frament 생성

(This lesson shows how to extend the Fragment class using the Support Libraryso your app remains compatible with devices running system versions as low as Android 1.6.)

*API 버전 11이전에는 Fragment를 사용하기 위해 FragmentActivity를 사용해야하지만 그 이후는 그냥 Activity를 사용하면된다.


- Fragment 클래스를 extend 히여 생성한다. (Activity와 동일)

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.ViewGroup;

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

- 레이아웃을 정의하기 위해서 onCreateView() 메소드를 사용한다.


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);
   
}
}

- 액티비티에서 각 상태별로 호출되는 함수가 callback 되면 모든 fragment의 동일한 함수들도 동시에 호출된다.

- Fragment를 사용하려면 레이아웃 선언시 xml에서 해당 fragment를 사용하면된다.

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

   
<fragment android:name="com.example.android.fragments.HeadlinesFragment"
             
android:id="@+id/headlines_fragment"
             
android:layout_weight="1"
             
android:layout_width="0dp"
             
android:layout_height="match_parent" />

   
<fragment android:name="com.example.android.fragments.ArticleFragment"
             
android:id="@+id/article_fragment"
             
android:layout_weight="2"
             
android:layout_width="0dp"
             
android:layout_height="match_parent" />

</LinearLayout>


2. 유연한 UI 구축

- 레이아웃에 fragment를 추가하는 방법 대신, 런타임에 fragment를 activity에 추가할 수 있다.

- FragmentManager를 사용하여 FragmentTraction을 총해 fragment를 런타임에 컨트롤한다.

- Fragment를 제거하거나 replace하려면 inital fragment를 onCreate() 함수에 추가해야한다.

- 런타임에 Fragment를 사용하려면 fragment가 살수있는 View를 가져야 한다.

- 보통의 레이아웃 대신 FrameLayout을 사용한다.

<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" />


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 컨테이너를 사용했으므로 activity는 해당 fragment를 없애거나 대체가 가능하다.

// 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();


3. 다른 Fragments과 통신

- fragment과 통신은 액티비티를 통해서만 가능하다. (direct한 통신은 불가능)

- fragment에 인터페이스를 정의하고, 그것을 activity 안에서 구현한다.

- 해당 fragment는 구현을 onAttach() 라이프사이클에서 capture한다.

public class HeadlinesFragment extends ListFragment {
   
OnHeadlineSelectedListener mCallback;

   
// Container Activity must implement this interface
   
public interface OnHeadlineSelectedListener {
       
public void onArticleSelected(int position);
   
}

   
@Override
   
public void onAttach(Activity activity) {
       
super.onAttach(activity);
       
       
// This makes sure that the container activity has implemented
       
// the callback interface. If not, it throws an exception
       
try {
            mCallback
= (OnHeadlineSelectedListener) activity;
       
} catch (ClassCastException e) {
           
throw new ClassCastException(activity.toString()
                   
+ " must implement OnHeadlineSelectedListener");
       
}
   
}
   
   
...
}

- fragment는 아래 메소드를 호출한다. 상위 액티비티 이벤트를 전달하는 콜백 인터페이스를 사용한다.


  @Override
   
public void onListItemClick(ListView l, View v, int position, long id) {
       
// Send the event to the host activity
        mCallback
.onArticleSelected(position);
   
}


- fragment로부터 클래스에 정의된 인터페이스를 구현해야 activity 호스트 이벤트 콜백을 받을수있다.


public static class MainActivity extends Activity
       
implements HeadlinesFragment.OnHeadlineSelectedListener{
   
...
   
   
public void onArticleSelected(int position) {
       
// The user selected the headline of an article from the HeadlinesFragment
       
// Do something here to display that article
   
}
}


- 호스트 activity는 findFragmentById()와 조각 인스턴스를 캡쳐하여 fragment 인스턴스를 캡쳐하여 fragment에 메세지를 전달 한후 직접 frgment의 공개 메소드를 호출 할 수 있다.


public static class MainActivity extends Activity
       
implements HeadlinesFragment.OnHeadlineSelectedListener{
   
...

   
public void onArticleSelected(int position) {
       
// The user selected the headline of an article from the HeadlinesFragment
       
// Do something here to display that article

       
ArticleFragment articleFrag = (ArticleFragment)
                getSupportFragmentManager
().findFragmentById(R.id.article_fragment);

       
if (articleFrag != null) {
           
// If article frag is available, we're in two-pane layout...

           
// Call a method in the ArticleFragment to update its content
            articleFrag
.updateArticleView(position);
       
} else {
           
// Otherwise, we're in the one-pane layout and must swap frags...

           
// Create fragment and give it an argument for the selected article
           
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();
       
}
   
}
}








안드로이드 api 가이드 정리

http://developer.android.com/intl/ko/guide/components/fragments.html

http://developer.android.com/intl/ko/training/basics/fragments/communicating.html

참고사이트

http://blog.naver.com/ktw5724/120210978193

http://egloos.zum.com/killins/v/3005780