2011년 8월 31일 수요일

Android 3.0 Fragments Example


Fragment을 적용한 Sample App을 만들어보기로 하겠습니다.

App의 형태는 아래와 같습니다.
  1. 좌측은 메뉴 형태로 각 사이트로 이동할 수 있는 버튼이 4개 있습니다.
  2. 버튼을 클릭하면 우측 Webview가 해당 사이트로 이동합니다.
  3. 처음 시작은 m.nate.com 에서 시작됩니다.



위 화면을 구성하기 위해서는 외곽을 구성하는 하나의 Activity와 두 개의 Fragment을 작성해야 합니다.



Step 1: AndroidMainfest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.daddycat.fragment"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="12" />

<application
android:icon="@drawable/icon"
android:label="@string/app_name">
<activity
android:name=".FirstFragmentsLayoutActivity"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:anyDensity="true" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

Step 2:  Activity을 구성하는 default_fragment_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/menu"
class="com.daddycat.fragment.FirstFragmentsLayoutActivity$menuFragment"
android:layout_width="400px"
android:layout_height="fill_parent" />


<FrameLayout
android:id="@+id/details"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="?android:attr/detailsElementBackground" />
</LinearLayout>

Step 3: Menu Fragment Layout, menu_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#bbbbbb">


<Button
android:id="@+id/naver"
android:layout_width="150px"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="네이버"
android:tag="m.naver.com" />


<Button
android:id="@+id/daum"
android:layout_width="150px"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="다음"
android:tag="m.daum.net" />


<Button
android:id="@+id/google"
android:layout_width="150px"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="구글"
android:tag="m.google.com" />


<Button
android:id="@+id/yahoo"
android:layout_width="150px"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="야후"
android:tag="w.yahoo.co.kr" />

</LinearLayout>

Step 4: Detail Fragment Layout, detail_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<WebView
android:id="@+id/mainWebkit"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="none" />

</LinearLayout>

Step 5: Activity & Fragments, FirstFragmentsLayoutActivity.java
package com.daddycat.fragment;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.webkit.CookieSyncManager;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;

public class FirstFragmentsLayoutActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.default_fragment_layout);

}

public static class menuFragment extends Fragment {
boolean mDualPane;
private Button naver;
private Button daum;
private Button google;
private Button yahoo;

@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);

naver = (Button) getActivity().findViewById(R.id.naver);
daum = (Button) getActivity().findViewById(R.id.daum);
google = (Button) getActivity().findViewById(R.id.google);
yahoo = (Button) getActivity().findViewById(R.id.yahoo);

naver.setOnClickListener(new MenuOnClickListener());
daum.setOnClickListener(new MenuOnClickListener());
google.setOnClickListener(new MenuOnClickListener());
yahoo.setOnClickListener(new MenuOnClickListener());

View detailsFrame = getActivity().findViewById(R.id.details);
mDualPane = detailsFrame != null
&& detailsFrame.getVisibility() == View.VISIBLE;
showDetailFragment("m.nate.com");
}

private void showDetailFragment(String index) {
if (mDualPane) {
DetailsFragment details = (DetailsFragment) getFragmentManager()
.findFragmentById(R.id.details);
details = DetailsFragment.newInstance(index);
FragmentTransaction ft = getFragmentManager()
.beginTransaction();
ft.replace(R.id.details, details);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.menu_fragment, null);
}

private class MenuOnClickListener implements OnClickListener {

@Override
public void onClick(View v) {

showDetailFragment(v.getTag().toString());
}

}
}

public static class DetailsFragment extends Fragment {

public static DetailsFragment newInstance(String index) {
DetailsFragment f = new DetailsFragment();

Bundle args = new Bundle();
args.putString("index", index);
f.setArguments(args);

return f;
}

public String getShownIndex() {
return getArguments().getString("index", "m.nate.com");
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.detail_fragment, null);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);

WebView browser = (WebView) getActivity().findViewById(
R.id.mainWebkit);
browser.setWebViewClient(new MainWebViewClient());
WebSettings ws = browser.getSettings();
ws.setJavaScriptEnabled(true);
ws.setDefaultZoom(WebSettings.ZoomDensity.FAR);
browser.requestFocusFromTouch();
boolean ScrollBarEnabled = getActivity().getIntent()
.getBooleanExtra("ScrollBarEnabled", true);
if (!ScrollBarEnabled) {
browser.setVerticalScrollBarEnabled(false);
browser.setHorizontalScrollBarEnabled(false);
}
browser.loadUrl("http://" + getShownIndex());
}

private class MainWebViewClient extends WebViewClient {

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {

view.loadUrl(url);
return true;
}

@Override
public void onPageFinished(WebView view, String url) {
CookieSyncManager.getInstance().sync();

}
}
}
}

Android 3.0 Fragment

Fragment 소개

Application 개발자는 Android 3.0에 새롭게 추가된 Fragment를 이용하여 Application의 UI를
아이패드 처럼 FrameLayout으로 나눌 수 있게 되었습니다.

