반응형

Android 3.0(API 11)에서 Fragment가 처음 소개되었습니다. 재사용 가능한 유저 인터페이스를 생성하기 위한 새로운 컴포넌트라고 볼 수 있습니다. Fragment를 사용하면 코드를 재사용하여 동시에 여러 크기의 기기 화면 크기를 지원해 줄 수 있습니다. 예를 들어 태블릿과 폰을 위한 코드를 각각 따로 작성해줄 필요가 없습니다. 


Fragment는 자신만의 레이아웃을 이용하여 화면에 유저 인터페이스를 보여줄 수 있으며, 자신만의 라이프 사이클 콜백 함수들을 가지고 자신을 포함한 Activity로부터 독립적으로 동작합니다. 하지만 혼자 독자적으로 동작할 수 없으며, Fragment 라이프 사이클은 자신이 포함된 Activity 라이프 사이클의 영향을 받습니다. 예를 들어 Activity가 pause되면 Activity 내에 속한 모든 Fragment가 pause됩니다.  유저 인터페이스 없이 동작하는 Fragment도 구현가능합니다. Fragment는 유저 인터페이스를 정의한 자신만의 뷰객체를 가지고 있지만 화면에 보여주기 위해서는 액티비티가 필요하기 때문에 Fragment를 Activity의 ViewGroup에 추가해주어야 합니다.


fragment가 위에서 언급한 것처럼 자신이 속한 Activity로부터 독립적으로 동작하지만, 디자인할 때 모듈된 재사용가능한 컴포넌트가 되도록 해주어야 하며 fragment간에 직접적인 조작을 피해야 합니다.  이렇게 해줌으로써 하나의 fragment를 다수의 Activity에 포함시켜 사용할 수 있습니다.



fragment는 자신이 속한 activity와만 통신을 해야만 하며,  항상 자신이 속한 activity를 통해서 다른 fragment나 activity와 통신해야 합니다.

fragment와 activity가 통신할 수 있는 방법에는 3가지가 있습니다.

1. Bundle -  activity는 fragment를 생성 후, 데이터를 넣은 bundle을 전달할 수 있습니다.  fragment는 onActivityCreated메소드에서 bundle을 받게 됩니다.

2. Method - activity는 fragment의 메소드를 호출 할 수 있습니다.

3. Listener - fragment는 interface를 사용하여  activity에서 리스너 이벤트를 발생시킬수 있습니다.



하나의 Activity에 속하는 다수의 Fragment가 동시에 동작가능합니다.  Activity는 한 화면을 다 차지해야 했는데 Fragment는 화면 일부만 점유해도 동작가능하기 때문에 화면을 분할하여 부분마다 독립적인 유저 인터페이스를 구현 할수 있습니다.  Activity의 일부분을 차지하는 각각 Fragment가  자신만의 유저 인터페이스를 보여주고, 사용자의 입력에 반응합니다. 


기기의 화면 크기나 화면 방향등에 따라 달라지는 Activity의 레이아웃에 맞추어 Fragment의 레이아웃을 재배열하거나 결합할 수 있습니다. 예를 들어 태블릿에서는 Activity에 두개의 Fragment 추가한 유저 인터페이스를 보여주지만 폰에서는 Activity 당 하나의 Fragment 추가한 유저 인터페이스를 보여줍니다.


하나의 앱에서 태블릿과 폰에 대해 각각 다른 레이아웃을 제공해줄 수 있습니다. 타블렛의 경우에는 하나의 Activity A에 글목록을 위한 Fragment A와  글내용을 위한 Fragment B를 한번에 보여 줄 수 있지만(dual-pane layout), 폰의 경우에는 하나의 Activity A에 글목록을 위한 Fragment A를 보여줄 공간밖에 없습니다(single-pane layout).  폰의 경우에는 목록에서 글 선택시 다른 Activity B를 시작하여 글내용을 위한 Fragment B를 보여줘야 합니다. 여기서 주목할 점은 타블렛과 폰에 대한 레이아웃이 다르지만 같은 Fragment를 재사용하고 있다는 것입니다.


기기의 방향에 따라서도 보여줄 수 있는 내용이 달라질 수 있습니다.  Portrait의 경우에는 글목록과 글내용을 위한 Fragment를 별도의 Activity에 보여줘야 하지만


