[Android] Getting Started (3)

Android developer site study

  • Getting Started
    • Building Your First App
    • Supporting Different Devices
    • Building a Dynamic UI with Fragments
    • Saving Data
    • Interacting with Other Apps
    • Working with System Permissions
https://developer.android.com/training/index.html

동적이고 multi-pane 사용자 인터페이스를 생성하기 위해 UI 컴포넌트들과 activity 행동들을 캡슐화하여 모듈을 만들고 이를 activity에 넣었다 뺐다 할 수 있다.

이런 모듈들은 Fragmentclass로 생성할 수 있다. 이는 중첩된 activity처럼 행동하며 자신의 레이아웃을 정의하고 스스로의 라이프사이클을 관리한다.

fragment가 자신의 레이아웃을 정하면 여러가지 화면 크기에 따라 레이아웃 구성을 변경하기 위해 activity 내의 다른 fragmet들과 다르게 조합될 수 있다. (작은 화면에서는 하나의 fragment만 보여주지만 큰 화면에서는 두 개 이상의 fragment를 한 번에 보여줄 수 있다.)

Fragment의 생성

Fragment를 스스로의 라이프사이클을 갖고, 스스로의 입력 이벤트를 수신하고, activity가 동작하는 동안 추가/삭제가 가능한 activity의 모듈부라고 생각해도 된다. (“sub activity”로서 다른 activity들에서 재사용하는 것의 일종)

아래 예제들을 따라하기 전에…

만약 Suport Library를 사용한 적이 없다면 아래 페이지를 통해 v4 library를 setup하는 과정이 선행되어야 한다.

v7 appcompat Library를 사용해도 된다.

Fragment Class의 생성

fragment를 생성하기 위해 Fragment Class를 상속받아야 한다. 그 후 key lifecycle 메서드를 Override하여 app logic에 삽입한다. 이는 Activity class의 사용과 유사한 부분이다.

Fragment를 생성할 때 하나의 차이점이 있는데 레이아웃을 정의할 때 반드시 onCreateView() callback를 사용해야 한다는 것이다.

사실 이것이 fragment가 수행될 때 필요한 유일한 callback이다.

간단한 fragment 예제를 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
}

Activity와 비슷하게, fragment는 다른 라이프사이클 callback들을 구현해야 한다. 이는 activity에 추가/삭제될 때, 그리고 activity가 라이프사이클 상태를 천이(transition)하는 것을 관리할 수 있어야 한다.

예를 들어, activity의 onPause() 메서드가 호출되면 activity에 속한 fragment들도 onPause()를 수신한다.

XML을 사용하여 Activity에 Fragment를 추가하는 방법

모듈러 UI 요소로서의 fragment가 재사용 가능할 때 Fragment class의 각 인스턴스는 반드시 부모인 FragmentActivity와 연결되어야 한다.

이는 각 fragment를 activity 레이아웃 XML 파일에 정의함으로써 연결이 가능하다.

FragmentActivity는 API level 11 이전의 오래된 시스템 버전에서 fragment를 다루기 위한 Support Libary에 속한 특별한 activity이다. 만약 시스템 버전이 API level 11보다 높다면 일반적인 Activity를 사용하면 된다.

아래의 예시는 activity 내의 두 개의 fragment를 결합하는 레이아웃 파일이다.

res/layout-large/news_article.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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>

그리고 이 레이아웃은 activity 내에 아래와 같이 적용한다.

1
2
3
4
5
6
7
8
9
10
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);
}
}

만약 v7 appcompat library를 사용한다면 FragmentActivity의 subclass인 AppCompatActivity를 상속받아야 한다.

레이아웃 XML 파일 내에 fragment를 정의하여 activity 레이아웃 내에 fragment를 추가하는 경우 runtime 시 이 fragment를 제거할 수 없다.

만약 동작 중에 fragment를 제거하거나 바꿔치기 해야 할 경우 반드시 activity가 처음 시작할 때 activity 내에 넣어야 한다.

이에 대해서는 아래 계속 살펴본다.

Flexible UI 구현

다양한 화면 크기를 지원하는 App의 경우 fragment를 여러가지 레이아웃 구성에 재활용할 수 있다.

FragmentManager Class는 동적 환경 생성을 위해 runtime 시 activity에 fragment를 추가/삭제/교체할 수 있는 메서드를 제공한다.

Runtime 시 activity에 fragment를 추가하는 방법

Fragment의 추가/삭제 등의 트랜잭션을 수행하려면 FragmentManager를 사용하여 FragmentTransaction을 생성해야 한다.
FragmentTransaction은 fragment를 추가/삭제/교체 및 기타 fragment transaction을 수행하는 API를 제공한다.

