본문 바로가기

유저 인터페이스

네비게이션 드로어(Navigation Drawer) 사용하기


네비게이션 드로어(Navigation drawer)는 액션바와, 프래그먼트와 함께 애플리케이션의 깊이(Depth)를 크게 줄이고,

사용자에게 조금 더 직관적인 UI를 제공할 수 있는 UI 요소입니다.


네비게이션 드로어의 주요 구성요소 및 주된 상호작용 방법을 그림으로 요약하면 다음과 같습니다.






네비게이션 드로어를 구성하기 위한 절차는 크게 다음과 같습니다.


1. 레이아웃 구성

2. 드로어 내 메뉴 리스너 구현하기

3. 액션바 토글 구현하기 (드로어 열림/닫힘 상태 감지 및 앱 아이콘을 이용한 열기/닫기 지원)



레이아웃 구성


네비게이션 드로어를 사용하려면 DrawerLayout을 최상위 뷰로 사용해야 합니다. DrawerLayout은 안드로이드 표준 플랫폼이 아닌  Support Library v4에 포함된 클래스이므로, Support Library를 받아 빌드 패스에 추가해야 사용할 수 있습니다.

Support library를 빌드 패스에 추가하는 방법은 생략하도록 하겠습니다.


DrawerLayout을 최상위 뷰로 선언한 후에는 주 컨텐츠가 표시될 뷰, 드로어에 표시될 뷰를 차례로 넣어주면 됩니다. 단, 드로어에 표시될 뷰는 필히 android:layout_gravity 속성을 지정해 주어야 합니다. 이 속성값으로 드로어가 왼쪽에서 표시되도록 할지, 혹은 오른쪽에서 표시되도록 할 지 지정할 수 있습니다. (이 부분에 대한 자세한 내용은 아래에서 다루겠습니다)


예를 들어, 주 컨텐츠를 표시할 뷰로 FrameLayout, 드로어에 표시될 뷰로 ListView를 사용할 경우 대략 다음과 같은 구조를 갖게 되지요.


<android.support.v4.widget.DrawerLayout ...>

    <FrameLayout ... />  <!-- 1. 주 컨텐츠를 표시할 뷰 -->

    <ListView ...        <!-- 2. 드로어에 표시될 뷰 -->

        android:layout_gravity="드로어가 표시될 방향" />        

</android.support.v4.widget.DrawerLayout>


여기에서 주의해야 할 점 하나는 바로 드로어로 표시되는 뷰를 반드시 주 컨텐츠를 표시할 뷰보다 나중에 선언하는 것입니다. 만약 이 순서를 지키지 않으면, 드로어 영역이 표시되기는 하지만 드로어 영역을 클릭할 수 없게 되는 해괴한(...) 상황에 빠질 수 있습니다. (사실... 경험담이라는거..ㅠㅠ)


조금 더 빠른 이해를 위해 다음 그림을 한번 봅시다.



위 그림과 같이, DrawerLayout  내에 뷰를 선언하게 되면 마치 종이를 포개놓듯이 뷰들이 하나하나씩 쌓이게 됩니다. 따라서 드로어로 표시될 뷰(위 그림에서는 'Navigation Drawer')를 뒤쪽에 선언하지 않으면 이보다 앞에 있는 뷰가 이벤트를 낚아채서 드로어 영역으로는 이벤트가 전달되지 않는 것이죠.


그럼 이제 간단한 예제를 한번 구현해보겠습니다. 기본 구성은 위에서 본 것과 동일하게 DrawerLayout 내에 주 컨텐츠를 표시할 FrameLayout 하나와 드로어로 표시할 ListView 하나로 구성되어 있습니다.


[activity_main.xml]

<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/dl_activity_main_drawer"
    tools:context=".MainActivity" >

    <FrameLayout android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/fl_activity_main_container" />
    
    <ListView android:layout_width="240dp"
        android:layout_height="match_parent"
        android:id="@+id/lv_activity_main_nav_list"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:background="#EDEDED"/>

</android.support.v4.widget.DrawerLayout>


드로어로 표시되는 ListView의 속성을 살펴보겠습니다. 드로어로 표시되도록 하기 위해 android:layout_gravity 속성을 지정하였습니다. 그런데, 여기에 left, right가 아닌 'start'를 지정했습니다.'start'로 지정하게 되면 시스템 언어 설정에 따라 왼편에서 나오도록 할지, 오른편에서 나오도록 할 지 자동으로 설정해줍니다. 한국어나 영어같이 왼쪽으로 오른쪽으로 쓰는 언어는 'left'로, 아랍어같이 오른쪽에서 왼쪽으로 쓰는 언어는 'right'로 설정됩니다.