Landscape의 경우에는 글목록과 글내용을 위한 Fragment를 하나의 Activity에 보여줄 수 있습니다. 여기서도 Portrait와 Landscape에 대한 레이아웃이 다르지만 같은 Fragment를 재사용하고 있습니다.


프래그먼트는 Context 클래스를 상속받지 않기때문에 부모 액티비티를 얻기위해 getActivity()를 사용해야 합니다.


Activity가 실행되는 중에 Fragment를 Activity에 추가/삭제/변경하는게 가능하며 기존 Fragment를 조합하여 레이아웃마다 다른 모습을 보여줄 수 있습니다. 이 때 레이아웃마다 Fragment의 코드를 다시 작성하지 않고, 한번 작성한 Fragment 코드를 재사용합니다.



Fragment의 라이프 사이클

fragment 생성시 1~5번은 순서대로 진행됩니다.


1. onAttach() 

Fragment가 Activity에 추가될 때 한번 호출됩니다. 아직 Activity와 Fragment의 초기화가 끝나지 않은 시점입니다.

Fragment에서 Activity에 대한 참조를 얻기 위해 사용되어 집니다. API 23 이상에서는 onAttach(Context context)와 onAttach(Activity activity) 둘다 호출되지만 그외 버전에서는 onAttach(Activity activity)만 호출됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    @TargetApi(23)
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Activity a;
        if (context instanceof Activity) {
            a = (Activity) context;
            this.listener = (CreateLayerListener) a;
        }
    }
 
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        if (Build.VERSION.SDK_INT < 23) {
            this.listener = (CreateLayerListener) activity;
        }
    }
cs

 

2. onCreate(Bundle)  

Fragment가 생성된 시점에 호출됩니다.  Activity의 onCreate메소드가 아직 완료된 시점이 아니라서 유저 인터페이스와 관련있는 것을 제외한 Fragment에서 사용되는 리소스들이 초기화됩니다.  유저 인터페이스와 관련된 처리는 onActivityCreated 메소드에서 해주어야 합니다. Fragment가 paused 또는 stop되었다가 다시 resume되었을 때 유지하고 싶은 Fragment의 컴포넌트들를 여기서 초기화 해주어야 합니다.


setRetainInstance(ture)를 호출하여 fragment의 인스턴스를 유지하도록 할 수 있습니다. 이 때 다음 세가지가 기존과 달라집니다.

  • Activity가 재생성되어도 Fragment가 유지되기 때문에 onCreate는 호출되지 않습니다.  
  • onDestroy()는 호출되지 않지만, Activity로부터 fragment가 detach될때 onDetach()는 호출됩니다.
  • onAttach(Activity)와 onActivityCreated(Bundle)는 호출됩니다.


3. onCreateView(LayoutInflater, ViewGroup, Bundle) 

Fragment의 유저 인터페이스가 화면에 그려지는 시점에 호출됩니다. XML 레이아웃을 inflate하여 Fragment를 위한 View를 생성하고 Fragment 레이아웃의 root에 해당되는 View를 Activity에게 리턴해야 합니다. inflate란 XML 레이아웃에 정의된 뷰나 레이아웃을 읽어서 메모리상의 view 객체를 생성해주는 겁니다. 여기서 view를 리턴했다면, view가 release될때 onDestroyView()가 호출됩니다.

유저 인터페이스 없는 Fragment의 경우에는 null을 리턴하면 됩니다. 

1
2
3
4
5
6
7
8
9
10
public class MyFragment extends Fragment {
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup,
                             Bundle savedInstanceState) {
 
        View rootView = inflater.inflate(R.layout.fragment_my, parentViewGroup, false);
        return rootView;
    }
}
cs

4. onActivityCreated(Bundle) 

Activity의 onCreate()를 완료되고 fragment의 View 생성이 완료했을때 호출됩니다. Activity와 Fragment의 View가 모두 생성된 시점이라 findViewById()를 사용하여 View 객체에 접근하는게 가능합니다.- inflate된 레이아웃 내에서 R.java에 할당되어있는 주어진 ID를 가지고 view를 찾습니다.

또한 Context객체를 요구하는 객체를 초기화 할 수 있습니다.

5. onViewStateRestored(Bundle) 

view의 저장되었던 상태가 복구되었음을 fragment에게 알려줍니다. 저장되었던 상태를 바탕으로 초기화시 사용됩니다. 예를 들어 fragment의 유저 인터페이스에 있는 체크박스가 선택 상태를 복구하는데 사용할 수 있습니다.


6. onStart() 

