X

Navigation Drawer con Fragments – Android Design Support Library

Esta entrada es la continuación de la anterior, Navigation Drawer – Android Design Support Library, en la cual vamos a añadir fragmentos, es decir, un Navigation Drawer con Fragments.
Hay que tener en cuenta que aquí solo se mostrará como añadir los fragmentos al tutorial anterior, es decir, solo cambian los 4 primeros puntos del tutorial anterior, el resto es idéntico.
Todo lo que has se va a mostrar en esta entrada también lo puedes seguir ver en el siguiente vídeo de abajo, sobretodo si tenéis problemas en como crear el segundo fragmento.
palette

MATERIAL DESIGN

Especificaciones de un Navigation Drawer con diseño Material Design

PROYECTO EN GITHUB

Puedes descargar el proyecto con todo el código en mi GitHub

Primero vamos a ver el archivo MainActivity.java al completo para ver los cambios respecto al tutorial anterior.

import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.NavigationView;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
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.Toast;

import com.videumcorp.desarrolladorandroid.navigationdrawerandroiddesignsupportlibraryandfragments.fragments.InboxFragment;
import com.videumcorp.desarrolladorandroid.navigationdrawerandroiddesignsupportlibraryandfragments.fragments.StarredFragment;

public class MainActivity extends AppCompatActivity {

    DrawerLayout drawerLayout;
    Toolbar toolbar;
    ActionBar actionBar;

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

        //First start (Inbox Fragment)
        setFragment(0);

    }

    @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) {
                        switch (menuItem.getItemId()) {
                            case R.id.item_navigation_drawer_inbox:
                                menuItem.setChecked(true);
                                setFragment(0);
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                            case R.id.item_navigation_drawer_starred:
                                menuItem.setChecked(true);
                                setFragment(1);
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                            case R.id.item_navigation_drawer_sent_mail:
                                menuItem.setChecked(true);
                                Toast.makeText(MainActivity.this, "Launching " + menuItem.getTitle().toString(), Toast.LENGTH_SHORT).show();
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                            case R.id.item_navigation_drawer_drafts:
                                menuItem.setChecked(true);
                                Toast.makeText(MainActivity.this, "Launching " + menuItem.getTitle().toString(), Toast.LENGTH_SHORT).show();
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                            case R.id.item_navigation_drawer_settings:
                                menuItem.setChecked(true);
                                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;
                    }
                });
    }

    public void setFragment(int position) {
        FragmentManager fragmentManager;
        FragmentTransaction fragmentTransaction;
        switch (position) {
            case 0:
                fragmentManager = getSupportFragmentManager();
                fragmentTransaction = fragmentManager.beginTransaction();
                InboxFragment inboxFragment = new InboxFragment();
                fragmentTransaction.replace(R.id.fragment, inboxFragment);
                fragmentTransaction.commit();
                break;
            case 1:
                fragmentManager = getSupportFragmentManager();
                fragmentTransaction = fragmentManager.beginTransaction();
                StarredFragment starredFragment = new StarredFragment();
                fragmentTransaction.replace(R.id.fragment, starredFragment);
                fragmentTransaction.commit();
                break;
        }
    }
}

Los cambios que hay en este archivo al que había en el tutorial anterior son:

  • Desaparece el TextView en mitad de la pantalla en el cual poníamos el nombre del elemento presionado en el Navigation Drawer.
  • Añadimos abajo del todo el método setFragment.
  • Utilizamos dicho método para llamar a nuestros fragmentos dentro de setupNavigationDrawerContent.

El método setFragment al combinarlo con el switch nos permitirá llamar a los fragmentos que usaremos en esta entrada (en mi caso solo dos, Inbox y Starred, ya que hacer más fragmentos sería simplemente repetir el mismo proceso lo cual es innecesario).

Los fragmentos funcionarán del siguiente modo, se creará un archivo java llamado InboxFragment.java, y un layout asociado al mismo, fragment_inbox.xml. Para crearlos es tan simple como hacer botón derecho con el ratón en sus respectivas carpetas, el java en la carpeta donde se encuentran los archivos java de las actividades, dándole a New y después a Java Class. El xml lo crearemos en la carpeta de los layout del mismo modo, pero esta vez seleccionamos layout resource file. También haremos otros para el elemento Starred del Navigation Drawer.

Estos archivos para explicarlo de un modo sencillo, “simularán” una actividad dentro de tu actividad principal. El xml dará la estética dentro del fragmento, en este ejemplo tendrá un botón y un texto y el java hará los cambios que nosotros queramos, y es que ese botón cambie el texto.

