2013년 2월 6일 수요일

[Android] Fragment example(프레그먼트 예제)

Fragment 예제가 제일 잘 나와 있는 곳은 Android API 소스입니다.

  • App → Fragment 에 들어가 보면 많이 나와있습니다.


사실 Fragment가 만들어지게 된 큰 이유중에 하나가 타블렛에서의 화면 비효율성 때문입니다. 이제는 스마트폰의 크기도 커지면서 LandScape일때와 Portrait일때의 화면 구성도 달라져야 합니다. 예제 중 Fragment의 활용을 가장 잘 보여준다고 생각하는 FragmentLayout 예제를 가지고 Fragment를 설명 하겠습니다.

Portrait XML(res - layout)
<?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:orientation="vertical" >

    <fragment
        android:id="@+id/fragment1"
        android:name="com.example.wawoops_fragment_test.FragmentLayoutActivity$TitlesFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>


LandScape XML(res - layout-land)
<?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:orientation="horizontal" >

    <fragment
        android:id="@+id/fragment1"
        android:name="com.example.wawoops_fragment_test.FragmentLayoutActivity$TitlesFragment"
        android:layout_width="0dip"
        android:layout_height="match_parent" 
        android:layout_weight="1"/>

    <FrameLayout
        android:id="@+id/flLandFragmentLayout"
        android:layout_width="0dip"
        android:layout_height="match_parent" 
        android:layout_weight="1">
    </FrameLayout>

</LinearLayout/>


일단 위와 같이 portrait 화면 일 때와 LandScape 화면일때를 구성해 줍니다. 두 파일명은 같아야 합니다. 저는 activity_fragment_layout.xml로 만들어 줬습니다.

android:name="com.example.wawoops_fragment_test.FragmentLayoutActivity$TitlesFragment" 의 의미는 com.example.wawoops_fragment_test 패키지 안에 FragmentLayoutActivity 클래스 안에 TitlesFragment 라는 Fragment를 상속받은 클래스가 있다는것을 의미 합니다. 이처럼 Fragment Component는 Fragment를 extends 하는 클래스를 만들고 그 경로를 써줘야 됨을 알 수 있습니다.

두 화면의 차이점은 LandScape 화면일때 옆에 화면을 하나 더 구성하기 위해 FrameLayout을 추가해 주었습니다.

간단하게 프로세스를 설명 드리자면 Activity 클래스에서 setcontentView(activity_fragment_layout); 와 같이 layout을 호출하면 layout안에 fragment 태그안에서 java 파일의 TitlesFragment 를 호출 하게 됩니다.

FragmentLayoutActivity$TitlesFragment
public static class TitlesFragment extends ListFragment{
  boolean mLandPane;
  int mCurCheckPosition = 0;
  @Override
  public void onActivityCreated(Bundle savedInstanceState) {
   // TODO Auto-generated method stub
   super.onActivityCreated(savedInstanceState);
   Log.v(TAG, "onActivityCreated");
   
   setListAdapter(new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));
   
   View detailsFrame = getActivity().findViewById(R.id.flLandFragmentLayout);
   mLandPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
   
   if (savedInstanceState != null) {
    mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
   }
   