Fragment가 사용자에게 보여질때 호출되며 아직 사용자와 상호작용은 불가능한 상태입니다. (fragment가 속한 activity가 start된거랑 관련있습니다.)


7. onResume() 

fragment가 사용자에게 보여지고 실행 중일때 호출되며 사용자와 상호작용할 수 있는 상태입니다. (fragment가 속한 activity가 resume된거랑 관련있습니다.)


보통 onResume에서 자원을 할당하고 onPause에서 자원을 해제해줍니다.



fragment가 더이상 사용되지 않을때 호출됩니다. 

1. onPause() 

Activity가 pause되어 fragment가 더이상 사용자와 상호작용하지 않을 때이다.


2.onStop()

Activity가 stop되었거나 fragment의 operation이 activity에 의해 수정되었을 경우로 fragment가 더이상 사용자에게 보여지지 않을 때입니다.



fragment가 destroy될때 다음 순서대로 호출됩니다.

1.onDestroyView()

fragment가 화면에서 안보이고 view의 현재상태가 저장된후 호출됩니다. 이 메소드가 호출된 후에 fragment의 모든 View가 destroy됩니다. onCreateView에서 초기화했던 UI들을 여기서 해제하면 됩니다.


2.onDestroy()

fragment를 더 이상 사용하지 않을때 호출되며 activity와 연결이 끊어진 상태는 아니지만 fragment는 동작을 하지 않는 상태입니다.

시스템에서 onDestroy가 항상 호출되는 것을 보장해주지 않습니다.


3.onDetach()

fragment가 activity와의 연결이 끊어지기 전에 호출되며 fragment의 view hierarchy가 더 이상 존재하지 않게 됩니다. 

부모 activity가 full 라이프사이클을 완료하지 않고 종료되었다면 onDetach()는 호출되지 않습니다.



Fragment 생성

Activity에 Fragment를 추가하는 방법에는 두가지가 있습니다.  공통적으로 Fragment 클래스를 상속받아 Fragment를 생성해야 합니다.


1. 정적인 방법

Activity XML 레이아웃  파일에 <fragment>태그 추가하여 Fragment를 정의합니다. Fragment의 레이아웃은 Activity에서 inflate되어 ViewGroup이 됩니다.

--> onInflate 메소드 호출시 Fragment 라이프 사이클이 시작됩니다.

-->처음 화면에 출력될 프래그먼트 지정이나 화면에 동적변화 없을경우 사용됩니다.

-->android:class 속성에 프래그먼트 클래스를 적어줘야합니다.

-->실행중에 fragment를 추가/삭제/변경할 수 없습니다.


2. 동적인 방법 - Activity 실행중에 Fragment를 추가/삭제/변경

Activity XML 레이아웃 파일에 <FrameLayout>태그 추가 후, 프로그래밍적으로 FragmentManager를 이용하여 ViewGroup에 fragment를 동적으로 추가합니다.

-->onAttach 콜백 메소드에서 Fragment 라이프 사이클이 시작됩니다.



기기의 화면 방향이 Landscape 일 경우 MainActivity의 레이아웃인 res/layout-land/activity_main.xml을 살펴보면 TitlesFragment는 정적으로 삽입되기 때문에 해당자리에 <fragment>태그가 사용되고 있고 동적으로 교체되는 DetailsFragment를 위한 자리는 <FrameLayout> 태그가 사용되고 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:baselineAligned="false"
              android:orientation="horizontal" >
 
    <fragment
        android:id="@+id/titles"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1"
        class="com.tistory.webnautes.fragment_example.TitlesFragment" />
 
    <FrameLayout
        android:id="@+id/details"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="?android:attr/detailsElementBackground" />
 
</LinearLayout>
cs


레이아웃에 Fragment이 추가되었으면, android.app.Fragment 클래스를 상속한 클래스를 만들어야 합니다.

1
2
3
4
5
6
import android.app.Fragment;
...............................
 
public class MyFragment extends Fragment {
.........................................
}
cs


Fragment를 위한 레이아웃을 제공하기 위해서는 Fragment 클래스의 onCreateView() 콜백 메소드를 오버라이드해서 구현해주어야 합니다. onCreateView() 콜백 메소드는 Fragment를 위한 유저 인터페이스를 화면에 그리는 시점에 호출됩니다. 이 콜백 메소드에서 Fragment 레이아웃의 root에 해당되는 ViewGroup를 리턴해주어야 합니다. 단 ListFragment의 경우에는 onCreateView() 콜백 메소드의 디폴트 리턴이 Listview라서 이 콜백 메소드를 구현해 줄 필요가 없습니다.

