Naps Tecnología y educación

Menú lateral que abre un CardView en Android

Aprenderemos a programar un menú lateral que abre un CardView en Android. Una vez que tenemos elaborado un menú lateral, de seguro queremos que al darle clic a alguna de sus opciones, nos realice alguna acción. En éste artículo veremos cómo mostrar un CardView utilizando el componente RecyclerView.

Éste artículo está explicado en video: https://youtu.be/PINqvwe8z54

Qué es un CardView

Un CardView nos permitirá presentar información de una forma coherente y organizada (Griffits, 2015).

Fig. 1. CardView que desarrollaremos

En el ejemplo anterior, se tiene un artículo que forma parte de una lista de artículos, cada uno tiene un título (“¿Qué es el sobrepeso?”), un subtítulo (“Y en qué es diferente de la obesidad”), una imagen, la leyenda Saber más junto con un icono de flecha hacia abajo, y un botón que abre un menú contextual.

Qué es un RecyclerView

RecyclerView es una de las vistas más comúnmente usadas en Android. Viene siendo una versión más avanzada de ListView. Es un contenedor que permite administrar un conjunto grande de datos o información, al reciclarlos eficientemente a medida que la lista es desplazada hacia arriba o hacia abajo (Guleria, 2019).

Pasos para programar un menú lateral que abre un CardView

En los siguientes pasos, considero que ya tienes desarrollado un Menú Lateral. En caso de que no fuese así, te invito a realizarlo siguiendo los pasos que se muestran en el siguiente enlace: Pasos para desarrollar un menú en una app Android.

Veamos ahora los pasos para programar el menú lateral que abre un CardView

Paso 1. Añadir las dependencias que utilizaremos

Abre tu archivo build.gradle (Module:app) y en la sección dependencies añade las siguientes:

implementation 'com.android.support:cardview-v7:27.1.1'
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.squareup.picasso:picasso:2.5.2'

Paso 2. Crea el modelo de datos que deseas mostrar usando CardView

El modelo de datos es una nueva clase Java. Ubica el paquete donde crearás ésta clase. Puede ser tu com o un package dentro de tu com. Por ejemplo:

En la imagen de arriba, la clase que creamos se llama Dato y está dentro de un package llamdo Models. (Pero puede estar en el com.naps.aaa.bbb). Los packages Activities, Adapter, Clases y Fragments los he creado para organizar mejor la aplicación.

Da clic derecho y selecciona New->Java Class.

El código de la clase Dato.java es el siguiente:

public class Dato {
    private String titulo;
    private String subtitulo;
    private int imagen;
    private String contenido;

    // Constructor de la clase
    public Dato(String titulo, String subtitulo, int imagen, String contenido) {
        this.titulo = titulo;
        this.subtitulo = subtitulo;
        this.imagen = imagen;
        this.contenido = contenido;
    }

    // Metodos setter y getter
    public String getSubtitulo() {
        return subtitulo;
    }

    public void setSubtitulo(String subtitulo) {
        this.subtitulo = subtitulo;
    }

    public String getTitulo() {
        return titulo;
    }

    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }

    public void setContenido(String contenido){
        this.contenido = contenido;
    };

    public int getImagen() {
        return imagen;
    }

    public String getContenido() {
        return contenido;
    }

    public void setImagen(int imagen) {
        this.imagen = imagen;
    }

}

Paso 3. Crea el fragment donde aparecerá el CardView

Clic derecho sobre el package donde vas a crear el fragment luego en New->Fragment->Fragment (Blank)

Paso 4. Modificar el layout fragment_informacion.xml

Abre el archivo fragment_informacion.xml que se encuentra dentro de res->layout

En éste archivo agregaremos un componente RecyclerView. El archivo quedará más o menos de la siguiente forma (considera que la linea que indica el context cambiará en tu proyecto):

<FrameLayout 
    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"
    tools:context="com.naps.gibgarcia.npsalud.Fragments.InformacionFragment">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"/>