Activity에서 fragment의 교체와 제거를 허용하는 경우, activity의 onCreate() 메서드에서 fragment를 activity에 추가해야 한다.

Fragment 관련 작업 시 (특히 runtime 시 fragment를 추가하는 경우) 주의해야 할 것은 fragment를 삽입할 수 있는 Container View를 activity 레이아웃에 포함하는 것이다.

아래 코드는 fragment를 다른 fragment와 교체하기 위해 Container View로 빈 FrameLayout을 사용한 것이다.

res/layout/new_articles.xml
1
2
3
4
<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" />
  1. Activity에서 Support Library API를 통해 getSupportFragmentManager()를 호출하여 FragmentManager를 가져오고
  2. beginTransaction()으로 FragmentTransaction을 생성하고
  3. add()로 fragment를 추가한다.

하나의 FragmentTransaction으로 activity의 여러 fragment transaction을 수행할 수 있다. 변경 준비가 완료되면 commit()을 호출해야 한다.

아래 코드는 레이아웃에 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
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와 교체하는 방법

추가하는 절차와 비슷하지만 add() 대신 replace()를 사용한다.

Fragment 교체 또는 제거와 같은 fragment transaction 수행 시 사용자가 변경 사항을 “실행 취소”할 수 있도록 허용해야 할 때도 있다. 이런 동작 (fragment transaction에서 사용자가 뒤로 돌아갈 수 있게 하는 동작)을 위해서는 FragmentTransactioncommit하기 전에 addToBackStack()을 호출하면 된다.

Fragment를 제거 또는 교체 후 해당 transaction을 BackStack에 추가하면 제거된 fragment가 파기되지 않고 중단된다. 사용자가 되돌아가서 fragment를 복원하면 fragment가 재시작된다.
반면 transaction을 BackStack에 추가하지 않으면 fragment가 제거되거나 교체될 때 파기된다.

다른 fragment와의 교체는 아래와 같이 이루어진다.

addToBackStack() 메서드는 transaction의 고유한 이름을 지정하는 문자열 매개변수를 취한다. 하지만 FragmentManager.BackStackEntry API를 사용하여 고급 fragment 작업을 수행하는 경우 외에는 이 이름이 필요하지 않다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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();

다른 Fragment와의 커뮤니케이션

Fragment UI 요소를 재사용하기 위해 스스로의 레이아웃 및 행동을 갖는 각각의 독립적인 모듈러 요소를 빌드해야 한다. 이 재사용 가능한 Fragment를 한 번 정의한 뒤, 이들을 Activity와 연결하고 App의 로직과 연결하여 복합적인 UI를 실제로 구현할 수 있다.

사용자 이벤트 기반의 컨텐츠를 교환하는 등의 동작을 위해 하나의 Fragment를 다른 fragment와 연결하고 싶을 때가 있을 것이다.

모든 Fragment-to-Fragment 통신은 연관된 activity를 통해 수행된다. 두 개의 fragment는 직접 통신할 수 없다.

Interface의 정의

Activity에 속한 Fragment가 통신을 하기 위해 Activity와 연관된 fragment class의 interface와 구현체를 정의해야 한다.

Fragment는 onAttach() 라이프사이클 메서드에서 interface 구현을 확인하고 Activity와 통신하기 위해 interface 메서드를 호출한다.

아래는 Fragment와 Activity 간 통신하는 예제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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는 onArticleSelected() 메서드(또는 interface 내의 다른 메서드)를 통해 activity에 message를 전달할 수 있다. 이 때 OnHeadlineSelectedListener interface의 mCallback 인스턴스가 사용된다.

예를 들어, fragment의 아래 메서드는 사용자가 list item을 클릭하면 호출된다. Fragment는 이 이벤트를 부모 activity로 전달할 때 callback interface를 사용한다.

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

Interface의 구현

Fragment로부터 이벤트 콜백을 수신하기 위해 activity는 fragment class에 정의된 interface를 구현해야 한다.

아래 코드는 위의 예제 interface를 구현한 코드이다.

1
2
3
4
5
6
7
8
9
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
}
}

Fragment로 Message 전달

Activity는 findFragmentById() 메서드로 Fragment 인스턴스를 가져온 후 직접 fragment의 public 메서드를 호출하믕로써 message를 전달할 수 있다.

만약, 위의 activity가 fragment를 포함할 수 있다고 가정한다면 activity는 콜백 메서드에서 받은 정보를 다른 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
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();
}
}
}

출처

Share