1
2
3
4
5
6
7
8
9
10
public class MyFragment extends Fragment {
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup,
                             Bundle savedInstanceState) {
 
        View rootView = inflater.inflate(R.layout.fragment_my, parentViewGroup, false);
        return rootView;
    }
}
cs


LayoutInflater

XML 레이아웃 파일을  대응하는 ViewGroup와 View로 변환해줍니다. layout.inflate()를 호출하여 수행합니다.


inflate()메소드는 다음 3가지 파라메터를 가집니다.

R.layout에 있는 XML 레이아웃의 ID

Fragment의 View가 추가될 부모 Activity의 ViewGroup

false - XML 레이아웃 파일로부터 Fragment의 View가 inflate되어 부모의 ViewGroup에 추가됩니다.


Activty 내에서 Fragment의 유저 인터페이스가 보여지게 하기 위해선 Fragment를 Activty에 추가해주어야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyActivity extends Activity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
 
        if (savedInstanceState == null) {
            getFragmentManager()
                    .beginTransaction()
                    .add(R.id.fragmentParentViewGroup, new MyFragment())
                    .commit();
        }
    }
 
}
cs


새로운 Activity가 생성되고 Activty에 Fragment가 다시 연결될 때 안드로이드 OS는 Activity에 추가된 fragment를 기억합니다. onCreate메소드에서 savedInstanceState가 null이면, activity를 위해 fragment가 처음 생성되어 추가된 것입니다.



Fragment 예제

MainActivty

MainActivity에서 레이아웃 XML 파일을 로드시 기기 방향에 따라 자동으로 res/layout/activity_main.xml (portrait) 또는 res/layout-land/activity_main.xml  (landscape)가 선택됩니다.  참고로 res/layout-port라고 따로 portrait를 위한 예약된 폴더가 있지만 디폴트로 portrait를 많이 사용하므로 res/layout 폴더에 portrait를 위한 레이아웃을 넣어놓으면 됩니다.


앱이 start 또는 resume되거나 기기의 방향이 전환될 떄,   MainActivty의 onCreate() 콜백 메소드가 호출됩니다.  이 떄, setContentView 메소드를 이용하여 Activity에서 보여줄 레이아웃을 지정해주는데 여기에서는 activity_main.xml 파일안에 Fragment도 포함되어 있습니다.  Activity 레이아웃 안에 <fragment> 태그로 지정한 곳에 fragment가 삽입되어 화면에 보여지게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
public class MainActivity extends Activity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        //orientation에 따라
        // res/layout/activity_main.xml (portrait) 또는
        // res/layout-land/activity_main.xml (landscape)가 시스템에서 자동으로 선택된다.
        setContentView(R.layout.activity_main);
    }
}
cs


Portrait를 위한  res/layout/activity_main.xml 파일입니다.  <fragment> 태그의 class 속성에 TitlesFragment 클래스를 명시하고 있습니다. TitlesFragment 클래스의 onCreateView() 콜백 메소드에서 리턴한 View가 이 자리에 삽입됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="match_parent" >
 
    <fragment
        android:id="@+id/titles"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.tistory.webnautes.fragment_example.TitlesFragment" />
 
</FrameLayout>
cs


Landscape의 경우 시스템에 의해서 자동으로 res/layout-land/activity_main.xml 레이아웃 파일이 읽혀집니다. TitlesFragment의 경우에는 레이아웃 파일이 Activity에서 로드되자마자 바로 화면에 보여지게 되지만, 오른쪽에 위치하게 되는 DetailsFragment의 자리는 비워둡니다. 글목록에서 사용자가 선택하여 새로운 Fragment가 생성될 때 비로소 화면에 DetailsFragment가 보여지게 됩니다. 이렇게 동적으로 생성한 Fragment를 Activity의 레이아웃에 추가하기 위해서는 Fragment가 삽입될 위치에 <fragment>태그 대신에 <FrameLayout>태그를 사용해야 합니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:baselineAligned="false"
              android:orientation="horizontal" >
 
    <fragment
        android:id="@+id/titles"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1"
        class="com.tistory.webnautes.fragment_example.TitlesFragment" />
 
    <FrameLayout
        android:id="@+id/details"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="?android:attr/detailsElementBackground" />
 