</FrameLayout>

Deberás agregar las líneas del componente RecyclerView únicamente.

Paso 5. Crear un layout para los item de información

Dentro del RecyclerView irán los item de información con el diseño que mostramos arriba, en la Fig. 1.

Vamos a diseñar ese layout.

En res->layout, da clic derecho, luego en New->Layout resource file

Como nombre de archivo, usaré list_item_informacion.xml

A continuación te muestro cómo quedó el código que utilicé.

Se crea un componente CardView. Dentro, va un LinearLayout vertical. Éste LinearLayout tiene tres elementos: un Toolbar (que contiene el título y el subtítulo), un ImageView (para la imagen), otro LinearLayout (ésta vez horizontal), que contiene un TextView (que lleva la frase “Saber más”), y un ImageView (con una flecha hacia abajo). Notarás otro LinearLayout, y es el que contiene la información que aparecerá cuando se le de clic a la flecha hacia abajo para “saber más”.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/cardView"
    android:layout_margin="16dp"
    app:cardUseCompatPadding="true"
    app:cardCornerRadius="4dp"
    tools:context="com.naps.gibgarcia.npsalud.Fragments.InformacionFragment">

<!--  app:cardBackgroundColor="#E2E2E2"  -->

    <LinearLayout
        android:id="@+id/linearLayoutCardContent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbarCard"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/white"
            app:popupTheme="@style/Theme.AppCompat.Light"
            app:subtitleTextAppearance="@style/Card.Subtitle"
            app:theme="@style/ToolbarCard"
            app:titleTextAppearance="@style/Card.Title" />

        <ImageView
            android:id="@+id/imageViewPoster"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:adjustViewBounds="true"
            android:contentDescription="contentDescription"
            android:src="@drawable/blur" />

        <LinearLayout

            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/unlinearl"
            android:orientation="horizontal"
            android:padding="8dp">

            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:layout_weight="1"
                android:gravity="center_vertical"
                android:text="Saber más"
                android:textColor="@android:color/black"
                android:textSize="20sp" />

            <ImageView
                android:id="@+id/imageViewExpand"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"
                android:contentDescription="Details"
                android:src="@drawable/more" />
        </LinearLayout>


        <LinearLayout
            android:id="@+id/linearLayoutDetails"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:paddingLeft="8dp"
            android:visibility="gone">

            <TextView
                android:id="@+id/textViewInfo"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Infoo"
                android:textColor="@android:color/black"
                android:textSize="16sp" />
        </LinearLayout>


    </LinearLayout>



</android.support.v7.widget.CardView>

Notas que utiliza algunos estilos que aún no están establecidos. Vamos a crearlos en el archivo res->values->styles.xml

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
    <style name="ToolbarCard" 
        parent="ThemeOverlay.AppCompat.ActionBar">
        <item name="colorPrimary">@android:color/black</item>
        <item name="android:textColorPrimary">@android:color/black</item>
    </style>
    <style name="Card.Title" 
        parent="TextAppearance.Widget.AppCompat.Toolbar.Title">
        <item name="android:textSize">24sp</item>
    </style>
    <style name="Card.Subtitle" 
        parent="TextAppearance.Widget.AppCompat.Toolbar.Subtitle">
        <item name="android:textSize">16sp</item>
        <item name="android:textColor">@android:color/darker_gray</item>
    </style>
</resources>

Tú solo agrega las líneas correspondientes a los 3 estilos ToolbarCard, Title y Subtitle.

Paso 6. Crear un Adapter para el RecyclerView

Se necesita un adapter que realice un enlace entre los datos que deseamos mostrar, y  la vista que los desplegará en pantalla, en éste caso un RecyclerView.

Damos clic derecho sobre el package donde lo crearemos, luego en New->Java Class, y como nombre, usaré InformacionAdapter.

