본문 바로가기

유저 인터페이스

네비게이션 드로어 활용 - (3) 액티비티 상태 변화 대응하기


지금까지 네비게이션 드로어와 프래그먼트를 연동하여 사용하는 방법에 대해 알아 보았는데요, 앞에서 만든 예제들은 '탄탄한 애플리케이션'을 위한 대비가 제대로 되어 있지 않습니다. 바로, '액티비티 생애주기'를 고려하고 있지 않습니다.


안드로이드 애플리케이션에서 화면을 표시하는 역할을 담당하는 액티비티는 동작 모습에 따라 여러 상태를 가지며, 각 상태가 변경될 때마다 그에 맞는 콜백 메서드가 호출되어 개발자가 각 상태에 알맞는 작업을 할 수 있게끔 구성되어 있습니다. 


그런데, 액티비티는 별도의 예외 처리를 하지 않는 한 시스템 환경이 변할 때 새로 생성됩니다. 즉, 처음부터 다시 생애주기 메서드가 호출되므로 이전의 액티비티 상태 정보를 잃게 됩니다. 그러므로, 액티비티 상태가 변하더라도 기존에 사용하던 액티비티의 상태 정보를 잃지 않도록 별도의 처리가 필요합니다.


앞에서 만든 예제는 화면을 회절할 경우, 현재 표시되고 있는 프래그먼트에 대한 정보를 잃게 됩니다. 때문에, ImageFragment를 보고 있는 상태에서 화면을 회전할 경우 ImageFragment가 아닌 초기에 표시되는 프래그먼트인 TextFragment가 표시됩니다.


따라서, 액티비티에 현재 표시하고 있는 프래그먼트에 대한 정보를 저장하고, 이 정보를 사용하여 액티비티가 재생성 될 때 기존에 표시하고 있던 프래그먼트를 표시하도록 구현해야 합니다.


이를 구현하기 위해, 액티비티를 수정해야 합니다. 먼저 액티비티에 다음과 같이 현재 선택된 프래그먼트를 저장할 때 사용할 키 값과 각 프래그먼트에 할당할 ID, 마지막으로 선택한 프래그먼트의 ID를 저장할 변수와 프래그먼트를 선택하는 메서드를 구현합니다.


[MainActivity.java]

static final String KEY_FRAGMENT_ID = "fragment_id";
static final int FRAGMENT_TEXT = 0;
static final int FRAGMENT_IMAGE = 1;

int lastFragmentId = FRAGMENT_TEXT;

private void selectFragment(int fragmentId){
    switch(fragmentId){
        case FRAGMENT_TEXT:
            getSupportFragmentManager()
                    .beginTransaction()
                    .replace(R.id.fl_activity_main, fragText).commit();
            break;
        case FRAGMENT_IMAGE:
            getSupportFragmentManager()
                    .beginTransaction()
                    .replace(R.id.fl_activity_main, fragImage).commit();
            break;
    }
    lastFragmentId = fragmentId;
}


다음, onSaveInstanceState()를 다음과 같이 오버라이드 합니다. onSaveInstanceState() 메서드는 시스템 환경이 변경되거나 액티비티가 종료되기 전에 액티비티 상태를 저장하기 위해 호출되는 메서드로, 이 곳에서 마지막으로 선택한 프래그먼트의 ID를 Bundle 객체를 통해 저장하면 나중에 액티비티가 복구되는 도중에 해당 값을 참조할 수 있습니다.


[MainActivity.java]

@Override
public void onSaveInstanceState(Bundle outState){
    super.onSaveInstanceState(outState);
    outState.putInt(KEY_FRAGMENT_ID, lastFragmentId);
}


이제, 위에서 저장한 정보를 사용할 차례입니다. 액티비티 재 생성시 이전에 저장된 정보가 있을 경우, onCreate(Bundle) 및 onRestoreInstanceState(Bundle)의 인자로 저장한 정보를 불러올 수 있습니다. 이 예제에서는 onCreate() 에서 정보를 불러와 프래그먼트 상태를 복구합니다.


다음과 같이 onCreate() 메서드에 프래그먼트 상태를 복구하는 코드를 추가합니다. 기존에 저장된 정보가 없다면 (onCreate() 메서드의 Bundle 객체가 null) 기본 프래그먼트인 TextFragment를 표시하고, 그렇지 않다면 Bundle 객체 내에  저장된 프래그먼트 ID를 불러와 해당하는 프래그먼트를 표시합니다.


[MainActivity.java]

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

    // Fragments
    fragText = TextFragment.newInstance();
    fragImage = new ImageFragment().newInstance();

    if(savedInstanceState==null){
        selectFragment(FRAGMENT_TEXT);
    }else{
        selectFragment(savedInstanceState.getInt(KEY_FRAGMENT_ID));
    }


다음, 네비게이션 드로어 내 메뉴를 선택했을 때, 선택한 프래그먼트를 보여주면서 동시에 마지막으로 선택한 프래그먼트 정보를 저장하기 위해 다음과 같이 selectFragment() 메서드를 사용하도록 변경합니다.


[MainActivity.java]

lvDrawerList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        selectFragment(position);
        dlDrawer.closeDrawer(lvDrawerList);
    }
});


모든 구현이 끝났습니다. 예제를 실행한 후, ImageFragment를 선택한 후 화면을 회전해도 그대로 ImageFragment가 선택된 채로 화면에 잘 표시되는 것을 확인할 수 있습니다.







추가로 참고할 만한 링크 : http://developer.android.com/guide/topics/resources/runtime-changes.html