También hay que recordar que utilizamos el setFragment(0) al iniciarse la actividad para que muestre el fragmento de Inbox, ya que sino se quedaría completamente en blanco hasta que el usuario presionase el elemento Inbox en el Navigation Drawer.

Figura 1. Elementos del Navigation Drawer.

Layout activity_main.xml

Cómo hemos hecho antes, vamos a ver el código al completo y lo vamos comparando:

<?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_kitkat_height"
            android:background="?colorPrimary"/>

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="@dimen/status_bar_lollipop_height"
            android:background="?colorPrimaryDark"/>

    </LinearLayout>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/status_bar_margin_top">

        <FrameLayout
            android:id="@+id/fragment"
            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" />

    </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 único cambio aquí es que hemos eliminado el TextView para añadir lo que será nuestro fragmento, el FrameLayout de ID fragment. Cuando presionemos el botón Inbox en el Navigation Drawer, este FrameLayout será “sustituido” por el layout fragment_inbox.layout, mostrando tanto el botón como el texto que mencionamos en el apartado anterior.

Si se presionase Starred, en vez de reiniciar la actividad, simplemente se “sustituiría” otra vez dicho layout por el fragment_starred.xml. Un ejemplo claro de lo que ocurre en este tutorial, es lo que hace la app Gmail al ir presionando los elementos de su Navigation Drawer.

Fragment InboxFragment.java

Primero crea el archivo java, haz botón derecho con el ratón en la carpeta donde están los archivos java, selecciona New y después a Java Class.

Aquí es donde vamos a especificar que queremos que haga el botón dentro del fragmento, en mi caso simplemente cambiará el texto, tanto lo que pone como su color:

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import com.videumcorp.desarrolladorandroid.navigationdrawerandroiddesignsupportlibraryandfragments.MainActivity;
import com.videumcorp.desarrolladorandroid.navigationdrawerandroiddesignsupportlibraryandfragments.R;

public class InboxFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        // Inflate the layout for this fragment
        final View view = inflater.inflate(R.layout.fragment_inbox, container, false);

        ((MainActivity) getActivity()).getSupportActionBar().setTitle("Fragment Inbox");

        Button buttonChangeText = (Button) view.findViewById(R.id.buttonFragmentInbox);

        final TextView textViewInboxFragment = (TextView) view.findViewById(R.id.textViewInboxFragment);

        buttonChangeText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                textViewInboxFragment.setText("This is the Inbox Fragment");
                textViewInboxFragment.setTextColor(getResources().getColor(R.color.md_yellow_800));

            }
        });

        return view;
    }

}

Como se puede ver, se le indica al fragmento que el layout que tiene que utilizar es el fragment_inbox, es decir, el fragment_inbox.xml que crearemos después de termianr con este archivo. Cuando crees el fragmento para la sección Starred, recuerda que tendrás que cambiar también este código dentro de StarredFragment.java para que asocie este java con fragment_starred.xml.

Presta atención que para llamar a la ActionBarToolbar de la actividad principal tienes que especificarlo, utilizando el (MainActivity) como se puede ver en la línea 21.

En la línea 24 podemos ver como se ha declarado el TextView como final, ya que sino, el IDE te dará un error al utilizarlo dentro del método onClick. En el resto de líneas destacadas, 31 y 32, se cambia el texto y el color del TextView al presionar el botón.

Layout fragment_inbox.xml

Crea el archivo xml, haz botón derecho con el ratón en la carpeta layout, selecciona New y después a layout resource file.

Este xml es muy básico y tiene poco que explicar. Simplemente es un RelativeLayout que contendrá nuestros dos elementos mencionados, un Button y un TextView.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="Text inside fragment"
        android:id="@+id/textViewInboxFragment"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="159dp" />

    <Button
        android:backgroundTint="@color/md_yellow_700"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button change text"
        android:id="@+id/buttonFragmentInbox"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

Esto mismo que hemos hecho, lo repetiríamos otra vez, creando otro archivo java y otro xml para el fragmento Starred, y cambiando todo lo que hacía referencia a Inbox por los nuevos archivos de Starred.

De aquí en adelante todo el tutorial es igual que lo que se vio en la entrada anterior.

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:

  1. Para que Navigation Drawer se vea bien en Kitkat
  2. 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í.

Los comentarios de Disqus están cargando....