// Hereda de RecyclerView.Adapter
public class InformacionAdapter extends RecyclerView.Adapter<InformacionAdapter.ViewHolder> {
    // Una lista de objetos "Dato"
    private List<Dato> datos;
    // id del layout list_item_informacion
    private int layout;
    private OnItemClickListener itemClickListener;
    private Context context;
    private static final int DURATION = 250;    
    
    public InformacionAdapter(List<Dato> datos, int layout, OnItemClickListener listener) {
            this.datos = datos;
            this.layout = layout;
            this.itemClickListener = listener;
            }
    
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View v = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false);
            context = parent.getContext();
            ViewHolder vh = new ViewHolder(v);
            return vh;
            }
    
    @Override
    public void onBindViewHolder( ViewHolder holder, int position) {
            holder.bind(datos.get(position), itemClickListener);
            }
    
    @Override
    public int getItemCount() {
            return datos.size();
            }
    
    public  class ViewHolder extends  RecyclerView.ViewHolder{
        // Elementos de diseño de list_item_informacion.xml
        public ImageView imageViewPoster;
        public ViewGroup linearLayoutDetails;
        public Toolbar toolbarCard;
        public LinearLayout unlinearl;
        public ImageView imageViewExpand;
        public TextView textViewInfo;
    
        public ViewHolder(View itemView){
            super(itemView);
            // Enlazamos los elementos de la vista
            imageViewPoster = itemView.findViewById(R.id.imageViewPoster);
            linearLayoutDetails =  itemView.findViewById(R.id.linearLayoutDetails);
            toolbarCard =  itemView.findViewById(R.id.toolbarCard);
            unlinearl =  itemView.findViewById(R.id.unlinearl);
            imageViewExpand =  itemView.findViewById(R.id.imageViewExpand);
            textViewInfo =  itemView.findViewById(R.id.textViewInfo);
        }
    
    
        public void bind(final Dato dato, final OnItemClickListener listener){
            // Sustituimos los elementos por su valor dentro de la lista de datos
            toolbarCard.setTitle(dato.getTitulo());
            toolbarCard.setSubtitle(dato.getSubtitulo());
            // Cree un menu dentro de res->layout->menu con el nombre card_menu
            toolbarCard.inflateMenu(R.menu.card_menu);
            toolbarCard.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
                @Override
                public boolean onMenuItemClick(MenuItem item) {
                    switch (item.getItemId()) {
                        case R.id.action_option1:
                            Toast.makeText(context, "op1", Toast.LENGTH_SHORT).show();
                            break;
                        case R.id.action_option2:
                            Toast.makeText(context, "op2", Toast.LENGTH_SHORT).show();
                            break;
                        case R.id.action_option3:
                            Toast.makeText(context, "op3", Toast.LENGTH_SHORT).show();
                            break;
                    }
                    return true;
                }
            });
            textViewInfo.setText(dato.getContenido());
            // Mostrar y ocultar la información
            unlinearl.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (linearLayoutDetails.getVisibility() == View.GONE) {
                        // Cree la clase ExpandAndCollpaseViewUtil
                        ExpandAndCollapseViewUtil.expand(linearLayoutDetails, DURATION);
                        imageViewExpand.setImageResource(R.drawable.more);
                        // Rotamos el icono de flecha 180 grados
                        Animation animation = new RotateAnimation(0.0f, -180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                        animation.setFillAfter(true);
                        animation.setDuration(DURATION);
                        imageViewExpand.startAnimation(animation);
    
                    } else {
                        ExpandAndCollapseViewUtil.collapse(linearLayoutDetails, DURATION);
                        imageViewExpand.setImageResource(R.drawable.less);
                        Animation animation = new RotateAnimation(0.0f, 180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                        animation.setFillAfter(true);
                        animation.setDuration(DURATION);
                        imageViewExpand.startAnimation(animation);
    
                    }
                }
            });
    
    
            // Picasso mejora el uso de imagenes
            // square.github.io/picasso
    
            Picasso.with(context).load(dato.getImagen())
                        .into(imageViewPoster);
    
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    listener.onItemClick(dato, getAdapterPosition());
                }
            });
        }
    }
    
    
    public interface OnItemClickListener{
        void onItemClick(Dato dato, int position);
    }
}

