El Navigation Drawer es una gran forma de organizar tu app ya que permite disponer de todo el espacio en pantalla además de ser conocido por los usuarios al utilizarse en la gran mayoría de las apps, entre ellas las de Google.
Éste último ha publicado durante la I/O 2015 una nueva librería, Android Design Support Library, que permite crear un Navigation Drawer fácilmente entre otros elementos, como pueden ser los botones de acción flotantes (floating action buttons), pestañas (tabs), etc.
Todo lo que has se va a mostrar en esta entrada también lo puedes seguir ver en el siguiente vídeo de abajo.
Contenidos
- Actividad MainActivity.java
- Layout activity_main.xml
- Layout navigation_drawer_header.xml
- Menú navigation_drawer_menu.xml
- Archivos de estilos styles.xml para todas las versiones
- Colores mediante el archivo colors.xml
- Archivos de atributos attrs.xml para todas las versiones
- Drawables utilizados
- Segunda Actividad
Actividad MainActivity.java
Primero vamos a ver el archivo MainActivity.java
al completo para después ir desguazando cada parte del mismo.
import android.content.Intent; import android.os.Bundle; import android.support.design.widget.NavigationView; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends AppCompatActivity { DrawerLayout drawerLayout; Toolbar toolbar; ActionBar actionBar; TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); actionBar = getSupportActionBar(); actionBar.setHomeAsUpIndicator(R.drawable.ic_menu_white_24dp); actionBar.setDisplayHomeAsUpEnabled(true); drawerLayout = (DrawerLayout) findViewById(R.id.navigation_drawer_layout); NavigationView navigationView = (NavigationView) findViewById(R.id.navigation_view); if (navigationView != null) { setupNavigationDrawerContent(navigationView); } setupNavigationDrawerContent(navigationView); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: drawerLayout.openDrawer(GravityCompat.START); return true; } return super.onOptionsItemSelected(item); } private void setupNavigationDrawerContent(NavigationView navigationView) { navigationView.setNavigationItemSelectedListener( new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem menuItem) { textView = (TextView) findViewById(R.id.textView); switch (menuItem.getItemId()) { case R.id.item_navigation_drawer_inbox: menuItem.setChecked(true); textView.setText(menuItem.getTitle()); drawerLayout.closeDrawer(GravityCompat.START); return true; case R.id.item_navigation_drawer_starred: menuItem.setChecked(true); textView.setText(menuItem.getTitle()); drawerLayout.closeDrawer(GravityCompat.START); return true; case R.id.item_navigation_drawer_sent_mail: menuItem.setChecked(true); textView.setText(menuItem.getTitle()); drawerLayout.closeDrawer(GravityCompat.START); return true; case R.id.item_navigation_drawer_drafts: menuItem.setChecked(true); textView.setText(menuItem.getTitle()); drawerLayout.closeDrawer(GravityCompat.START); return true; case R.id.item_navigation_drawer_settings: menuItem.setChecked(true); textView.setText(menuItem.getTitle()); Toast.makeText(MainActivity.this, "Launching " + menuItem.getTitle().toString(), Toast.LENGTH_SHORT).show(); drawerLayout.closeDrawer(GravityCompat.START); Intent intent = new Intent(MainActivity.this, SettingsActivity.class); startActivity(intent); return true; case R.id.item_navigation_drawer_help_and_feedback: menuItem.setChecked(true); Toast.makeText(MainActivity.this, menuItem.getTitle().toString(), Toast.LENGTH_SHORT).show(); drawerLayout.closeDrawer(GravityCompat.START); return true; } return true; } }); } }
Lo primero que vemos arriba son todos los elementos que se han importado, hay que tener cuidado ya que algunas veces cuando añades una, por ejemplo, Toolbar
, en vez de importarte import android.support.v7.widget.Toolbar;
te importa otro elemento diferente. Esto ocurre con varias de ellos.
Lo siguiente que hacemos es declarar los elementos necesarios dentro de nuestra case principal, pero fuera del método onCreate
:
DrawerLayout drawerLayout; Toolbar toolbar; ActionBar actionBar; TextView textView;
En el método onCreate
damos soporte a la Toolbar
que más adelante añadiremos al layout activity_main
.xml
, aparte de mostrar el icono característico de las tres rayas paralelas en la ActionBar
o Toolbar
(como queráis llamar a la barra superior de la app).
toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); actionBar = getSupportActionBar(); actionBar.setHomeAsUpIndicator(R.drawable.ic_menu_white_24dp); actionBar.setDisplayHomeAsUpEnabled(true);
El código restante dentro del método onCreate
son los engranajes para hacer funcionar al navigation drawer mediante el nuevo View
llamado NavigationView
. Básicamente hacemos referencia al mismo, y para que quede más limpio el código, creamos un método llamado setupNavigationDrawerContent
, en el cual le pasamos la instancia que hemos creado navigationView
. El método lo colocaremos fuera del método onCreate
siendo el siguiente:
private void setupNavigationDrawerContent(NavigationView navigationView) { navigationView.setNavigationItemSelectedListener( new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem menuItem) { textView = (TextView) findViewById(R.id.textView); switch (menuItem.getItemId()) { case R.id.item_navigation_drawer_inbox: menuItem.setChecked(true); textView.setText(menuItem.getTitle()); drawerLayout.closeDrawer(GravityCompat.START); return true; case R.id.item_navigation_drawer_starred: menuItem.setChecked(true); textView.setText(menuItem.getTitle()); drawerLayout.closeDrawer(GravityCompat.START); return true; case R.id.item_navigation_drawer_sent_mail: menuItem.setChecked(true); textView.setText(menuItem.getTitle()); drawerLayout.closeDrawer(GravityCompat.START); return true; case R.id.item_navigation_drawer_drafts: menuItem.setChecked(true); textView.setText(menuItem.getTitle()); drawerLayout.closeDrawer(GravityCompat.START); return true; case R.id.item_navigation_drawer_settings: menuItem.setChecked(true); textView.setText(menuItem.getTitle()); Toast.makeText(MainActivity.this, "Launching " + menuItem.getTitle().toString(), Toast.LENGTH_SHORT).show(); drawerLayout.closeDrawer(GravityCompat.START); Intent intent = new Intent(MainActivity.this, SettingsActivity.class); startActivity(intent); return true; case R.id.item_navigation_drawer_help_and_feedback: menuItem.setChecked(true); Toast.makeText(MainActivity.this, menuItem.getTitle().toString(), Toast.LENGTH_SHORT).show(); drawerLayout.closeDrawer(GravityCompat.START); return true; } return true; } }); }
Cómo se puede observar en Figura 1, básicamente estamos haciendo referencia a todos los elementos que habrá en el Nacivigation Drawer. Así, con el switch
, cuando el usuario seleccione uno de los elementos, ocurrirá lo siguiente para cada uno de ellos:
- Se marcará como seleccionado el ítem en cuestión gracias a
menuItem.setChecked(true)
. - El
TextView
que se encuentra en mitad de la pantalla cambiará de nombre, obteniendo el mismo del título del ítem presionado,textView.setText(menuItem.getTitle());
. - Se cerrará el navigation drawer mediante
drawerLayout.closeDrawer(GravityCompat.START);
.
- En el caso del elemento
Settings
, también se va a iniciar otra actividad, como se puede ver en las líneas destacadas:
case R.id.item_navigation_drawer_settings: menuItem.setChecked(true); textView.setText(menuItem.getTitle()); Toast.makeText(MainActivity.this, "Launching " + menuItem.getTitle().toString(), Toast.LENGTH_SHORT).show(); drawerLayout.closeDrawer(GravityCompat.START); Intent intent = new Intent(MainActivity.this, SettingsActivity.class); startActivity(intent); return true;
Y para finalizar, el código siguiente es para abrir el navigation drawer si se presiona sobre el botón de las tres rayas en la Toolbar, mediante el siguiente método:
@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: drawerLayout.openDrawer(GravityCompat.START); return true; } return super.onOptionsItemSelected(item); }
Layout activity_main.xml
Cómo hemos hecho antes, vamos a ver el código al completo y lo vamos desguazando:
<?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/navigation_drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="@bool/fitsSystemWindows"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <FrameLayout android:layout_width="match_parent" android:layout_height="@dimen/status_bar_height" android:background="?colorPrimary"/> </LinearLayout> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/status_bar_height"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?actionBarSize" android:background="?attr/colorPrimary" android:elevation="4dp" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:theme="@style/ToolbarTheme" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Inbox" android:textAppearance="@style/TextAppearance.AppCompat.Display1" android:textColor="@color/md_text" /> </FrameLayout> <android.support.design.widget.NavigationView android:id="@+id/navigation_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="@bool/fitsSystemWindows" app:headerLayout="@layout/navigation_drawer_header" app:menu="@menu/navigation_drawer_menu" app:theme="@style/NavigationViewTheme" /> </android.support.v4.widget.DrawerLayout>
El contenedor principal es, evidentemente, un DrawerLayout
, con un atributo que hay que mencionar por ser muy importante, que es el android:fitsSystemWindows="true"
, para evitar que los elementos de la pantalla superpongan las barras del sistema. El DrawerLayout
se encarga de poder tener un Navigation Drawer además de contener el resto de elementos de la actividad principal.
Dentro del DrawerLayout
se puede ver que hay un FrameLayout
que contiene una Toolbar,
un TextView, y el elemento estrella, un NavigationView
. En este es en el que nos tenemos que centrar en esta entrada, para los otros dos visita las páginas correspondientes o echa un vistazo al vídeo en el que explicaré más cosas que aquí.
<android.support.design.widget.NavigationView android:id="@+id/navigation_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="@bool/fitsSystemWindows" app:headerLayout="@layout/navigation_drawer_header" app:menu="@menu/navigation_drawer_menu" app:theme="@style/NavigationViewTheme" />
Vamos a ver cada uno de los nuevos atributos del NavigationView
:
- El primer atributo que quiero destacar es el
android:fitsSystemWindows="@bool/fitsSystemWindows"
, ya que como veis no le estamos dando un valortrue
ofalse
, le estamos indicando que le daremos el valor@bool/fitsSystemWindows
, este valor lo determinaremos nosotros en los archivosattrs.xml
que haremos para cada versión, ya que casi todas las versiones necesita que seaTrue
, excepto para KitKat es necesario que seaFalse
. También verás en laToolbar
o en elFrameLayout
que también utilizamos esto, además de una altura que variará en elFrameLayout
y en el margen superior de otroFrameLayout
. Como he dicho antes, visita el vídeo si no te queda claro el por qué lo he hecho. - El atributo relevante es
app:headerLayout="@layout/navigation_drawer_header"
se encarga de permitirte que puedas añadir un header (la parte superior clásica con la imagen, el avatar, etc) al navigation drawer, este header lo tienes que crear en otrolayout
que verás más adelante y que se llamaránavigation_drawer_header.xml
. - El atributo
app:menu="@menu/navigation_drawer_menu"
es para indicarle los elementos que tendrá el navigation drawer. Es aquí donde se encuentra la magia ya que te evitas tener que hacer unRecyclerView
o unListView
además de tener que personalizar cada item para que cumpla las especificaciones de material design. - El último atributo
app:theme="@style/NavigationViewTheme"
lo he añadido yo personalmente ya que, por ahora, los textos están utilizando por defecto la fuente Roboto Regular, y si has visitado las especificaciones de material design para el navigation drawer, verás que debería ser Roboto Medium. Con este método lo arreglamos después en nuestro archivostyles.xml
. Es probable que esto más adelante no haga falta ya que lo arregle Google por defecto.
Archivos de estilos styles.xml para todas las versiones
Aquí básicamente tenemos los elementos necesarios para dar los colores y el formato necesario para todas las versiones. El arreglo que mencioné anteriormente de que es necesario que el Navigation Drawer muestre los elementos con la letra Roboto Medium en vez de Roboto Regular es el siguiente:
<style name="NavigationViewTheme" parent="AppTheme"> <item name="textAppearanceListItem">@style/TextAppearance.AppCompat.Body2</item> </style>
Como se puede ver, en la v19 le indicamos que la status bar sea translucida y en la v21 que sea transparente además del otro atributo para que en el navigation drawer se muestre el header debajo de la status bar. Como veis, aunque le decimos que la barra tiene que ser transparente en la v21, la librería ya se encarga de añadir el translucido cuando el navigation drawer se abre.
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppTheme" parent="Base.AppTheme"> </style> <style name="Base.AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">@color/md_indigo_500</item> <item name="colorPrimaryDark">@color/md_indigo_700</item> <item name="colorAccent">@color/md_pink_500</item> <item name="colorControlHighlight">@color/md_indigo_500_25</item> <item name="android:windowBackground">@color/md_white_1000</item> </style> <style name="ToolbarTheme" parent="AppTheme"> <item name="android:textColorPrimary">@android:color/white</item> <item name="android:textColorSecondary">@android:color/white</item> </style> <style name="NavigationViewTheme" parent="AppTheme"> <item name="textAppearanceListItem">@style/TextAppearance.AppCompat.Body2</item> </style> </resources>
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppTheme" parent="Base.AppTheme"> <item name="android:windowTranslucentStatus">true</item> </style> </resources>
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppTheme" parent="Base.AppTheme"> <item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:statusBarColor">@android:color/transparent</item> </style> </resources>
Colores mediante el archivo colors.xml
Los colores utilizados los puedes añadir tanto de mi GitHub como visitando esta página sobre Color en Material Design. Simplemente crea un archivo colors.xml
en app/res/values/colors.xml
.
Los strings son los que se crean por defecto al ir creando cada uno de los elementos.
<resources> <string name="app_name">Navigation Drawer</string> <string name="hello_world">Hello world!</string> <string name="action_settings">Settings</string> <string name="title_activity_settings">Settings</string> </resources>
Archivos de atributos attrs.xml para todas las versiones
Estos archivos son para arreglar lo que hemos ido mencionando a lo largo de la entrada:
- Para que Navigation Drawer se vea bien en Kitkat
- Para que el avatar del header se vea bien en versiones de Android con barra translucida y negra.
<?xml version="1.0" encoding="utf-8"?> <resources> <bool name="fitsSystemWindows">true</bool> <dimen name="navigation_drawer_header_margin">16dp</dimen> <dimen name="status_bar_height">0dp</dimen> </resources>
<?xml version="1.0" encoding="utf-8"?> <resources> <bool name="fitsSystemWindows">false</bool> <dimen name="navigation_drawer_header_margin">41dp</dimen> <dimen name="status_bar_height">25dp</dimen> </resources>
<?xml version="1.0" encoding="utf-8"?> <resources> <bool name="fitsSystemWindows">true</bool> <dimen name="navigation_drawer_header_margin">41dp</dimen> <dimen name="status_bar_height">0dp</dimen> </resources>
Drawables utilizados
Los drawables utilizados los puedes encontrar en la siguiente carpeta de GitHub.
Además, recomiendo visitar las siguientes páginas para obtener más recursos:
Segunda Actividad
Cómo se ha dicho, el elemento del navigation drawer Settings, inicia una segunda actividad, por lo que es necesario crearla, aunque no tiene nada de especial, simplemente os dejo aquí los cambios que he añadido al SettingsActivity.java
y al activity_settings.xml
, ya que el archivo menu_settings
no ha sido modificado.
import android.os.Build; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.Toolbar; import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; public class SettingsActivity extends AppCompatActivity { Toolbar toolbar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_settings); toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); TypedValue typedValueColorPrimaryDark = new TypedValue(); SettingsActivity.this.getTheme().resolveAttribute(R.attr.colorPrimaryDark, typedValueColorPrimaryDark, true); final int colorPrimaryDark = typedValueColorPrimaryDark.data; if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(colorPrimaryDark); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_inbox, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?actionBarSize" android:background="?attr/colorPrimary" android:elevation="4dp" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:theme="@style/ToolbarTheme" /> <TextView android:textAppearance="@style/TextAppearance.AppCompat.Display1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Settings" android:textColor="@color/md_text" /> </FrameLayout>
Y ya está todo listo, tu navigation drawer debería funcionar correctamente si lo ejecutas.
Puedes ver más información sobre Navigation Drawer en la página oficial de Google en inglés pulsando aquí.