</LinearLayout>
cs



TitlesFragment
이제 화면에 TitleFragment가 보여지게 되었습니다. 그 결과 화면에 글목록이 보이게됩니다.

기기 방향이 Portrait일때에는 Activity에 하나의 Fragment를 보여줄 공간밖에 없어서 Activity에 TitlesFragment만 보여줍니다.  하지만 Landscape일 경우에는 글목록에서 디폴트로 선택되는 첫번째 글을 위한 DetailsFragment를 같이 보여줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
 
        .................................................................
 
        if (mDualPane) {//landscape
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            showDetails(mCurCheckPosition);
        } else {//portrait
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            getListView().setItemChecked(mCurCheckPosition, true);
        }
    }
cs


사용자가 TitlesFragment가 보여주는 글목록 중 하나를 선택하게 되면 onListItemClick 콜백 메소드가 호출되는데  내부에서  showDetails 메소드를 호출합니다.
1
2
3
4
5
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
 
        showDetails(position);
    }

cs


showDetails 메소드에서 기기의 방향에 따라 글내용을 위한 DetailsFragment를 보여주는 방법이 달라지는데 Portrait일 경우에는 DetailsActivity Activty를 시작하여 DetailsFragment를 보여주고, Landsacpe일 경우에는 DetailsFragment Fragment를 새로 생성하여 기존거와 교체합니다.  이런 교체과정으로 Activity 레이아웃에서 <FrameLayout>태그를 사용한 곳의 유저 인터페이스가 변하게 됩니다. 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
    //기기의 방향에 따라 글내용을 위한 DetailsFragment 보여주는 방법 결정
    void showDetails(int index) {
        mCurCheckPosition = index;
 
        if (mDualPane) {
            //기기의 방향이 Landscape라면 dual-pane가능
            //글목록 Fragment와 글내용 Fragment를 하나의 Activity에서 보여줌
 
            //리스트에서 선택한 아이템 하이라이트
            getListView().setItemChecked(index, true);
 
            DetailsFragment details = (DetailsFragment) getFragmentManager()
                    .findFragmentById(R.id.details);
            //DetailsFragment를 처음 생성하는 경우이거나 글목록에서 기존과 다른 글이 선택되었다면
            if (details == null || details.getShownIndex() != index) {
 
                //새로운 DetailsFragment 인스턴스를 생성
                details = DetailsFragment.newInstance(index);
 
                //transaction을 실행하여 기존 fragment를 교체함
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                ft.replace(R.id.details, details);
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }
 
        } else {
            //기기의 방향이 portrait인경우 single-pane이다.
            //다른 Activity를 시작하여 글목록에서 선택된 글의 내용을 위한 DetailsFragment를 보여준다.
 
            //새로운 intent를 생성
            Intent intent = new Intent();
 
            //Activty context와 시작할 Activity의 클래스를 지정한다.
            intent.setClass(getActivity(), DetailsActivity.class);
 
            //글목록에서 현재 선택된 위치를 전달한다.
            intent.putExtra("index", index);
 
            //DetailsActivity 시작
            startActivity(intent);
        }
    }

cs


Activity 실행 중에 fragment를 동적으로 추가/제거/교체하는 transaction 을 수행하기 위해서는 FragmentManager를 이용하여 FragmentTransaction을 생성해야 합니다. FragmentTransaction은 fragment를 추가/제거/교체하는 API를 제공합니다. 또한 FragmentManager의 findFragmentById 또는 findFragmentByTag 메소드를 사용하면 Activity의 레이아웃에서 Fragment를 찾을 수 있습니다. 

Activity가 Fragment를 동적으로 추가/제거/교체하려면, Activity의 onCreate()메소드에서 초기 Fragment를  Activity에 추가해주어야 합니다.  MainActivity에서는 사용하는 Landscape를 위한 레이아웃에  fragment 추가를 대비해 <FrameLayout>태그를 사용하고 있습니다. 그리고 글목록에서 글을 선택하게되면 TitleFragment에서 Activity 레이아웃의 <FrameLayout>태그를 사용한 곳에 삽입된 Fragment를 교체합니다. getFragmentManager() 메소드를 사용하여 FragmentManager를 획득한 후,  FragmentManager의 beginTransaction() 메소드를 호출하여 FragmentTransaction를 생성합니다.  그리고나서 FragmentTransaction의 replace메소드를 사용하여 Fragment를 교체합니다. 마지막으로 commit() 메소드를 호출해야 실제로 Fragment에 관한 수행이 이루어집니다.
1
2
3
4
5
6
7
8
                //새로운 DetailsFragment 인스턴스를 생성
                details = DetailsFragment.newInstance(index);
 
                //transaction을 실행하여 기존 fragment를 교체함
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                ft.replace(R.id.details, details);
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
cs