Paso 7. Crear el menu de cada tarjeta

Deseamos que cada tarjeta contenga un menú similar a éste:

En el código de adapter hay una línea que manda a llamar ese menú:

toolbarCard.inflateMenu(R.menu.card_menu);

Pero hay que crear ese menú. Ya hay un directorio de menú que creamos en el artículo anterior. Está en res->layout->menu

De clic derecho sobre el directorio menu, luego en New->Menu Resource File

Cree un nuevo archivo con el nombre: card_menu.xml

El código de éste archivo será similar a éste:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".MainActivity">

    <item
        android:id="@+id/action_option1"
        android:title="Compartir"
        app:showAsAction="never" />

    <item
        android:id="@+id/action_option2"
        android:title="Enviar a mi correo"
        app:showAsAction="never" />

    <item
        android:id="@+id/action_option3"
        android:title="Archivar"
        app:showAsAction="never" />
</menu>

Observas que contiene las tres opciones de la ilustración, con sus id correspondientes.

Paso 8. Crear una clase para el efecto Expandir y Colapsar

Al dar clic al icono a la derecha de “saber más”, se expande la tarjeta, mostrando información. El icono rota y se muestra como una flecha hacia arriba, y al dar clic sobre ésta, la tarjeta se colapsa, ocultando la información.

Para realizar éste efecto crearemos una clase llamada ExpandAndCollapseViewUtil.

De clic derecho sobre el package donde va a crear la clase. Luego en New->Java Class.

El código de ésta clase es el siguiente:

public class ExpandAndCollapseViewUtil {
    public static void expand(final ViewGroup v, int duration) {
        slide(v, duration, true);
    }

    public static void collapse(final ViewGroup v, int duration) {
        slide(v, duration, false);
    }

    private static void slide(final ViewGroup v, int duration, final boolean expand) {
        try {
            //onmeasure method is protected
            Method m = v.getClass().getDeclaredMethod("onMeasure", int.class, int.class);
            m.setAccessible(true);
            m.invoke(
                    v,
                    View.MeasureSpec.makeMeasureSpec(((View) v.getParent()).getMeasuredWidth(), View.MeasureSpec.AT_MOST),
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            );
        } catch (Exception e) {
            Log.e("slideAnimation", e.getMessage(), e);
        }

        final int initialHeight = v.getMeasuredHeight();

        if (expand) {
            v.getLayoutParams().height = 0;
        } else {
            v.getLayoutParams().height = initialHeight;
        }
        v.setVisibility(View.VISIBLE);

        Animation a = new Animation() {
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
                int newHeight = 0;
                if (expand) {
                    newHeight = (int) (initialHeight * interpolatedTime);
                } else {
                    newHeight = (int) (initialHeight * (1 - interpolatedTime));
                }
                v.getLayoutParams().height = newHeight;
                v.requestLayout();

                if (interpolatedTime == 1 && !expand) {
                    v.setVisibility(View.GONE);
                }
            }

            @Override
            public boolean willChangeBounds() {
                return true;
            }
        };

        a.setDuration(duration);
        v.startAnimation(a);
    }

}

 

Paso 9. Enlazar todo: código del InformacionFragment

Vamos al archivo InformacionFragment, que es el fragment que contendrá nuestros CardView. Recuerda que el layout de éste fragment, ya contiene un RecyclerView.

En el método onCreate, enlazamos todo.

En el método getAllData (es un método propio), se crea un ArrayList de objetos de clase “Dato”, con algunos valores de ejemplo. Ésta lista es pasada al adapter que creamos, junto con el layout de los items.

El código será como el siguiente:

public class InformacionFragment extends Fragment {
    private List<Dato> datos;
    private RecyclerView.Adapter mAdapter;
    private RecyclerView.LayoutManager mLayoutManager;
    private RecyclerView mRecyclerView;