   if (mLandPane) {
    showDetails(mCurCheckPosition);
   }
  }
  
  @Override
  public void onSaveInstanceState(Bundle outState) {
   // TODO Auto-generated method stub
   super.onSaveInstanceState(outState);
   Log.e(TAG, "onSaveInstanceState");
   outState.putInt("curChoice", mCurCheckPosition);
  }

  @Override
  public void onListItemClick(ListView l, View v, int position, long id) {
   // TODO Auto-generated method stub
   showDetails(position);
  }

  void showDetails(int index){
   Log.v(TAG, "index : " + index);
   mCurCheckPosition = index;
   getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
   getListView().setItemChecked(index, true);
   
   if (mLandPane) {
    Log.v(TAG, "mLandPane index : " + index);
    DetailsFragment details = (DetailsFragment) getFragmentManager().findFragmentById(R.id.flLandFragmentLayout);
    if (details == null || details.getShowIndex() != index) {
     details = DetailsFragment.newInstance(index);
     
     FragmentTransaction ft = getFragmentManager().beginTransaction();
     ft.replace(R.id.flLandFragmentLayout, details);
     ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
     ft.commit();
    }
   }else {
    Intent intent = new Intent();
    intent.setClass(getActivity(), DetailsActivity.class);
    intent.putExtra("index", index);
    startActivity(intent);
   }

  }
 }
  • TitlesFragment는 ListFragment를 상속 받았습니다.
  • public void onActivityCreated(Bundle savedInstanceState) 는 Fragment의 생명주기중 화면에 보여지기 바로 직전의 method 입니다.
  • View detailsFrame 에 LandScape 화면일때 상세화면을 보여줄 FrameLayout를 findViewById를 해줍니다. 여기서 getActivity()는 TitlesFragment를 띄우고 있는 Activity를 말합니다. Fragment를 사용하다보면 많이 사용하게 됩니다.
  • mLandPane는 화면이 LandScape일때를 구분해 주는 flag 입니다.
  • public void onListItemClick(ListView l, View v, int position, long id) 는 ListView를 클릭 했을때의 CallBack Method  입니다.
  • void showDetails(int index) 부분이 FragmentLayout 프로젝트의 핵심 부분으로 LandScape 화면일때는 FrameLayout에 DetailsFragment를 매칭시키며 Portrait 화면일 경우에는 DetailsActivity를 호출하는 내용입니다.
    • (DetailsFragment)getFragmentManager().findFragmentById(R.id.flLandFragmentLayout) :  Fragment 안에서 Activity의 findViewById와 마찬가지로 findFragmentById를 사용하여 fragment로 선언해 줄수 있습니다.
    • details = DetailsFragment.newInstance(index) : DetailsFragment클래스안에 static newInstance  method를 만들어 놨습니다.
    • FragmentTransaction ft = getFragmentManager().beginTransaction() : FragmentManager().beginTransaction()를 통해 FragmentTransaction를 가져옵니다.
    • ft.replace(R.id.flLandFragmentLayout, details) : FragmentTransaction를 이용하여 fragment를 Activity에 추가하였다가 삭제할 수 있습니다. 현재는 R.id.flLandFragmentLayout에 details를 replace 하는 Method 입니다.
    • ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) : setTransition는 Fragment 에서 애니메이션 효과를 주는 기능입니다.
    • ft.commit() : 마지막에 꼭 commit 해줘야 화면에서 보입니다.
참고)
◆ setTranstion (int transit) : 애니메이션 효과를 준다.
  • TRANSIT_NONE : 애니메이션 효과를 제공하지 않는다.
  • TRANSIT_FRAGMENT_OPEN : 스택에서 꺼내는 효과를 창출한다.
  • TRANSIT_FRAGMENT_CLOSE : 스택에 넣는 효과를 창출한다.
◆ setCustomAnimation (int enter, int exit) : 사용자가 제작한 애니메이션을 사용하여 Fragment를 출력한다.
  • enter : 시작시점 효과
  • exit : 끝나는 시점의 효과
FragmentLayoutActivity$DetailsFragment
public static class DetailsFragment extends Fragment {
  
  public static DetailsFragment newInstance(int index){
   
   DetailsFragment f = new DetailsFragment();
   Bundle args = new Bundle();
   args.putInt("index", index);
   f.setArguments(args);
   return f;
  }
  
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
   // TODO Auto-generated method stub
   if (container == null) {
    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[getShowIndex()]);
   return scroller;
  }
  
  public int getShowIndex(){
   return getArguments().getInt("index", 0);
  }
 }

DetailsFragment에서 유심히 봐야할 메소드는 setArguments입니다. Activity에서는 Intent.putExtra()와 Intent.getExtra()와 같은 method를 이용하여 데이터를 주고 받았지만 Fragment에서는 setArguments()와 getArguments()메소드를 이용하여 데이터를 주고 받을 수 있습니다.
  • onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) 는 Fragment의 생명주기중 View를 inflater할때 사용할수 있는 곳입니다. 매개변수를 보면 여기에는 화면을 붙일 수 있는 inflater와 부모View를 가지고 올 수 있는 container와 저장된 정보를 사용 할 수 있는 savedInstanceState로 구성되어 있습니다. 하단 코딩은 UI를 자바 코드로 표현한 것입니다.
  • getShowIndex()에서 getArguments()를 사용하여setArguments()한 데이타를 가지고 오도록 했습니다.