DetailsActivity에서는 onCreate 콜백 메소드에서 FragmentTransaction의 add메소드를 사용하여 ID로 지정한 DetailsActivity의 ViewGroup에 Fragment 인스턴스를 추가합니다. 
1
2
3
4
5
6
7
8
9
10
            //DetailsFragment를 생성한다.
            DetailsFragment details = new DetailsFragment();
 
            //현재 글목록에서 선택된 항목의 인덱스를 가져와 설정한다.
            //DetailsActivity 시작시 인덱스가 intent로 전달됨
            details.setArguments(getIntent().getExtras());
 
            //현재 Activty에 DetailsFragment 추가
            getFragmentManager().beginTransaction()
                    .add(android.R.id.content, details).commit();
cs

사용자가 Back 버튼을 눌렀을 때, 이전 화면으로 돌아가기를 원하는데 Fragment를 Back Stack에 추가해줌으로써 transaction을 거꾸로 되돌려 취소할 수 있습니다.  이를 위해 FragmentTransaction의 commit()메소드를 호출하기 전에 addToBackStack()메소드를 호출하여 fragment를 Back Stack에 추가해주어야 합니다. 

기기의 방향이 전환되어도 TitlesFragment는 항상 생성되므로 여기서 글목록에서 선택된 항목 인덱스 정보를 관리합니다. TitlesFragment가 destroy되기전 호출되는 onSaveInstanceState 콜백 메소드에서 글목록에서 현재 선택된 항목의 인덱스를 bundle로 저장하고 
1
2
3
4
5
6
7
8
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

 
        outState.putInt("curChoice", mCurCheckPosition);
    }
cs


Fragment가 다시 생성되었을 때, onActivityCreated 콜백 메소드에서 항목 위치를 복구합니다.

1
2
3
4
5
6
7
8
9
   @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
 
        ......................................................................
        if (savedInstanceState != null) { //TitlesFragment가 처음 생성된게 아니라면
            //글목록에서 체크했던 항목 위치를 복구
            mCurCheckPosition = savedInstanceState.getInt("curChoice"0);
        }
cs



전체 소스코드입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.tistory.webnautes.fragment_example;
 
import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
 
//글목록을 보여주는 fragment
//글 선택시 기기의 방향에 따라 글내용을 보여주는 방법이 달라짐
public class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;
 
    @Override
    //Activity의 onCreate() 메소드 완료시 호출됨
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
 
        //Shakespeare 클래스에 있는 글제목으로 리스트를 보여줌
        setListAdapter(new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_activated_1,
                Shakespeare.TITLES));
 
 
        View detailsFrame = getActivity().findViewById(R.id.details);
 
        mDualPane = detailsFrame != null  //기기 방향이 Landscape라서 DetailsFragment를 위한 레이아웃 구성요소가 포함되어 있다면 DetailsFragment가 생성된다.
                && detailsFrame.getVisibility() == View.VISIBLE; //DetailsFragment가 Activity에 추가되었다면 화면에 보여짐
 
 
        if (savedInstanceState != null) { //TitlesFragment가 처음 생성된게 아니라면
            //글목록에서 체크했던 항목 위치를 복구
            mCurCheckPosition = savedInstanceState.getInt("curChoice"0);
        }
 
        if (mDualPane) {
            //dual-panel 모드라면
            //리스트에서 아이템 하나씩 선택가능하도록 하고
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            //리스트에서 선택된 아이템을 하이라이트 및 DetailsFragment에 글내용을 표시하여 같이 보여줌.
            showDetails(mCurCheckPosition);
        } else {//uni-pane 모드이라면
            //리스트에서 아이템 하나씩 선택가능하도록 하고
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            //리스트에서 선택된 아이템을 하이라이트
            getListView().setItemChecked(mCurCheckPosition, true);
        }
    }
 
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Toast.makeText(getActivity(), "onSaveInstanceState",
                Toast.LENGTH_LONG).show();
 
        outState.putInt("curChoice", mCurCheckPosition);
    }
 
    //사용자가 리스트에서 글 선택시, showDetails 호출하여 기기방향에 따라 글내용을 위한 DetailsFragment 보여주는 방법 결정
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
 
        showDetails(position);
    }
 
    
    //기기의 방향에 따라 글내용을 위한 DetailsFragment 보여주는 방법 결정
    void showDetails(int index) {
        mCurCheckPosition = index;
 
        if (mDualPane) {
            //기기의 방향이 Landscape라면 dual-pane가능
            //글목록 Fragment와 글내용 Fragment를 하나의 Activity에서 보여줌
            
            //리스트에서 선택한 아이템 하이라이트
            getListView().setItemChecked(index, true);
            
            DetailsFragment details = (DetailsFragment) getFragmentManager()
                    .findFragmentById(R.id.details);
            //DetailsFragment를 처음 생성하는 경우이거나 글목록에서 기존과 다른 글이 선택되었다면
            if (details == null || details.getShownIndex() != index) {
 
                //새로운 DetailsFragment 인스턴스를 생성
                details = DetailsFragment.newInstance(index);
 
                //transaction을 실행하여 기존 fragment를 교체함
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                ft.replace(R.id.details, details);
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }
 
        } else {
            //기기의 방향이 portrait인경우 single-pane이다.
            //다른 Activity를 시작하여 글목록에서 선택된 글의 내용을 위한 DetailsFragment를 보여준다.
            
            //새로운 intent를 생성
            Intent intent = new Intent();
 
            //Activty context와 시작할 Activity의 클래스를 지정한다.
            intent.setClass(getActivity(), DetailsActivity.class);
 
            //글목록에서 현재 선택된 위치를 전달한다.
            intent.putExtra("index", index);
 
            //DetailsActivity 시작
            startActivity(intent);
        }
    }
}
 