이 외에 choiceMode, divider, divederHeight 등은 리스트 모양을 변경하기 위해 사용한 속성으로 드로어 속성과는 큰 관련이 없으므로 넘어가도록 하겠습니다.


드로어 내 메뉴 리스너 구현하기


이제 드로어에 표시되는 메뉴를 클릭했을 때 처리할 작업을 구현할 차례입니다.  예제에서는 드로어 내 메뉴에서 색상을 선택하면 배경 색이 바뀌도록 구현해 보겠습니다. 먼저 각 뷰의 인스턴스와 메뉴 항목으로 표시할 배열을 준비하고, 메뉴 리스너를 설정합니다.


[MainActivity.java]

public class MainActivity extends Activity {

	private String[] navItems = {"Brown", "Cadet Blue", "Dark Olive Green", 
									"Dark Orange", "Golden Rod"};
	private ListView lvNavList;
	private FrameLayout flContainer;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		lvNavList = (ListView)findViewById(R.id.lv_activity_main_nav_list);
		flContainer = (FrameLayout)findViewById(R.id.fl_activity_main_container);
		
		lvNavList.setAdapter(
				new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, navItems));
		lvNavList.setOnItemClickListener(new DrawerItemClickListener());


메뉴 리스너(DrawerItemClickListener)의 구현부입니다. 선택한 항목에 따라 배경 색을 바꿔주고 있습니다.  

private class DrawerItemClickListener implements ListView.OnItemClickListener{ @Override public void onItemClick(AdapterView<?> adapter, View view, int position, long id) { switch(position){ case 0: flContainer.setBackgroundColor(Color.parseColor("#A52A2A")); break; case 1: flContainer.setBackgroundColor(Color.parseColor("#5F9EA0")); break; case 2: flContainer.setBackgroundColor(Color.parseColor("#556B2F")); break; case 3: flContainer.setBackgroundColor(Color.parseColor("#FF8C00")); break; case 4: flContainer.setBackgroundColor(Color.parseColor("#DAA520")); break; } }


여기까지 구현한 후 앱을 실행해보면, 화면 왼쪽 바깥쪽으로부터 안쪽으로 드래그하면 드로어가 표시되고, 드로어 내 항목을 클릭하면 선택한 색상으로 배경색이 변경되는 것을 확인할 수 있습니다.


액션바 토글 구현하기


화면을 드래그하는 제스처로도 네비게이션 바를 표시하거나 숨길 수 있지만, 사용자에게 네비게이션 드로어의 상태를 좀 더 직관적으로 알려주고, 이를 쉽게 조작할 수 있도록 액션바 토글을 구현하는 것을 권장합니다.그리 어렵지 않게 액션바 토글을 구현할 수 있습니다. 


먼저, 다음과 같이 DrawerLayout 및 ActionBarDrawerToggle 인스턴스를 MainActivity 액티비티 내에 선언합니다.

