탐색 창 생성

탐색 창은 앱의 기본 탐색 옵션이 표시된 패널로, 왼쪽 가장자리에 있습니다. 대부분의 경우 숨겨져 있지만 사용자가 손가락으로 화면의 왼쪽 가장자리를 스와이프하거나 앱의 최상위에서 사용자가 작업 모음의 앱 아이콘을 터치하면 탐색 창이 표시됩니다.

이 과정에서는 지원 라이브러리에서 제공되는 DrawerLayout API를 사용하여 탐색 창을 구현하는 방법에 대해 설명합니다.

탐색 창 디자인

앱에서 탐색 창을 사용할 것인지 결정하기 전에 먼저 탐색 창 디자인 가이드에 정의된 사용 사례 및 디자인 원칙을 이해해야 합니다.

창 레이아웃 생성

탐색 창을 추가하려면 DrawerLayout 객체를 사용하여 사용자 인터페이스를 레이아웃의 루트 뷰로 선언합니다. 화면의 기본 콘텐츠가 포함된 뷰(창이 숨겨져 있을 때의 기본 레이아웃)와 탐색 창의 콘텐츠가 포함된 다른 뷰를 DrawerLayout 내에 추가합니다.

예를 들면, 다음 레이아웃에서는 두 개의 하위 뷰가 포함된 DrawerLayout을 사용합니다. FrameLayout에는 런타임 시 Fragment를 사용하여 입력하는 기본 콘텐츠가 포함되고, ListView에는 탐색 창의 콘텐츠가 포함됩니다.

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- The main content view -->
    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <!-- The navigation drawer -->
    <ListView android:id="@+id/left_drawer"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:background="#111"/>
</android.support.v4.widget.DrawerLayout>

이 레이아웃은 몇 가지 중요한 레이아웃 특성을 보여줍니다.

  • 기본 콘텐츠 뷰(위의 FrameLayout)는 DrawerLayout첫 번째 하위 항목이어야 합니다. XML 순서가 z 순서(z-ordering)를 의미하고 창은 콘텐츠의 맨 위에 위치해야 하기 때문입니다.
  • 기본 콘텐츠 뷰는 탐색 창이 숨겨져 있을 때 전체 UI를 보여주기 때문에 상위 뷰의 너비 및 높이와 일치하도록 설정되어 있습니다.
  • 창 뷰(ListView)는 android:layout_gravity 특성을 사용하여 가로 중력을 지정해야 합니다. 오른쪽에서 왼쪽 방향(RTL) 언어를 지원하려면 값을 "left" 대신 "start"로 지정합니다. 레이아웃이 RTL일 때 창은 오른쪽에 표시됩니다.

  • 창 뷰는 너비를 dp 단위로 지정하며 높이는 상위 뷰와 일치합니다. 사용자가 항상 기본 콘텐츠의 일부를 볼 수 있으려면 창 너비는 320dp 이하여야 합니다.

창 목록 초기화

액티비티에서 가장 먼저 해야 할 일은 탐색 창의 항목 목록을 초기화하는 것입니다. 초기화 방법은 앱의 콘텐츠에 따라 다르지만 보통 탐색 창은 ListView로 구성되어 있기 때문에 목록은 ArrayAdapter 또는 SimpleCursorAdapter와 같은 Adapter를 사용하여 입력해야 합니다.

예를 들면, 다음과 같이 문자열 배열을 사용하여 탐색 목록을 초기화할 수 있습니다.

public class MainActivity extends Activity {
    private String[] mPlanetTitles;
    private DrawerLayout mDrawerLayout;
    private ListView mDrawerList;
    ...

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

        mPlanetTitles = getResources().getStringArray(R.array.planets_array);
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerList = (ListView) findViewById(R.id.left_drawer);

        // Set the adapter for the list view
        mDrawerList.setAdapter(new ArrayAdapter<String>(this,
                R.layout.drawer_list_item, mPlanetTitles));
        // Set the list's click listener
        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());

        ...
    }
}

또한 이 코드는 setOnItemClickListener()를 호출하여 탐색 창의 목록에서 클릭 이벤트를 수신합니다. 다음 섹션에서는 인터페이스를 구현하고, 사용자가 항목을 선택했을 때 콘텐츠 뷰를 변경하는 방법을 보여줍니다.

탐색 클릭 이벤트 처리

사용자가 창 목록에서 항목을 선택하면 시스템은 setOnItemClickListener()에 지정된 OnItemClickListener에서 onItemClick()을 호출합니다.

onItemClick() 메서드로 수행하는 작업은 앱 구조를 구현한 방법에 따라 달라집니다. 다음 예의 경우, 목록의 각 항목을 선택하면 기본 콘텐츠 뷰(R.id.content_frame ID로 식별한 FrameLayout 요소)에 서로 다른 Fragment가 삽입됩니다.

private class DrawerItemClickListener implements ListView.OnItemClickListener {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        selectItem(position);
    }
}

/** Swaps fragments in the main content view */
private void selectItem(int position) {
    // Create a new fragment and specify the planet to show based on position
    Fragment fragment = new PlanetFragment();
    Bundle args = new Bundle();
    args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
    fragment.setArguments(args);

    // Insert the fragment by replacing any existing fragment
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.beginTransaction()
                   .replace(R.id.content_frame, fragment)
                   .commit();

    // Highlight the selected item, update the title, and close the drawer
    mDrawerList.setItemChecked(position, true);
    setTitle(mPlanetTitles[position]);
    mDrawerLayout.closeDrawer(mDrawerList);
}

