- 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) : 애니메이션 효과를 준다.
◆ 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()메소드를 이용하여 데이터를 주고 받을 수 있습니다.
FragmentLayout.java 소스 전문- onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) 는 Fragment의 생명주기중 View를 inflater할때 사용할수 있는 곳입니다. 매개변수를 보면 여기에는 화면을 붙일 수 있는 inflater와 부모View를 가지고 올 수 있는 container와 저장된 정보를 사용 할 수 있는 savedInstanceState로 구성되어 있습니다. 하단 코딩은 UI를 자바 코드로 표현한 것입니다.
- getShowIndex()에서 getArguments()를 사용하여setArguments()한 데이타를 가지고 오도록 했습니다.
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)의 위치를 수정 하였습니다.
관련글
댓글 없음:
댓글 쓰기