	private DrawerLayout dlDrawer;
	private ActionBarDrawerToggle dtToggle;


다음, DrawerLayout의 인스턴스를 얻고 액션바 토글 인스턴스를 생성합니다.

dlDrawer = (DrawerLayout)findViewById(R.id.dl_activity_main_drawer);
		dtToggle = new ActionBarDrawerToggle(this, dlDrawer, 
				R.drawable.ic_drawer, R.string.open_drawer, R.string.close_drawer){

					@Override
					public void onDrawerClosed(View drawerView) {
						super.onDrawerClosed(drawerView);
					}

					@Override
					public void onDrawerOpened(View drawerView) {
						super.onDrawerOpened(drawerView);
					}
			
		};
		dlDrawer.setDrawerListener(dtToggle);
		getActionBar().setDisplayHomeAsUpEnabled(true);


액션바 토글 생성자에 대한 자세한 설명은 다음과 같습니다.


API

public ActionBarDrawerToggle(Activity activity, DrawerLayout layout, int drawerImageRes, int openDrawerContentDescRes, int closeDrawerContentDescRes)

새로운 액션바 토글 인스턴스를 생성합니다.


activity : 드로어를 포함하는 액티비티

drawerLayout : 액티비티의 액션바와 연동할 드로어

drawerImageRes : 드로어의 상태 표시에 사용할 이미지 (리소스)

openDrawerContentDescRes : '드로어 열기' 에 해당하는 문자열 리소스 (접근성 지원용)

closeDrawerContentDescRes : '드로어 닫기'에 해당하는 문자열 리소스 (접근성 지원용)


여기에서 드로어 상태 표시에 사용할 이미지 (일반적으로 가로줄 세개)는개발자사이트의 네비게이션 드로어 페이지에서 다운로드 할 수 있으니 다운로드하여 사용하면 됩니다. (우측의 Download the Action Bar Icon Pack 이용)


액션바 토글을 사용하면 드로어의 열림,닫힘 상태를 감지할 수 있습니다.  (onDrawerClosed, onDrawerOpened)액션바 토글 인스턴스를 생성한 후, setDrawerListener() 를 통해 드로어와 액션바 토글을 연결해줍니다. 마지막으로, 액션바의 홈 버튼을 업 버튼(setDisplayHomeAsUpEnabled)으로 표시되도록 합니다. 이렇게 해야 홈 버튼을 눌러 드로어를 열고 닫을 수 있게 됩니다.


다음으로 액션바 토글 상태를 지속적으로 동기화하여 표시할 수 있도록 두 개의 생애주기 메서드를 오버라이드하여 다음과 같이 액션바 토글이 해당 상테에 따라 적절한 작업을 수행할 수 있도록 합니다. 그리고, 홈 버튼을 눌렀을 때 액션바 토글에서 모든 이벤트를 처리하고 더 이상 이벤트가 다른 곳으로 전달되지 않도록 하기 위해 onOptionsItemSelected()을 아래와 같이 구현합니다.


	protected void onPostCreate(Bundle savedInstanceState){
		super.onPostCreate(savedInstanceState);
		dtToggle.syncState();
	}
	
	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		super.onConfigurationChanged(newConfig);
		dtToggle.onConfigurationChanged(newConfig);
	}
	
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		if(dtToggle.onOptionsItemSelected(item)){
			return true;
		}
		return super.onOptionsItemSelected(item);
	}


이제, 마지막으로 메뉴 항목을 선택하면 자동으로 드로어가 닫히도록 하기 위해 항목 선택 후 closeDrawer()를 호출하도록 합니다.

	private class DrawerItemClickListener implements ListView.OnItemClickListener{

		@Override
		public void onItemClick(AdapterView<?> adapter, View view, int position,
				long id) {
			switch(position){
			case 0:
				flContainer.setBackgroundColor(Color.parseColor("#A52A2A"));
				break;
			case 1:
				flContainer.setBackgroundColor(Color.parseColor("#5F9EA0"));
				break;
			case 2:
				flContainer.setBackgroundColor(Color.parseColor("#556B2F"));
				break;
			case 3:
				flContainer.setBackgroundColor(Color.parseColor("#FF8C00"));
				break;
			case 4:
				flContainer.setBackgroundColor(Color.parseColor("#DAA520"));
				break;
			}
			dlDrawer.closeDrawer(lvNavList); // 추가됨
		}
		
	}


API

void closeDrawer(View view)

인자로 받은 드로어 뷰를 닫아줍니다. 해당 드로어가 붙어있는 방향(왼쪽 혹은 오른쪽)으로 밀려 사라지게 됩니다.


이것으로 모든 구현이 끝났습니다. 작성한 예제를 실행해 보겠습니다. 예제를 실행한 후, 홈 버튼을 누르거나 화면 왼쪽에서 오른쪽으로 슬라이드하면 다음과 같이 드로어가 왼쪽에서 표시됩니다. 또한, 드로어가 열린 상태이기에 홈버튼 왼쪽 이미지가 약간 왼쪽으로 빨려들어가 있는 것을 볼 수 있습니다.



다음은 드로어 메뉴 중 'Dark Olive Green'을 선택한 모습입니다. 드로어가 사라지면서 선택한 색상이 배경색으로 변합니다. 드로어가 닫혔으므로 홈버튼 왼쪽의 드로어 상태 이미지가 다시 길게 보이는 것을 확인할 수 있습니다.



이것으로 간단한 예제와 함께 Navigation Drawer에 대해 알아보았습니다. 처음에는 이것저것 세팅하는데 다소 어려움이 있겠지만, 몇 번 하다보면 급방 익숙해지리라 생각합니다 :)


예제 프로젝트는 아래 저장소에서 찾으실 수 있습니다.


https://github.com/kunny/blog_samples/tree/master/Android/2013-07-21_Navigation_Drawer_Example