2013년 9월 12일 목요일

Android side menu, Navigation Drawer

이전 블로그에서 이전 함 (원본 글 2013/09/12 작성)

 죄송합니다.
본 게시물은 material design이 나오기 전에 작성되어
부분적으로 오래되어 맞지 않는 사항들이 있습니다.
 다른 게시물을 참고하시기를 부탁 드립니다.





App에서 Android side menu, navigation drawer를 사용하고자 찾아 봤고 
side menu를 구현하고자 할 때 참고되는 것들은 아래와 같음.

: 여러 app들에서 사용되고 있다고 함.

: 일반적인 side menu

Navigation Drawer
: Android 공식적으로 제공하는 Navigation Drawer 일반적인 side menu의 기능을 제공함.



일반 사용자들이 만든 library가 주로 사용되고 있는 듯 하지만
그래도 Android에서 제공하는게 있어 호환성, 유지보수성, 귀찮음 측면에서
이를 그냥 사용하기로 함.


일단 아래 링크를 읽어 보면 다음의 사항을을 확인할 수 있다.
 - navigation drawer의 사용이 필요한 경우
 - navigation drawer의 사용 시 navigation flow에 대한 설명
 - navigation drawer와 action bar간의 상호 동작
 - navigation drawer의 구성 시 권고하는 style에 대한 설명
 - 타 side menu library를 사용 시 Navigation drawer로 변경을 위한 checklist

일반적인 side menu를 사용한 app (facebook, 카카오톡 등)을 사용할 경우 일반적으로 이해가 되는 개념이고 꼭 알아둬야 할 것은 ActionBar와의 연동, Style인것 같다.



일단 실제 동작하는 sample 코드와 설명을 보려면 다음 페이지를 확인해야 한다.

navigation drawer의 태양계 행성을 선택하면 화면에 행성의 이미지가 보이는 간단한 샘플이다.
다소 navigation drawer의 동작이 부자연 스럽지만 그냥 간단히 사용하기는 어렵지가 않아 보인다.



해당 링크에서 Navigation Drawer의 사용에 대한 설명을 간략히 정리 하자면


 <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>

샘플 코드의 Layout을 보면 DrawerLayout이 전체 view를 차지하는 형태이고
화면에 보여줄 content는 FrameLayout내에서 보여지고 Drawer의 메뉴 항목들은 ListView의 항목으로 추가되어 보여지는 형태이다.

위 링크와 코드에서 강조하는 사항들은 다음과 같다.
- DrawerLayout은 top-level content view로써 layout_width, layout_height의 속성은 "match_parent"이어야 한다.
- main content view인 FrameLayout는 DrawerLayout의 첫번째 child가 되어야 하고 DrawerLayout의 전체 view에서 보여줘야 하므로 mach_parent 속성을 가져야 한다.
- Drawer view의 android:layout_gravity를 right-to-left(RTL)을 지원하기 위해서는 "start"를 선택하라고 하는데.. 사실 뭔소리인지.. 
- Drawer의 사이즈는 height는 parent에 맞게 width는 320dp를 넘지 말게 하라라는 권고.


 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);
}

샘플에서는 NavigationDrawer의 메뉴를 선택하면 선택된 행성에 대해서 PlanetFragment를 생성하여 content_frame(FrameLayout)에 있는 기존의 fragment와 교체하는 방법으로 구현되어 있음.


 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.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {

            /** Called when a drawer has settled in a completely closed state. */
            public void onDrawerClosed(View 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) {
                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);
    }
}

위 Navigation Drawer 예제에서는 Drawer의 open, close 시점에 따라 ActionBar의 메뉴를 조절하는 것을 볼 수 있고 이를 위해서 Android에서 제공하는 ActionBarDrawerToggle을 제공하고 있다.

ActionBarDrawerToggle에 대한 class overview는 다음과 같다.

  This class provides a handy way to tie together the functionality of DrawerLayout and the framework ActionBar to implement the recommended design for navigation drawers.
To use ActionBarDrawerToggle, create one in your Activity and call through to the following methods corresponding to your Activity callbacks:
Call syncState() from your Activity's onPostCreate to synchronize the indicator with the state of the linked DrawerLayout afteronRestoreInstanceState has occurred.
ActionBarDrawerToggle can be used directly as a DrawerLayout.DrawerListener, or if you are already providing your own listener, call through to each of the listener methods from your own.

위 class overview에서 강조하는 것 처럼 ActionBar와 NavigationDrawer와 연동 시 status의 동기화(ActionBar 상에서의 indicator를 업데이트 하기 위해) syncState()를 onPostCreate()에서 호출해 주는 것이 중요함.

Clean up the action bar when the drawer is fully expanded. Remove actions that are not needed and display your app's name in the title area.

그리고 참고로 DrawerLayout.DrawerListener는 다음의 methods들로 구성이 되어 있음.

abstract void  onDrawerClosed(View drawerView)
 : Called when a drawer has settled in a completely closed state.
abstract void  onDrawerOpened(View drawerView)
 : Called when a drawer has settled in a completely open state.
abstract void  onDrawerSlide(View drawerView, float slideOffset)
 : Called when a drawer's position changes.
abstract void  onDrawerStateChanged(int newState)
 : Called when the drawer motion state changes.



[관련 링크]

: Stack overflow에서의 Side menu관련 글

: Google IO에서의 Navigation Drawer에 대한 글

댓글 없음:

댓글 쓰기