@Override
public void setTitle(CharSequence title) {
    mTitle = title;
    getActionBar().setTitle(mTitle);
}

열기 및 닫기 이벤트 수신

창 열기 및 닫기 이벤트를 수신하려면 DrawerLayout에서 setDrawerListener()를 호출한 후 DrawerLayout.DrawerListener의 구현에 전달합니다. 이 인터페이스는 창 이벤트를 위한 콜백(예: onDrawerOpened()onDrawerClosed())을 제공합니다.

하지만 액티비티에 작업 모음이 포함되어 있는 경우에는 DrawerLayout.DrawerListener를 구현하는 대신 ActionBarDrawerToggle 클래스를 확장할 수 있습니다. ActionBarDrawerToggleDrawerLayout.DrawerListener를 구현하기 때문에 여전히 콜백을 재정의할 수 있을 뿐만 아니라 작업 모음 아이콘과 탐색 창 간 상호작용 동작도 원활하게 이루어집니다. 이 내용은 다음 섹션에서 자세히 설명하겠습니다.

탐색 창 디자인 가이드에서 설명한 대로, 창이 표시되어 있을 때 제목을 변경하고 기본 콘텐츠의 상황별 작업 항목을 제거하는 등 작업 모음 콘텐츠를 수정해야 합니다. 다음 코드에서는 ActionBarDrawerToggle 클래스의 인스턴스를 사용하여 DrawerLayout.DrawerListener 콜백 메서드를 재정의하는 방법으로 작업 모음 콘텐츠를 수정하는 방법을 보여줍니다.

public class MainActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    private CharSequence mDrawerTitle;
    private CharSequence mTitle;
    ...

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

        mTitle = mDrawerTitle = getTitle();
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
                R.string.drawer_open, R.string.drawer_close) {

            /** Called when a drawer has settled in a completely closed state. */
            public void onDrawerClosed(View view) {
                super.onDrawerClosed(view);
                getActionBar().setTitle(mTitle);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
            }

            /** Called when a drawer has settled in a completely open state. */
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                getActionBar().setTitle(mDrawerTitle);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
            }
        };

        // Set the drawer toggle as the DrawerListener
        mDrawerLayout.setDrawerListener(mDrawerToggle);
    }

    /* Called whenever we call invalidateOptionsMenu() */
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        // If the nav drawer is open, hide action items related to the content view
        boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
        menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
        return super.onPrepareOptionsMenu(menu);
    }
}

다음 섹션에서는 ActionBarDrawerToggle 생성자 인수와 생성자 인수를 설정하여 작업 모음 아이콘과의 상호작용을 처리할 수 있는 다른 단계에 대해 설명합니다.

앱 아이콘으로 열기 및 닫기

사용자는 화면의 왼쪽 가장자리에서 또는 왼쪽 가장자리로 스와이프하여 탐색 창을 열고 닫을 수 있습니다. 하지만 작업 모음을 사용하는 경우에는 사용자가 앱 아이콘을 터치하는 방법으로도 창을 열고 닫을 수 있도록 지원해야 합니다. 또한 앱 아이콘에서 특수 아이콘을 사용하여 탐색 창이 있다는 것을 표시해야 합니다. 이전 섹션에서 설명한 ActionBarDrawerToggle을 사용하여 이 동작을 모두 구현할 수 있습니다.

ActionBarDrawerToggle을 작동하게 하려면 해당 생성자를 사용하여 인스턴스를 생성합니다. 생성자에는 다음 인수가 필요합니다.

  • 창을 호스팅하는 Activity
  • DrawerLayout
  • 창 표시기로 사용할 드로어블 리소스

    표준 탐색 창 아이콘은 작업 모음 아이콘 팩 다운로드를 통해 이용할 수 있습니다.

  • "창 열기" 작업을 설명하는 문자열 리소스(접근성 용도)
  • "창 닫기" 작업을 설명하는 문자열 리소스(접근성 용도)

그런 다음, ActionBarDrawerToggle의 서브클래스를 창 리스너로 생성했는지 여부에 따라 액티비티 수명 주기 동안 몇 곳에서 ActionBarDrawerToggle을 호출해야 합니다.

public class MainActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    ...

    public void onCreate(Bundle savedInstanceState) {
        ...

        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(
                this,                  /* host Activity */
                mDrawerLayout,         /* DrawerLayout object */
                R.string.drawer_open,  /* "open drawer" description */
                R.string.drawer_close  /* "close drawer" description */
                ) {

            /** Called when a drawer has settled in a completely closed state. */
            public void onDrawerClosed(View view) {
                super.onDrawerClosed(view);
                getActionBar().setTitle(mTitle);
            }

            /** Called when a drawer has settled in a completely open state. */
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                getActionBar().setTitle(mDrawerTitle);
            }
        };

        // Set the drawer toggle as the DrawerListener
        mDrawerLayout.setDrawerListener(mDrawerToggle);

        getActionBar().setDisplayHomeAsUpEnabled(true);
        getActionBar().setHomeButtonEnabled(true);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        // Sync the toggle state after onRestoreInstanceState has occurred.
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Pass the event to ActionBarDrawerToggle, if it returns
        // true, then it has handled the app icon touch event
        if (mDrawerToggle.onOptionsItemSelected(item)) {
          return true;
        }
        // Handle your other action bar items...

        return super.onOptionsItemSelected(item);
    }

    ...
}

탐색 창의 전체 예시를 보려면 페이지 상단에서 샘플을 다운로드하세요.