[Do it! 안드로이드 앱 프로그래밍 #11] 프래그먼트란?
Android

[Do it! 안드로이드 앱 프로그래밍 #11] 프래그먼트란?

11 프래그먼트란?

11-1 프래그먼트에 대해 이해하기

여러 개의 화면을 구성할 때는 보통 각각의 화면을 액티비티로 만든 후 액티비티를 전환하는 방법을 사용한다. 그런데 화면의 일부분을 다른 화면으로 구성하고 싶을 때는 어떻게 해야 할까?

 

① 리니어 레이아웃 안에 다른 레이아웃을 추가하고 각각의 레이아웃 안에 필요한 뷰를 넣는다.

② 프레임 레이아웃 안에 여러 개의 레이아웃을 넣어 중첩시킨 후 가시성 속성을 설정한다.

③ 하나의 액티비티 안에 여러 개의 액티비티를 부분 화면으로 설정한다.

 

위 세 가지 방법은 이전 게시글에서 배웠다. 방법 ①은 코드가 복잡해지는 문제가 있고, 방법 ②는 다른 액티비티에서 사용하려 할 때 동일한 레이아웃을 중복해서 만들어야 하는 문제가 있다. 방법 ③은 시스템에서 액티비티를 구성 요소로 관리하는 안드로이드에겐 단말의 리소스를 많이 사용하는 비효율적인 방법이다.

 

이때 사용할 수 있는 것이 프래그먼트(Fragment)이다. 프래그먼트는 하나의 화면을 여러 부분으로 나눠서 보여주거나 각각의 부분 화면 단위로 바꿔서 보여주고 싶을 때 사용할 수 있다.

 

프래그먼트 사용 목적
분할된 화면들을 독립적으로 구성하기 위해 사용함
분할된 화면들의 상태를 관리하기 위해 사용함

 

액티비티가 독립적으로 동작하는 방식을 본떠 만든 프래그먼트
액티비티가 독립적으로 동작하는 방식을 본떠 만든 프래그먼트

프래그먼트는 액티비티와 유사한 방식으로 동작한다. 오른쪽 사진을 보면 액티비티가 시스템 역할을 하게 되므로 액티비티 위에 올라가 있지 않은 프래그먼트는 정상적으로 동작할 수 없다.

 

 

11-2 프래그먼트를 화면에 추가하는 방법 이해하기

프래그먼트도 하나의 XML 레이아웃과 하나의 자바 소스 파일로 동작한다.

 

프래그먼트를 위한 자바 소스는 Fragment 클래스를 상속하여 만들 수 있다. XML 레이아웃 파일의 내용을 소스 파일과 매칭하는 과정이 필요한데, 프래그먼트에는 setContentView 메서드가 없기 때문에 인플레이션 객체인 LayoutInflater를 사용해 인플레이션을 진행해야 한다.

 

새로 만든 프래그먼트를 메인 액티비티에 추가하는 방법은 두 가지가 있다. 하나는 XML 레이아웃에 추가하는 것, 다른 하나는 소스 코드에서 new 연산자로 객체를 만든 후 프래그먼트 매니저로 추가하는 것이다. 두 가지 방법은 다음 내용에서 알아본다.

 

 

11-3 프래그먼트 만들어 화면에 추가하기

새로운 프로젝트를 만들고, 프래그먼트를 추가한다.

 

▼ 프래그먼트 인플레이션 하기

public class MainFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_main, container, false);
    }
}

onCreateView 메서드를 제외한 나머지 코드는 모두 삭제한다.

inflate 메서드로 전달되는 첫 번째 파라미터는 XML 레이아웃 파일이고, 두 번째 파라미터는 이 XML 레이아웃이 설정될 뷰그룹 객체가 되는데 onCreateView 메서드로 전달되는 두 번째 파라미터가 이 프래그먼트의 가장 상위 레이아웃이다. 따라서 container 객체를 전달하면 된다. 세 번째 파라미터는 뷰그룹 객체에 뷰를 언제 설정할지 결정한다. true일 때는 지금, false일 때는 나중에 추가된다.

 

프래그먼트를 액티비티에 추가하는 두 가지 방법 중 이번에는 메인 액티비티의 XML 레이아웃에 태그로 프래그먼트를 추가하는 방법을 사용한다.

 

▼ XML 레이아웃에 프래그먼트 추가하기

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <fragment
        android:id="@+id/mainFragment"
        android:name="org.techtown.fragment.MainFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

액티비티 화면을 채우면서 올라가 있는 프래그먼트 화면
액티비티 화면을 채우면서 올라가 있는 프래그먼트 화면

 

 

11-4 버튼 클릭했을 때 코드에서 프래그먼트 추가하기

프래그먼트를 액티비티에 추가하는 두 가지 방법 중 이번에는 자바 소스 코드로 프래그먼트를 추가하는 방법을 사용한다.

 

▼ 메인 액티비티의 XML 레이아웃에 id 속성 값 추가하기

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

 

▼ 프래그먼트 인플레이션 하기

public class MainFragment extends Fragment {

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

 

▼ 자바 소스 코드로 프래그먼트 추가하기

public class MainActivity extends AppCompatActivity {
    MainFragment mainFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mainFragment = new MainFragment();

        getSupportFragmentManager().beginTransaction().add(R.id.container, mainFragment).commit();
    }
}