    public InformacionFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_informacion, container, false);
        datos = this.getAllData();
        mRecyclerView =  view.findViewById(R.id.recyclerView);
        mLayoutManager = new LinearLayoutManager(getContext());
        mAdapter = new InformacionAdapter(datos, R.layout.list_item_informacion,
                new InformacionAdapter.OnItemClickListener() {
                    @Override
                    public void onItemClick(Dato dato, int position) {


                    }
                });
        mRecyclerView.setHasFixedSize(true);
        // Añade un efecto por defecto, si le pasamos null lo desactivamos por completo
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());

        // Enlazamos el layout manager y adaptador directamente al recycler view
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setAdapter(mAdapter);


        return view;
    }

    private List<Dato> getAllData(){
        return new ArrayList<Dato>() {{
            add(new Dato("¿Qué es el sobrepeso?","Y en qué es diferente de la obesidad",  R.drawable.obesity, "La obesidad se define como un aumento de composición de grasa corporal. Este aumento se traduce en un incremento del peso y aunque no todo incremento del peso corporal es debido a un aumento del tejido adiposo, en la práctica médica el concepto de obesidad está relacionado con el peso corporal. La obesidad debe ser entendida como una enfermedad crónica, de forma semejante que lo es la diabetes mellitus o la hipertensión arterial. (Fuente: Infosalus.com)\n" +
                    "\n "));
            add(new Dato("¿Qué es el IMC?", "Por qué es importante saberlo",R.drawable.imc, ""));
            add(new Dato("¿Cómo medir la cintura?", "Cómo saber si tienes la medida correcta",R.drawable.belly, ""));
            add(new Dato("Alimentos que acumulan grasa abdominal", "Y que debes evitar",R.drawable.food, ""));


        }};
    }

}

Paso 10. Hacer que la opción del menú lateral abra el fragment que hemos creado

Vamos al MainActivity. (Recuerda que en el artículo Pasos para desarrollar un menú en una app Android creamos un menú con una opción que se llama Información. Ésta es la opción que usaremos para abrir nuestro fragment que contiene el RecyclerView con CardView).

Busca la línea que enlaza a navigationView. Se ve así:

navigationView = (NavigationViewfindViewById(R.id.navview);

Debajo de esa línea escribiremos el código que detectará cuando se da un clic a una opción del menú. Es éste:

        navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {

                boolean fragmentTransaction = false;

                switch (item.getItemId()){
                    
                    case R.id.menu_informacion:
                        frg = new InformacionFragment();
                        changeFragment(frg, item);
                        drawerLayout.closeDrawers();
                        fragmentTransaction = false;
                        break;
                }

                if (fragmentTransaction){
                    changeFragment (fragment , item );
                    drawerLayout.closeDrawers();
                }
                return true;
            }

Si ya habías creado éste Listener, no debes crear otro, sino únicamente añadir el caso correspondiente:

case R.id.menu_informacion:

frg = new InformacionFragment();

changeFragment(frg, item);

drawerLayout.closeDrawers();

fragmentTransaction = false;

break;

Notas que utiliza una función propia llamada changeFragment. Ésta función va una vez cierres la llave del onCreate, y contiene el siguiente código:

    private void changeFragment( Fragment fragment, MenuItem item){
        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.content_frame, fragment)
                .commit();
        item.setChecked(true);
        getSupportActionBar().setTitle(item.getTitle());
    }

¡Listo! Al darle clic a la opción Información del Menú Lateral, se abre un fragment que contiene un RecyclerView, con una lista de CardView. Espero logres completar tu proyecto en programar un menú lateral que abre un CardView en Android.

Mira el resultado y los pasos ya descritos en video:

¿Qué te pareció este artículo?
  • Poco informativo ()
  • No era lo que buscaba ()
  • Regular ()
  • Interesante ()
  • Excelente ()
(Visto 2.233 veces)

Tu comentario

opiniones