태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

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

2013.11.05 01:02


네비게이션 드로어(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



저작자 표시 비영리 변경 금지
신고

커니 유저 인터페이스 , , , , , , , , , , ,

  1. 이전 댓글 더보기
  2. Blog Icon
    구글CEO

    안녕하세요
    덕분에 많이 배워가는거같습니다.!
    근데 제가 FragmentActivity형태의 클래스를를 첫화면으로 해서 보여주려고하는데 가능한가요?

  3. 네, 가능합니다.

  4. Blog Icon
    시은아빠


    안녕하세요 덕분에 많이 배우고 있습니다.
    저는 예문 보고 그대로 해보고 있는데 onPostCreate 메소드의 dtToggle.syncState(); 에서 계속 NullPointException이 발생해서 원인을 찾고 있습니다.
    혹시 예상되시는 해결 포인트 있으시면 좀 가르쳐 주세요..^^

  5. onCreate() 메서드에서 dtToggle에 객체가 제대로 할당되지 않은 것 같습니다. 해당 부분을 다시 한 번 확인해 보는 것이 좋을 듯 합니다.

  6. Blog Icon
    Good

    안녕하세요.
    타이틀바 폰트, 색 등을 변경 하고싶은데요
    네이게이션때문인지 requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); 요게 안되네요..
    아직 부족하여 설명도 잘 못하겠네요^^;;
    혹시 다른 방법으로 구현해야하는거면 조언 부탁드릴게요

  7. 말씀주신 방법은 구버전 용이며, 액션 바의 속성을 바꾸는 방법으로 구현해야 합니다.
    https://developer.android.com/training/basics/actionbar/styling.html 을 참고하세요.

  8. Blog Icon
    지나가는행인

    커니님~ focus 관련해서 질문 하나 드릴께요.
    액션바 토글 선택해서 onDrawerOpened() 실행 될 때 메인컨텐츠 부분에는 setFocusable(false)처리 하고 리스트뷰에 requsetFocus()로 가도록 하였는데요.
    BT키보드로 리스트뷰 젤 아래까지 내려왔다가 다시 맨 위로 올라오면 액션바 토클 버튼으로 가고,
    그 이후에 방향키를 아래로 내리면 리스트뷰로 다시 내려가지 않네요.
    혹시 도움 주실 만한 내용 있으신지요 흑 ㅠ

  9. android:nextFocusDown 속성을 사용해 보는 것은 어떨까요?

  10. Blog Icon
    도와주세요

    FrameLayout 안에 리스트뷰1개밖에 못넣나요? 2종류로 구현하고싶으면 어찌해야대나요?

  11. FrameLayout은 실질적으로 하나의 요소를 담기 위해 사용하며, 두 개 이상의 요소로 레이아웃을 구성하려면 LinearLayout이나 RelativeLayout 등을 사용해야 합니다.

  12. Blog Icon
    Help

    네비게이션 드로어를 보통
    frgManager.beginTransaction().replace(R.id.fl_activity_main, fragment).commit();
    이러한 방식으로 구현하잖아요. 네비게이션 리스트 안에 들어가는 항목들을 fragment 상속받아서 사용하던데 fragmentActivity 로 사용하는 방법은 없을까요??

    MainActivity 말고 다른 항목으로 넘어갈때 사용하는 fragment들을 fragmentActivity 로 상속받아서 네비게이션 드로어 사용하는 방법을 알고 싶습니다 ㅠㅠㅠㅠ

  13. Blog Icon
    질문

    ListView 대신에
    RelativeLayout을 사용하여 button등을 넣고 싶은데
    ListView밖에 사용 못하는건가요?

  14. ListView 말도 다른 뷰도 얼마든지 사용 가능합니다~

  15. Blog Icon
    철이28호

    안녕하세요. 커니님.
    항상 도움 많이 받고 있습니다.

    DrawerLayout 이용 중 궁금한 부분이 있습니다.

    왼쪽에 보이는 ListView 항상 살짝은 보이게 처리하고 싶은데요.
    어떻게 처리를 해야할지 모르겠네요.
    DrawerLayout 을 상속 받아 커스텀을 해야하나요..?
    어떤 방법이 있을지 가르침 부탁 드립니다.

  16. 네, DrawerLayout을 커스텀 하셔야 할 것 같습니다. 자세한 내용은 저도 작업해 본 적이 없는지라 잘 모르겠네요 ㅠ.ㅠ

  17. 안녕하세요?
    정말 많이 보고 배우고 있습니다.
    제가 궁금한게 있어서 질문들 드립니다.
    토글을 넣어서 하고 있는데 커니님 사진 처럼 토글 버튼이 작게 표시되고 변경이 없어야 하는데 제가 만든 어플은 이상하게 토글 버튼을 누르면 화살표 표시로 바뀌고 자리 차지도 너무 많이 해서 보기가 안좋은데 해결하는 방법있을까요?

  18. 해당 코드가 업데이트 되어서 그렇습니다~
    support.v4.widget.ActionBarDrawerToggle 을 사용하시면 위와 같이 표시됩니다.

  19. Blog Icon
    김동수

    안녕하세요. 올리신 강의 많은 도움이 되고 있습니다.
    한가지 문의 드릴께 있는데요.

    NavagationDrawewr의 구성을 상단 리스트 항목은 동적으로 목록 리스트(예를 들면 사과, 배, 감 .....)으로 보여지게 하고 바로 밑에는 "설정" 이라든지 "목록 관리 "등과 같이 고정된 항목으로 보여주고자 합니다. 이경우 두개의 Listview를 사용해서 가능한지 문의 드립니다. 제가 해보니 첫번째 ListView만 보여지고, 두번째 ListView는 안보여지네요. 하나의 ListView만 사용해야 하는지 궁금하네요. 감사합니다.

  20. 리스트뷰를 두개로 사용할 수는 없습니다. 대신, 어댑터를 커스텀하여 하나의 리스트뷰에 각각 다른 두 개의 데이터가 표시되도록 해야 합니다.

    아래 글을 참고해보세요~
    http://jsharkey.org/blog/2008/08/18/separating-lists-with-headers-in-android-09/

  21. DrawerLayout 과 PulltoRefresh로 주 컨텐츠를 감싸서 예제를 적용하고 있는데요
    토글버튼 적용이 되질 않습니다.
    protected void onPostCreate(Bundle savedInstanceState){
    super.onPostCreate(savedInstanceState);
    drawerToggle.syncState();// 이 부분에서
    }
    Caused by: java.lang.NullPointerException
    at android.support.v4.app.ActionBarDrawerToggle.syncState(ActionBarDrawerToggle.java:251)
    at com.example.dell.assuk.Activity.TimelineActivity.onPostCreate(TimelineActivity.java:177)
    라고 로그캣이 찍히는데요. drawerToggle의 문제는 아닙니다. 초기화도 했구요. 디버깅을 해봤지만 왜 NullPointerException인지 모르겠습니다 혹시 도움을 주실 수 있으신가요?

  22. 음 글쎄요.... 초기화를 해 주었는데 중간에 인스턴스가 다시 null로 되어있다면 좀 더 디버깅이 필요할 것 같습니다.
    PullToRefresh와 DrawerLayout은 저도 같이 써보지 않아서 큰 도움을 드리긴 어렵겠네요.

  23. Blog Icon
    윤해성

    안녕하세요. 강좌 잘 보고 있습니다만, 막히는 부분이 발생하여 여쭙습니다. 혹시 drawerlayout을 맵뷰위에서 구현해보신적이 있으신가용? 현재 진행중인 프로젝트가 맵뷰를 기본으로 깔고 좌측에서 메뉴가 나오는 형태인데요. 갤럭시5등 최신폰에서는 크게 문제가 되지 않으나 옛날 모델의 경우 맵뷰아래로 drawerlayout이 깔려버립니다. 드로어 오픈 시 터치 등 이벤트는 동작을 하지만 맵 화면이 위에 덮혀 있습니다. ㅜㅜ 혹시 조언 가능하시면 부탁 좀 드리겠습니다.

  24. Blog Icon
    초보

    정말 잘 보고있습니다.
    한자기 궁금한점이 있는데
    dtToggle = new ActionBarDrawerToggle(this, dlDrawer,
    R.drawable.abc_btn_check_material, R.string.open_drawer, R.string.close_drawer)

    이부분 제가 R.drawable에 ic_drawer 이게 없어서 저걸로 대체를 했는데 적용할수 없다고 오류가 나네요 ㅠㅠ
    R.drawable에 ic_drawer가 없으면 어떡해야 되나요?

  25. Blog Icon
    ㅜㅜ

    navigation drawer activity 를 사용한것인지 빈 액티비티에 하신것인지 여쭙니다.
    혹시 navigation drawer activity를 사용한것이라면
    ListView 안에 다른 layout (Table..) 같은 것은 activity_main.xml에 넣은것인지 fragment_navigation_drawer.xml에 넣은것인지도 여쭤요..

  26. Blog Icon
    ㅜㅜ

    navigation drawer activity 를 사용한것인지 빈 액티비티에 하신것인지 여쭙니다.
    혹시 navigation drawer activity를 사용한것이라면
    ListView 안에 다른 layout (Table..) 같은 것은 activity_main.xml에 넣은것인지 fragment_navigation_drawer.xml에 넣은것인지도 여쭤요..

  27. Blog Icon
    밀ㅋ

    감사합니다.

  28. Blog Icon
    초보

    안녕하세요 강좌 잘보고 있습니다. 다름이 아니라 push 알림(yes/no)이 왔을때 해당하는 뷰로 activity를 띄우려고 하는데 네비게이션 드로어를 사용하게 되면 처음 fragment가 표시된 activity가 띄워지던데 혹시 fragment를 바꿔서 띄우는 방법이 있나요?

  29. 액티비티를 호출하는 인텐트에 부가정보(Extra 등)을 사용해서, 그 부가정보 값을 참조하여 원하는 프래그먼트를 띄우는 방식으로 구현하면 될 듯 합니다.

  30. Blog Icon
    아~

    R.string.open_drawer, R.string.close_drawer

    이부분에서 에러가나는데 어떤걸 추가 시켜줘야하나요???

  31. Blog Icon
    test

    토글의 위치를 변경할수는없나요??
    왼쪽 상단에 있는 리스트 모양(토글)의 위치를 오른쪽으로 변경하려고 하는데 잘안되네요 ㅠ
    좋은 방법있으신가요?

  32. Blog Icon
    기드래곤

    ic_drawer 를 어떤 파일로만들어서 drawble에 넣어야되나요?