프래그먼트 매니저는 프래그먼트를 다루는 작업을 해 주는 객체로 프래그먼트 추가, 삭제 또는 교체 등의 작업을 할 수 있게 한다. 그런데 이런 작업들은 프래그먼트를 변경할 때 오류가 생기면 다시 원래 상태로 돌릴 수 있어야 하므로 트랜잭션 객체를 만들어 실행한다. 트랜잭션 객체는 beginTransaction 메서드를 호출하면 시작되고 commit 메서드를 호출하면 실행된다.

 

*getSupportFragmentManager 메서드는 getFragmentManager 메서드와 같은 기능을 한다. 예전 버전까지 호환되도록 만들려면 getSupportFragmentManager 메서드를 사용하는 것이 좋다.

 

액티비티 화면을 채우면서 올라가 있는 프래그먼트 화면 2
액티비티 화면을 채우면서 올라가 있는 프래그먼트 화면 2

 

이제 여기에서 버튼을 클릭하면 메뉴 프래그먼트로 전환되는 기능을 추가한다.

 

▼ 버튼을 클릭해 프래그먼트 전환하기

public class MainActivity extends AppCompatActivity {
    MainFragment mainFragment;
    MenuFragment menuFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mainFragment = new MainFragment();
        menuFragment = new MenuFragment();

        getSupportFragmentManager().beginTransaction().add(R.id.container, mainFragment).commit();
    }

    public void onFragmentChanged(int index){
        if(index == 0){
            getSupportFragmentManager().beginTransaction().replace(R.id.container, menuFragment).commit();
        }else if(index == 1){
            getSupportFragmentManager().beginTransaction().replace(R.id.container, mainFragment).commit();
        }
    }
}

메뉴 프래그먼트 객체를 선언하고, onFragmentChanged 메서드를 추가한다. onFragmentChanged 메서드는 프래그먼트에서 호출할 수 있도록 정의한 것으로, 파라미터로 전달된 정수의 값이 0이면 메뉴 프래그먼트가 보이게 하고, 1이면 메인 프래그먼트가 보이게 한다. FragmentManager 객체의 replace 메서드를 사용해 프래그먼트를 바꾸도록 입력한다.

 

▼ 메인 프래그먼트에서 버튼 클릭 시 onFragmentChanged 메서드 호출하기

public class MainFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_main, container, false);

        Button button = rootView.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MainActivity activity = (MainActivity) getActivity();
                activity.onFragmentChanged(0);
            }
        });
        return rootView;
    }
}

 

▼ 메뉴 프래그먼트에서 버튼 클릭 시 onFragmentChanged 메서드 호출하기

public class MenuFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_menu, container, false);

        Button button =  rootView.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MainActivity activity = (MainActivity) getActivity();
                activity.onFragmentChanged(1);
            }
        });
        return rootView;
    }
}

 

 

11-5 프래그먼트의 수명주기

프래그먼트는 액티비티를 본떠 만들면서 액티비티처럼 독립적으로 동작하도록 수명주기(Life Cycle) 메서드를 추가했다. 프래그먼트는 액티비티 위에 올라가는 것이므로 프래그먼트의 수명주기도 액티비티의 수명주기에 종속적이지만 프래그먼트만 가질 수 있는 독립적인 상태 정보들이 더 추가되었다.

 

▼ 화면에 보이기 전에 호출되는 상태 메서드

메서드 설명
onAttach(Activity) 프래그먼트가 액티비티와 연결될 때 호출됨
onCreate(Bundle) 프래그먼트가 초기화될 때 호출됨
onCreateView(LayoutInflator, ViewGroup, Bundle) 프래그먼트와 관련되는 뷰 계층을 만들어서 리턴함
onActivityCreated(Bundle) 프래그먼트와 연결된 액티비티가 onCreate 메서드의 작업을 완료했을 때 호출됨
onStart() 프래그먼트와 연결된 액티비티가 onStart되어 사용자에게 프래그먼트가 보일 때 호출됨
onResume() 프래그먼트와 연결된 액티비티가 onResume되어 사용자와 상호작용할 수 있을 때 호출됨

 

▼ 중지되면서 호출되는 상태 메서드

메서드 설명
onPause() 프래그먼트와 연결된 액티비티가 onPause되어 사용자와 상호작용을 중지할 때 호출됨
onStop() 프래그먼트와 연결된 액티비티가 onStop되어 화면에서 더 이상 보이지 않을 때나 프래그먼트의 기능이 중지되었을 때 호출됨
onDestroyView() 프래그먼트와 관련된 뷰 리소스를 해제할 수 있도록 호출됨
onDestroy() 프래그먼트의 상태를 마지막으로 정리할 수 있도록 호출됨
onDetach() 프래그먼트가 액티비티와 연결을 끊기 바로 전에 호출됨

 

액티비티와 프래그먼트의 수명주기
액티비티와 프래그먼트의 수명주기

액티비티와 프래그먼트의 수명주기를 알아보기 위해 각각의 메서드를 재정의한 후 로그를 출력하도록 했다. 태그 Main은 임의로 지정한 것이므로 신경 쓸 필요 없고, [M]이 MainActivity, [F]가 MainFragment에서 호출되는 메서드이다. 각각의 메서드들이 어떤 순서로 호출되는지 알아두면 좋을 것 같다.