cs



DetailsActivity

기기 방향이 Portrait인 경우 Activity에 두 개의 Fragment를 보여줄 공간이 없어서 사용됩니다.  시작된 후, DetailsFragment를 추가합니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.tistory.webnautes.fragment_example;
 
import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
 
//기기방향이 Portrait일 때 사용자가 글목록에서 선택시
//DetailsFragment를 보여주기 위한것이다.
public class DetailsActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            //화면이 landscape orientation일 때 MainActivity에서 한꺼번에 두개의 프래그먼트를 보여주므로
            //이 액티비티가 필요없다. 따라서 종료시킨다.
            finish();
            return;
        }
 
        if (savedInstanceState == null) {//새로 생성하는 거라면
 
            //DetailsFragment를 생성한다.
            DetailsFragment details = new DetailsFragment();
 
            //현재 글목록에서 선택된 항목의 인덱스를 가져와 설정한다.
            //DetailsActivity 시작시 인덱스가 intent로 전달됨
            details.setArguments(getIntent().getExtras());
 
            //현재 Activity에 DetailsFragment 추가
            getFragmentManager().beginTransaction()
                    .add(android.R.id.content, details).commit();
        }
    }
}
 
cs



DetailsFragment

글내용을 화면에 보여주기 위한 Fragment입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.tistory.webnautes.fragment_example;
 
import android.app.Fragment;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
 
public class DetailsFragment extends Fragment {
 
    //새로운 DetailsFragment 인스턴스를 생성
    public static DetailsFragment newInstance(int index) {
        DetailsFragment f = new DetailsFragment();
 
        //글목록에서 선택된 항목에 대응되는 글내용을 보여줌
        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) {
 
        ScrollView scroller = new ScrollView(getActivity()); //ScrollView 생성
        TextView text = new TextView(getActivity()); //TextView 생성
        int padding = (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 4, getActivity()
                        .getResources().getDisplayMetrics());
        text.setPadding(padding, padding, padding, padding);
        scroller.addView(text); //ScrollView에 TextView추가
        
        text.setText(Shakespeare.DIALOGUE[getShownIndex()]); //TextView에 선택된 글내용을 보여줌
        
        return scroller; //DetailsFragment의 root View인 ScrollView 리턴
    }
}
 
 
cs



AndroidManifest.xml

두 개의 Activity를 정의해주어야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.tistory.webnautes.fragment_example">
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
 
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
 
        <activity android:name=".DetailsActivity" />
    </application>
 
</manifest>
cs








문제 발생시 지나치지 마시고 댓글 남겨주시면 가능한 빨리 답장드립니다.


제가 쓴 책도 한번 검토해보세요 ^^

+ Recent posts