다양한 크기의 화면을 가지는 단말기가 늘어남에 따라 한 화면에 여러 개의 화면 요소를 원하는 수요가 늘어가고 있습니다. 대표적으로 화면의 크기가 큰 태블릿 PC와 같이 화면의 크기가 큼에 따라 복잡한 레이아웃 구성과 뷰 위젯 배치들로 인해 기존의 Activity를 통한 레이아웃 구성만으로는 구현하기 버거운 면이 있었습니다.
이를 커버하기 위해 나온것이 안드로이드 3.0(API 11)부터 추가된 개념인 프래그먼트(Fragment)입니다. 프래그먼트는 액티비티 내에서 화면 UI의 일부를 나타냅니다. 여러 개의 프래그먼트를 조합하여 액티비티가 출력하는 한 화면의 UI를 표현할 수 있으며 하나의 프래그먼트를 다른 액티비티에 재사용할 수 있습니다. 액티비티(Activity)처럼 하나의 독립된 모듈처럼 실행되기 때문에 액티비티와 연관된 생명주기를 가지고 있으며 액티비티 실행 중에도 화면에 동적으로 추가되거나 다른 Fragment로 교체가 가능합니다.
기본적으로 한 개의 액티비티에 들어가는 화면 요소를 Fragment 단위로 나누어 관리하기 때문에 레이아웃을 분리 관리할 수 있고, 액티비티의 화면 구성을 위한 레이아웃의 복잡도도 줄일 수 있습니다.
위 그림처럼 프래그먼트가 정의한 UI 모듈이 태블릿 환경에서는 하나의 액티비티로 조합될 수 있는 반면 모바일 단말기에서는 두 개의 액티비티로 분리되어 사용될 수 있습니다.
예를 들어 Fragment A는 애플리케이션의 콘텐츠 목록을 표시하는 UI를 담당하고 Fragment B는 선택된 콘텐츠 목록에 해당하는 콘텐츠를 표시하는 UI를 담당하는 프래그먼트라고 가정하겠습니다. 화면의 크기가 큰 태블릿에서는 A 액티비티 안에서 Fragment A와 Fragment B를 조합하여 한 화면에 출력하는 게 가능한 반면 크기가 작은 일반 모바일 단말기에서는 액티비티 A에 Fragment A를 포함시켜 한 화면으로 출력하고 액티비티 B에서 Fragment B를 포함하여 한 화면에 출력하도록 구현하고 있습니다.
1. 프래그먼트(Fragment) 구현 예제
A Fragment와 B Fragment 두 개를 정의하고 MainActivity 안에 포함시킵니다. 두 프래그먼트는 버튼 클릭에 의해 액티비티 실행 중 동적으로 교체되도록 구현할 것입니다.
1.1 Fragment에서 사용할 Xml 레이아웃 리소스
■ A Fragment Xml 레이아웃 리소스 / fragment_a.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F10606">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fragment A"
android:textSize="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
■ B Fragment Xml 레이아웃 리소스 / fragment_b.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#061EF1">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fragment B"
android:textSize="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
▼ A 프래그먼트와 B 프래그먼트에서 사용할 UI 구성을 위한 XML 레이아웃 리소스입니다. 각 프래그먼트의 구분을 위해 최상위 레이아웃인 ConstraintLayout의 BackGroud 속성을 다르게 지정하였습니다.
1.2 Fragment를 상속받는 AFragment / BFragment 클래스 생성
■ AFragment.class
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class AFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_a, container, false);
}
}
■ BFragment.class
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class BFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_b, container, false);
}
}
▼ Fragment의 생명주기 관련 콜백 메소드 중 하나인 onCreateView() 함수를 재정의합니다. 해당 함수에서 해당 프래그먼트에 맞는 UI를 그리기 위해서 해당 함수에서 전개자를 통해 View를 생성해서 반환해야 합니다.
1.3 MainActivity 구현
먼저 Main Activity에서 사용할 UI 구성을 위한 Xml 레이아웃 리소스입니다.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/linearLayout"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
</FrameLayout>
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<Button
android:id="@+id/btn_fragmentA"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="clickHandler"
android:text="A" />
<Button
android:id="@+id/btn_fragmentB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="clickHandler"
android:text="B" />
</LinearLayout>
</android.support.constraint.ConstraintLayout>
▼ ConstraintLayout을 최상단 레이아웃으로 두고 하단에 FrameLayout과 버튼 두 개를 배치한 형태입니다.
public class MainActivity extends AppCompatActivity {
private FragmentManager fragmentManager;
private AFragment fragmentA;
private BFragment fragmentB;
private FragmentTransaction transaction;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentManager = getSupportFragmentManager();
fragmentA = new AFragment();
fragmentB = new BFragment();
transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.frameLayout, fragmentA).commitAllowingStateLoss();
}
public void clickHandler(View view)
{
transaction = fragmentManager.beginTransaction();
switch(view.getId())
{
case R.id.btn_fragmentA:
transaction.replace(R.id.frameLayout, fragmentA).commitAllowingStateLoss();
break;
case R.id.btn_fragmentB:
transaction.replace(R.id.frameLayout, fragmentB).commitAllowingStateLoss();
break;
}
}
}
프래그먼트의 트랜젝션을 수행(프래그먼트를 추가/삭제/교체)을 수행하기 위해서는 FragmentTrasaction에서 가져온 API를 활용해야 합니다. FragmentTrasaction 객체를 얻기 위해서는 FragmentManager 참조 객체가 필요합니다. 참조 객체를 얻기 위해서는 getFragmentManager() 함수 호출을 통해 가져올 수 있습니다.
최종적으로 FragmentTrasaction 참조 객체를 얻기 위해서는 FragmentManager의 beginTransaction() 함수를 통해 얻을 수 있습니다.
FragmentTrasaction 의 replace() 함수를 통해 프래그먼트를 교체하는 게 가능합니다. 첫 번째 인자는 액티비티의 레이아웃 상에서 교체되는 프래그먼트가 배치될 최상위 ViewGroup입니다. 레이아웃 리소스 ID값을 넘기며 위 예제에서는 FrameLayout이 해당됩니다. 두 번째 인자는 교체하고자 하는 Fragment 객체입니다.
변경 내용이 적용되기 위해서는 commit 관련된 함수를 호출을 해야 변경 내용이 적용됩니다.