Fragment는 별도의 UI와 생명주기를 갖는 독립된 Application Component 이며, 특정 Device 혹은 Screen 해상도에 따라여러 개의 Fragment를 조합하거나 재배치 하는 방식으로 원하는 UI를 손쉽게 구성할 수 있습니다.

이전에도 ActivityGroup을 이용하여 복수의 Activity를 하나의 Activity에 담을 수 있었지만, 기본적으로 Activity는 독립적으로 동작하도록 설계되었기 때문에 ActivityGroup을 적절하게 활용하는 것은 쉬운 일이 아니었습니다.

지금까지 화면이 회전 되거나 설정 값이 변경됨에 현재 Activity가 Destroy 되고 이어 새로운 Activity가 생성되는 경우 두 Activity 인스턴스간에 데이터를 유지하기 위해서는 Activity.onRetainNonConfigurationInstance() 메서드를 활용해야 했습니다.  하지만 Fragment API를 활용하여, 특정 Fragment인스턴스에 적절한 플래그만 설정해주면 가능하게 되었습니다.

DialogFragment 클래스가 제공되어 Activity 라이프 사이클에 잘 부합되도록 다이얼로그 박스를 표시하는게 훨씬 수월해진다.

ListFragment클래스가 제공됩니다.  기존 ListActivity와 유사하지만, Fragmen 특성 상, 임의의 Activity에 손쉽게 포함될 수 있기 때문에 테이터 목록과 동시에 여러가지 추가 정보를 표시하기 적합합니다.

현재 실행중인 Activity에 포함되어 있는 모든 Fragment 인스턴스 정보는 해당 Activity의 'saved instance state’로 자동으로 저장되며, Activity가 잠시 종료되었다가 재 시작될때 자동으로 복원됩니다.

Fragment Lifecycle

Activity의 생명주기 메서드와 매우 유사한 형태를 띄고 있으며, 뷰 생성과 관련된 몇몇 메서드가 더 추가되어 있습니다.

onAttach(Activity)
Fragment가 Activity 레이아웃에 포함되는 순간 호출됩니다. Activity 레이아웃에 Fragment를 정적으로 배치했다면 Activity가 시작될 때 같이 호출되며, 동적으로 레이아웃에 추가할 땐 Fragment를 레이아웃에 추가하는 순간 호출됩니다.

onCreate(Bundle)
Activity의 onCreate() 콜백 메서드와 유사하게 Fragment가 최초로 생성될 때 호출됩니다.

onCreateView(LayoutInflater, ViewGroup, Bundle)
Fragment의 UI를 구성하는 뷰(View)를 반환합니다. UI를 가지지 않는 Fragment일 경우 null을 반환할 수도 있습니다.

onStart()
Fragment가 화면에 표시될 때 호출됩니다. 하지만, 아직 사용자와 상호작용은 할 수 없는 상태입니다.

onResume()
Fragment가 사용자와 상호작용을 할 수 있게 되었을 때 호출됩니다. 즉, Fragment가 완전히 화면에 표시되어 제 역할을 수행할 수 있게 된 상태입니다.

onPause()
Activity의 onPause()와 유사하게 Fragment가 사용자와 상호작용을 할 수 없게 될 때 호출됩니다. Fragment가 아직 화면에 표시되고 있는 상태이나, 다른 요소에 의해 Fragment가 가려져 상호작용을 하지 못하는 상태입니다.

onStop()
Fragment가 화면에서 보이지 않게 될 때 호출됩니다. Activity가 화면에서 보이지 않게 될 때 onStop() 메서드가 호출되는 것과 유사합니다.

onDestroyView()
Fragment가 화면에서 사라진 후, 뷰의 현재 상태가 저장된 후 호출됩니다. 여기에서 저장된 뷰의 상태는 Activity와 유사하게 Bundle 형태로 저장되며, 저장된 뷰의 상태는 onCreate() 및 onCreateView()에서 다시 불러들일 수 있습니다.

onDestroy()
Fragment가 더 이상 사용되지 않을 때 호출됩니다.

onDetach()
Fragment가 Activity 레이아웃에서 제거될 때 호출됩니다.

기존 Activity을 Fragment로 변경한다면.
  1. 화면 유형을 적절하게 잘 나누어 "fragmentlayout"를 구성한다.
  2. 두개로 분할된 화면이라면 전체 외각을 구성하는 하나의 Activity와 2개의 Fragment 로 구성된다.
  3. 기존에는 Actrivity을 상속 받았지만 Fragment를 상속 받아야 한다.
  4. Fragment는 “onCreate”대신 "oncreateView”로 변경해줘야 함. Layout 지정은 "inflater.inflate(R.layout.menu_fragment, null)" 이렇게
  5. onActivityCreated을 사용해서 이벤트 리스너나 view을 핸들링한다.
  6. Fragment는 Activity가 아니기 때문에 “findViewById”는 “getActivity().findViewById”로 변경
  7. FrameLayout는 "getFragmentManager().findFragmentById”로 찾는다.

Which Mobile App Development Option is Better?

Different alternatives to native code development have their own advantages and philosophy behind. No one tool or approach can be clearly ma...