FragmentLayout.java 소스 전문
package com.example.wawoops_fragment_test;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.TextView;

public class FragmentLayoutActivity extends Activity {
 private static final String TAG = "FragmentLayoutActivity";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  // TODO Auto-generated method stub
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_fragment_layout);
 }
 
 public static class DetailsActivity extends Activity{

  @Override
  protected void onCreate(Bundle savedInstanceState) {
   // TODO Auto-generated method stub
   super.onCreate(savedInstanceState);
   
   if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
    finish();
    return;
   }
   
   if (savedInstanceState == null) {
//    DetailsFragment details = new DetailsFragment();
//    details.setArguments(getIntent().getExtras());
//    Log.e(TAG, "getIntExtra : " + getIntent().getIntExtra("index", 0));
    DetailsFragment details = DetailsFragment.newInstance(getIntent().getIntExtra("index", 0));
    getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
   }
  }
 }
 
 public static class TitlesFragment extends ListFragment{
  boolean mLandPane;
  int mCurCheckPosition = 0;
  @Override
  public void onActivityCreated(Bundle savedInstanceState) {
   // TODO Auto-generated method stub
   super.onActivityCreated(savedInstanceState);
   Log.v(TAG, "onActivityCreated");
   
   setListAdapter(new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));
   
   View detailsFrame = getActivity().findViewById(R.id.flLandFragmentLayout);
   mLandPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
   
   if (savedInstanceState != null) {
    mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
   }
   
   if (mLandPane) {
    showDetails(mCurCheckPosition);
   }
  }
  
  @Override
  public void onSaveInstanceState(Bundle outState) {
   // TODO Auto-generated method stub
   super.onSaveInstanceState(outState);
   Log.e(TAG, "onSaveInstanceState");
   outState.putInt("curChoice", mCurCheckPosition);
  }

  @Override
  public void onListItemClick(ListView l, View v, int position, long id) {
   // TODO Auto-generated method stub
   showDetails(position);
  }

  void showDetails(int index){
   Log.v(TAG, "index : " + index);
   mCurCheckPosition = index;
   getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
   getListView().setItemChecked(index, true);
   
   if (mLandPane) {
    Log.v(TAG, "mLandPane index : " + index);
    DetailsFragment details = (DetailsFragment) getFragmentManager().findFragmentById(R.id.flLandFragmentLayout);
    if (details == null || details.getShowIndex() != index) {
     details = DetailsFragment.newInstance(index);
     
     FragmentTransaction ft = getFragmentManager().beginTransaction();
     ft.replace(R.id.flLandFragmentLayout, details);
     ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
     ft.commit();
    }
   }else {
    Intent intent = new Intent();
    intent.setClass(getActivity(), DetailsActivity.class);
    intent.putExtra("index", index);
    startActivity(intent);
   }

  }
 }
 
 public static class DetailsFragment extends Fragment {
  
  public static DetailsFragment newInstance(int index){
   
   DetailsFragment f = new DetailsFragment();
   Bundle args = new Bundle();
   args.putInt("index", index);
   f.setArguments(args);
   return f;
  }
  
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
   // TODO Auto-generated method stub
   if (container == null) {
    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[getShowIndex()]);
   return scroller;
  }
  
  public int getShowIndex(){
   return getArguments().getInt("index", 0);
  }
 }
}
}

API 소스중 App → Fragment → FragmentLayout 의 소스이며 약간 수정한 부분이 있습니다.
수정한 부분은 API에서는 DetailsActivity에서 DetailsFragment를 새로 만들어서 사용하던것을 newInstance를 활용하는 방법을 사용 하였으며, 자동화면 전환 시 마지막 클릭한 정보를 Portrait 와 LandScape와 상관없이 알고 있다가  LandScape 화면 전화시 보여주게 하기 위하여 getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE), getListView().setItemChecked(index, true)의 위치를 수정 하였습니다.

관련글

댓글 없음:

댓글 쓰기