Si deseas dividir tu contenido en subcategorías, usar un Navigation Drawer no te será suficiente. Para ello debes usar pestañas o tabs en tu aplicación Android.
Esta vez la librería de diseño no se ha olvidado de este componente tan vital para la navegación. Nos ha entregado a los Android Developers el TabLayout.
Ya sé que antes habíamos visto como añadir pestañas en la action bar, pero recuerda que el nuevo concepto de App Bar cambió todo.
El TabLayout es un componente que facilita el cambio entre un contenido y otro. Ahora va por separado y no se liga a la Action Bar directamente.
Nos provee una forma automatizada y simplificada para asociarse a un View Pager, librándonos de todo el trabajo de coordinación entre los Swipe Views y las pestañas.
Veamos como añadir un Tab Layout y de qué forma integrarlo en un ejemplo completo.
Descargar Proyecto Final En Android Studio
A continuación, sigue las instrucciones para desbloquear el link de descarga del proyecto final:
[sociallocker id=»7121″][/sociallocker]
Añadir Tabs Con El TabLayout En Android
La lista de checkeo para Material Design nos indica de fondo que las pestañas ahora hacen parte de la superficie de la App Bar. Adicionalmente el estilo elimina las barras verticales de separación que antes se les añadía.
En definición Xml simplemente incluyes dentro de la etiqueta AppBarLayout
el componente TabLayout
:
<android.support.design.widget.AppBarLayout android:id="@+id/appbar" ...> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" .../> <android.support.design.widget.TabLayout android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" /> </android.support.design.widget.AppBarLayout>
El código anterior es para crear tabs predefinidas debajo de la toolbar.
Como ves, el TabLayout actúa como un widget normal. Así que puedes obtener su instancia programáticamente con findViewById()
:
TabLayout tabs = (TabLayout) findViewById(R.id.tabs);
Para añadir pestañas usa el método addTab()
, el cual recibe un nuevo objeto TabLayout.Tab
. Puedes obtener una instancia prefabricada con newTab()
y luego setear su título con setText()
.
... tabs.addTab(tabs.newTab().setText("TELÉFONOS")); tabs.addTab(tabs.newTab().setText("TABLETS")); tabs.addTab(tabs.newTab().setText("PORTÁTILES"));
Lo contrario a tabs fijas son las tabs deslizantes o scrollables. Su uso es excelente cuando la cantidad de pestañas es grande, ya que podemos movernos con un scroll.
Activa este modo con el método TabLayout.setTabMode()
. Donde debes añadir como parámetro la bandera TabLayout.MODE_SCROLLABLE
.
tabs.setTabMode(TabLayout.MODE_SCROLLABLE);
Por otro lado… ¿Cómo añadir iconos en las pestañas?
Sencillo, usa el método setIcon()
:
... tabs.addTab(tabs.newTab().setIcon(R.drawable.ic_teléfono)); tabs.addTab(tabs.newTab().setIcon(R.drawable.ic_tablet)); tabs.addTab(tabs.newTab().setIcon(R.drawable.ic_portátil));
A groso modo ese serían las herramientas básicas para el manejo de pestañas, sin embargo ten en cuenta los siguientes puntos:
- Procesa la selección de tabs con la escucha
OnTabSelectedListener
. Solo añádela consetOnTabSelectedListener()
y sobrescribe los controladores.tabs.setOnTabSelectedListener( new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { // ... } @Override public void onTabUnselected(TabLayout.Tab tab) { // ... } @Override public void onTabReselected(TabLayout.Tab tab) { // ... } } );
- Si deseas que un
ViewPager
escuche el cambio de pestañas usaTabLayout.TabLayoutOnPageChangeListener
.ViewPager viewPager = ...; TabLayout tabs = ...; viewPager.addOnPageChangeListener(new TabLayoutOnPageChangeListener(tabs));
- Puedes poblar las pestañas en conjunto de las secciones de un
ViewPager
consetTabsFromPagerAdapter()
. - Es posible ahorrarse la coordinación entre los eventos y la población de pestañas con el método
setupWithViewPager()
. Esto lo veremos en el siguiente ejemplo.
Crear Proyecto En Android Studio
Paso #1. Inicia Android Studio y crea un nuevo proyecto en File > New > New Project… con el nombre de «Tricky Market«. Como viste en el video, esta aplicación es una tienda online de tecnología.
Selecciona una actividad en blanco Blank Activity y confirma.
Paso #2. Dirígete al archivo build.gradle y copia las dependencias del siguiente código:
build.gradle
dependencies { ... compile 'com.android.support:appcompat-v7:22.2.0' compile 'com.android.support:design:22.2.0' compile 'com.github.bumptech.glide:glide:3.6.0' compile 'com.android.support:cardview-v7:22.2.0' compile 'in.srain.cube:grid-view-with-header-footer:1.0.12' }
Es necesario añadir la librería GridView With Header And Footer (u otra que te guste), ya que pondremos un ítem de cabecera dentro de la grilla.
Paso #3. Abre el archivo /res/values/strings.xml e inserta las siguientes cadenas:
strings.xml
<resources> <string name="app_name">Trick Market</string> <string name="title_section1">Teléfonos</string> <string name="title_section2">Tablets</string> <string name="title_section3">Portátiles</string> <string name="action_settings">Settings</string> <string name="action_shop">Carrito De Compras</string> </resources>
Paso #4. Define los colores del diseño con Material Design en /res/values/colors.xml de la siguiente forma:
colors.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="window_background">#EEEEEE</color> <color name="primaryColor">#8BC34A</color> <color name="primaryDarkColor">#689F38</color> <color name="accentColor">#FAD317</color> </resources>
Paso #5. Modifica el estilo de la aplicación desde el archivo /res/values/styles.xml con los colores que acabas de definir:
styles.xml
<resources> <style name="Theme.TrickMarket" parent="Base.AppTheme" /> <style name="Base.AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">@color/primaryColor</item> <item name="colorPrimaryDark">@color/primaryDarkColor</item> <item name="colorAccent">@color/accentColor</item> <item name="android:windowBackground">@color/window_background</item> </style> </resources>
Paso #6. Crea las dimensiones de abajo en tu archivo /res/values/dimens.xml.
dimens.xml
<resources> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="size_fab">56dp</dimen> <dimen name="fab_margin">16dp</dimen> <dimen name="badge_text_size">10sp</dimen> <dimen name="card_margin">16dp</dimen> <dimen name="grid_item_text_size">11sp</dimen> <dimen name="grid_view_padding">5dp</dimen> <dimen name="grid_item_image_size">90dp</dimen> </resources>
Paso #7. A continuación descarga los drawables e imágenes para la aplicación y cópialas en tu directorio /res.
Aunque en el anterior archivo te entrego los iconos, puedes descargar gran variedad desde la página Material Design Icons.
Crear GridView Con Header View
Paso #8. Crea una nueva clase java llamada Product.java. Esta representará el POJO para poblar el grid view con productos de tecnología, como tablets, smartphones y portátiles.
Solo tendremos en cuenta el nombre, la descripción, el precio, el rating o valoración y la imagen asociada al producto.
/** * POJO de un producto */ public class Product { /** * Nombre del producto */ private String nombre; /** * Especificaciones del producto */ private String descripcion; /** * Precio del producto */ private String precio; /** * Valoraciones del producto */ private float rating; /** * Identificador de la imagen para miniatura */ private int idThumbnail; public Product(String nombre, String descripcion, String precio, float rating, int idThumbnail) { this.nombre = nombre; this.descripcion = descripcion; this.precio = precio; this.rating = rating; this.idThumbnail = idThumbnail; } public Product() { } public String getNombre() { return nombre; } public String getDescripcion() { return descripcion; } public String getPrecio() { return precio; } public float getRating() { return rating; } public int getIdThumbnail() { return idThumbnail; } }
Paso #9. Ahora añade una clase llamada Products.java. Su propósito es representar el modelo u origen de datos de todos los productos que tendrá la aplicación.
/** * Datos de prueba para las pestañas */ public class Products { private static Product[] telefonos = { new Product( "Hisense HS-U98", " 4Gb ROM, 1GB RAM, Camara 8Mpx.", "$167 USD", 3.1f, R.drawable.smartphone_hisense_hs_u98), new Product( "DarkFull", " 16Gb ROM, 2GB RAM, Camara 13Mpx.", "$295 USD", 4.0f, R.drawable.smartphone_darkfull), new Product( "Moto G", " 4Gb Rom, 1GB RAM, Camara 13Mpx.", "$189.9 USD", 4.6f, R.drawable.smartphone_motog), new Product( "BQ Aquaris 5 HD", " 16Gb ROM, 1GB RAM, Camara 8Mpx.", "$199.9 USD", 3.0f, R.drawable.smartphone_bq_aquaris_5), new Product( "Aquaris 5.7", " 16Gb ROM, 2GB RAM, Camara 13Mpx.", "$259.9 USD", 4.6f, R.drawable.smartphone_aquaris_57), new Product( "Wiko Rainbow", " 4Gb ROM, 1GB RAM, Camara 8Mpx.", "$169 USD", 4.1f, R.drawable.smartphone_wiko_rainbow), new Product( "Hisense HS-U980", " 8Gb ROM, 1GB RAM, Camara 8Mpx.", "$203.9 USD", 2.8f, R.drawable.smartphone_hisense_hs_u980) }; private static Product[] tablets = { new Product( "Apple iPad Air 2", "iOS 8.1", "$448 USD", 5.0f, R.drawable.tablet_apple_ipad_air_2), new Product( "Samsung Galaxy Tab S 8.4", "Android 4.4 Kitkat", "$431 USD", 4.0f, R.drawable.tablet_samsung_galaxy_tab_s_84), new Product( "Lenovo ThinkPad 8", "Windows 8.1", "$390 USD", 4.6f, R.drawable.tablet_lenovo_thikpad_8), new Product( "Samsung Galaxy Pro 8.4", "Android", "$299 USD", 3.0f, R.drawable.tablet_galaxy_tab_pro_84), new Product( "Amazon Kindle Fire HDX 8.9", "Fire OS", "$379 USD", 3f, R.drawable.tablet_amazon_kindle_fire_hdx), new Product( "Nvidia Shield Tablet", "Android 4.4 Kitkat", "$375 USD", 4.8f, R.drawable.tablet_nvidia_shield), new Product( "ASUS Transformer Pad Infinity TF700", "Android 4.2 Jelly Bean", "$509 USD", 4f, R.drawable.tablet_asus_transformer_pad_infinity_tf700) }; private static Product[] portatiles = { new Product( "Dell Latitude 12", "Model No: 7204", "$6474 USD", 5.0f, R.drawable.portatil_latitude_12_rugged), new Product( "Alienware 17 R1 Flagship", "Gaming", "$3849 USD", 4.0f, R.drawable.portatil_alienware_17_flagship), new Product( "MSI GT80 Titan SLI", "Gaming", "$3299 USD", 4.6f, R.drawable.portatil_msi_gt80_titan), new Product( "ASUS ROG G751YJ-DH72X", "Gaming", "$2999 USD", 3.0f, R.drawable.portatil_asus_rog_g751jy), new Product( "Toshiba X70-AST3G26", "All-Purpose", "$2699 USD", 3f, R.drawable.portatil_toshiba_x70_ast3g26), new Product( "Sony VAIO Duo 13", "2-in-1", "$2699 USD", 4.8f, R.drawable.portatil_sony_vaio_duo_13_svd1322bpxb), new Product( "Gigabyte Aorus X3 Plus v3", "Gaming", "$2538 USD", 4f, R.drawable.portatil_gigabyte_aorus_x3_plus_v3) }; public static Product[] getTelefonos() { return telefonos; } public static Product[] getTablets() { return tablets; } public static Product[] getPortatiles() { return portatiles; } }
Paso #10. Para la representación gráfica de los ítems del grid view crea un nuevo archivo en /res/layout/ llamado grid_item.xml y añade el código de abajo. La valoración del artículo se representa con una rating bar.
grid_item.xml
<?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="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="@dimen/card_margin" android:layout_marginLeft="@dimen/card_margin" android:layout_marginRight="@dimen/card_margin" android:layout_marginTop="@dimen/card_margin" android:orientation="vertical"> <ImageView android:id="@+id/imagen" android:layout_width="@dimen/grid_item_image_size" android:layout_height="@dimen/grid_item_image_size" android:layout_gravity="center_horizontal" android:scaleType="fitCenter" /> <TextView android:id="@+id/nombre" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="Nombre" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="@android:color/black" android:textSize="@dimen/grid_item_text_size" /> <TextView android:id="@+id/descripcion" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:gravity="center" android:text="Descripción" android:textAppearance="?android:attr/textAppearanceSmall" android:textSize="@dimen/grid_item_text_size" /> <TextView android:id="@+id/precio" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="$ Precio" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="@color/primaryColor" android:textSize="@dimen/grid_item_text_size" /> <RatingBar android:id="@+id/rating" style="?android:attr/ratingBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:clickable="true" android:isIndicator="true" android:paddingTop="8dp" android:progressTint="#FDDB39" android:rating="3" android:secondaryProgressTint="#FDDB39" /> </LinearLayout> </android.support.v7.widget.CardView>
Paso #11. Crea otro layout nuevo llamado grid_header.xml y cambia las posiciones de la siguiente forma:
grid_header.xml
<?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="match_parent"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="@dimen/activity_horizontal_margin"> <ImageView android:id="@+id/imagen" android:layout_width="@dimen/grid_item_image_size" android:layout_height="@dimen/grid_item_image_size" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_centerVertical="true" android:layout_marginRight="@dimen/activity_horizontal_margin" /> <TextView android:id="@+id/nombre" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@+id/imagen" android:text="Nombre" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="@android:color/black" /> <TextView android:id="@+id/descripcion" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/nombre" android:layout_alignStart="@+id/nombre" android:layout_below="@+id/nombre" android:text="Descripción" android:textAppearance="?android:attr/textAppearanceSmall" /> <TextView android:id="@+id/precio" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/descripcion" android:layout_alignStart="@+id/descripcion" android:layout_below="@+id/descripcion" android:text="$ Precio" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?colorPrimary" /> <RatingBar android:id="@+id/rating" style="?android:attr/ratingBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/precio" android:layout_below="@+id/precio" android:layout_gravity="center_horizontal" android:clickable="true" android:isIndicator="true" android:paddingTop="8dp" android:progressTint="#FDDB39" android:rating="3" android:secondaryProgressTint="#FDDB39" /> </RelativeLayout> </android.support.v7.widget.CardView>
Crear Adaptador Para El GridView
Paso #12. Lo siguiente es crear un adaptador personalizado para el grid view. Para ello añade una nueva clase llamada GridAdapter.java y pega el siguiente código.
import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.RatingBar; import android.widget.TextView; import com.bumptech.glide.Glide; /** * {@link android.widget.BaseAdapter} personalizado para el gridview */ public class GridAdapter extends BaseAdapter { private final Context mContext; private final Product[] items; public GridAdapter(Context c, Product[] items) { mContext = c; this.items = items; } @Override public int getCount() { // Decremento en 1, para no contar el header view return items.length - 1; } @Override public Product getItem(int position) { return items[position]; } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View view, ViewGroup viewGroup) { if (view == null) { LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.grid_item, viewGroup, false); } Product item = getItem(position); // Seteando Imagen ImageView image = (ImageView) view.findViewById(R.id.imagen); Glide.with(image.getContext()).load(item.getIdThumbnail()).into(image); // Seteando Nombre TextView name = (TextView) view.findViewById(R.id.nombre); name.setText(item.getNombre()); // Seteando Descripción TextView descripcion = (TextView) view.findViewById(R.id.descripcion); descripcion.setText(item.getDescripcion()); // Seteando Precio TextView precio = (TextView) view.findViewById(R.id.precio); precio.setText(item.getPrecio()); // Seteando Rating RatingBar ratingBar = (RatingBar) view.findViewById(R.id.rating); ratingBar.setRating(item.getRating()); return view; } }
Paso #13. Añade un layout llamado fragment_main.xml en /res/layout/ y defínelo de la siguiente manera.
fragment_main.xml
<in.srain.cube.views.GridViewWithHeaderAndFooter xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/gridview" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:horizontalSpacing="@dimen/grid_view_padding" android:numColumns="2" android:padding="@dimen/grid_view_padding" android:stretchMode="columnWidth" android:verticalSpacing="@dimen/grid_view_padding" />
GridViewWithHeaderAndFooter es la variación del grid view que viene en la librería que implementamos.
Paso #14. Ahora falta crear un fragmento con un grid view para poblar las secciones del view pager. En nuestro caso tendremos tres secciones: "TELÉFONOS"
, "TABLETS"
y "PORTÁTILES"
.
Aunque podría crearse un fragmento para cada una, he decidido por usar una estructura switch
que infle la grilla con los datos correspondientes a la sección. Solución viable, ya que el contenido es similar.
Crea una nueva clase con el nombre de GridFragment.java e implementala así:
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.ImageView; import android.widget.RatingBar; import android.widget.TextView; import com.bumptech.glide.Glide; import in.srain.cube.views.GridViewWithHeaderAndFooter; /** * Un fragmento que contiene una grilla de productos */ public class GridFragment extends Fragment { /** * Argumento que representa el número sección al que pertenece */ private static final String ARG_SECTION_NUMBER = "section_number"; /** * Creación prefabricada de un {@link GridFragment} */ public static GridFragment newInstance(int sectionNumber) { GridFragment fragment = new GridFragment(); Bundle args = new Bundle(); args.putInt(ARG_SECTION_NUMBER, sectionNumber); fragment.setArguments(args); return fragment; } public GridFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); // Obtención del grid view GridViewWithHeaderAndFooter grid = (GridViewWithHeaderAndFooter) rootView.findViewById(R.id.gridview); // Inicializar el grid view setUpGridView(grid); return rootView; } /** * Infla el grid view del fragmento dependiendo de la sección * * @param grid Instancia del grid view */ private void setUpGridView(GridViewWithHeaderAndFooter grid) { int section_number = getArguments().getInt(ARG_SECTION_NUMBER); switch (section_number) { case 1: grid.addHeaderView(createHeaderView(6, Products.getTelefonos())); grid.setAdapter(new GridAdapter(getActivity(), Products.getTelefonos())); break; case 2: grid.addHeaderView(createHeaderView(6, Products.getTablets())); grid.setAdapter(new GridAdapter(getActivity(), Products.getTablets())); break; case 3: grid.addHeaderView(createHeaderView(6, Products.getPortatiles())); grid.setAdapter(new GridAdapter(getActivity(), Products.getPortatiles())); break; } } /** * Crea un view de cabecera para mostrarlo en el principio del grid view. * * @param position Posición del item que sera el grid view dentro de {@code items} * @param items Array de productos * @return Header View */ private View createHeaderView(int position, Product[] items) { View view; LayoutInflater inflater = getActivity().getLayoutInflater(); view = inflater.inflate(R.layout.grid_header, null, false); Product item = items[position]; // Seteando Imagen ImageView image = (ImageView) view.findViewById(R.id.imagen); Glide.with(image.getContext()).load(item.getIdThumbnail()).into(image); // Seteando Nombre TextView name = (TextView) view.findViewById(R.id.nombre); name.setText(item.getNombre()); // Seteando Descripción TextView descripcion = (TextView) view.findViewById(R.id.descripcion); descripcion.setText(item.getDescripcion()); // Seteando Precio TextView precio = (TextView) view.findViewById(R.id.precio); precio.setText(item.getPrecio()); // Seteando Rating RatingBar ratingBar = (RatingBar) view.findViewById(R.id.rating); ratingBar.setRating(item.getRating()); return view; } }
Importante resaltar que:
- Se creó el método
createHeaderView()
para inflar un view con el layout grid_header.xml. - El método
addHeaderView()
añade el header alGridView
.
Añadir Notificación Con Contador En Action Button
Paso #15. El siguiente paso es crear el menú de la Toolbar
dentro de la carpeta /res/menu con el nombre de menu_main.xml.
menu_main.xml
<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_settings" android:orderInCategory="100" android:title="@string/action_settings" app:showAsAction="never" /> <item android:id="@+id/action_shop" android:icon="@drawable/notification_drawable" android:orderInCategory="1" android:title="@string/action_shop" app:showAsAction="ifRoom" /> </menu>
Paso #16. Ahora crea un nuevo archivo en /res/drawable/ llamado notification_drawable.xml. Este será usado para crear el icono del carrito de compras con una notificación circular en su parte superior.
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/ic_notification" android:drawable="@drawable/ic_cart" android:gravity="center" /> <!-- Insignia flotante --> <item android:id="@+id/ic_badge" android:drawable="@drawable/ic_cart" /> </layer-list>
Paso #17. Lo siguiente es crear una nueva clase llamada BadgeDrawable.java que extienda de Drawable
. Con ella dibujaremos el círculo sobre el recurso creado previamente.
Este procedimiento para decorar un action button con una notificación, está basado en el artículo de Jesse Hendrickson denominado «Dynamically add flair to ActionBar MenuItems». De verdad es muy interesante y te recomiendo que lo leas.
BadgeDrawable.java
import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Drawable; /** * Drawable con un contador flotante */ public class BadgeDrawable extends Drawable { private float mTextSize; private Paint mBadgePaint; private Paint mTextPaint; private Rect mTxtRect = new Rect(); private String mCount = ""; private boolean mWillDraw = false; public BadgeDrawable(Context context) { mTextSize = context.getResources().getDimension(R.dimen.badge_text_size); mBadgePaint = new Paint(); mBadgePaint.setColor(Color.RED); mBadgePaint.setAntiAlias(true); mBadgePaint.setStyle(Paint.Style.FILL); mTextPaint = new Paint(); mTextPaint.setColor(Color.WHITE); mTextPaint.setTypeface(Typeface.DEFAULT_BOLD); mTextPaint.setTextSize(mTextSize); mTextPaint.setAntiAlias(true); mTextPaint.setTextAlign(Paint.Align.CENTER); } @Override public void draw(Canvas canvas) { if (!mWillDraw) { return; } Rect bounds = getBounds(); float width = bounds.right - bounds.left; float height = bounds.bottom - bounds.top; // Position the badge in the top-right quadrant of the icon. float radius = ((Math.min(width, height) / 2) - 1) / 2; float centerX = width - radius - 1; float centerY = radius + 1; // Draw badge circle. canvas.drawCircle(centerX, centerY, radius, mBadgePaint); // Draw badge count text inside the circle. mTextPaint.getTextBounds(mCount, 0, mCount.length(), mTxtRect); float textHeight = mTxtRect.bottom - mTxtRect.top; float textY = centerY + (textHeight / 2f); canvas.drawText(mCount, centerX, textY, mTextPaint); } /* Sets the count (i.e notifications) to display. */ public void setCount(int count) { mCount = Integer.toString(count); // Only draw a badge if there are notifications. mWillDraw = count > 0; invalidateSelf(); } @Override public void setAlpha(int alpha) { // do nothing } @Override public void setColorFilter(ColorFilter cf) { // do nothing } @Override public int getOpacity() { return PixelFormat.UNKNOWN; } }
Paso #18. A continuación crea una clase llamada Utils.java. La idea es añadirle un método estático llamado setBadgeCount()
para asignar el valor del contador de la notificación.
import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; /** * Clase de utilidades */ public class Utils { public static void setBadgeCount(Context context, LayerDrawable icon, int count) { BadgeDrawable badge; // Reusar drawable Drawable reuse = icon.findDrawableByLayerId(R.id.ic_badge); if (reuse != null && reuse instanceof BadgeDrawable) { badge = (BadgeDrawable) reuse; } else { badge = new BadgeDrawable(context); } badge.setCount(count); icon.mutate(); icon.setDrawableByLayerId(R.id.ic_badge, badge); } }
Paso #19. Ve a /res/layout/ y modifica el layout de la actividad principal. Es importante usar un CoordinatorLayout
para el botón flotante que usaremos en la parte inferior derecha. También por si quieres esconder la Toolbar y dejar fijas las pestañas al realizar scrolling.
activity_main.xml
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/coordinator" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v4.view.ViewPager android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> <android.support.design.widget.AppBarLayout android:id="@+id/appbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:theme="@style/ThemeOverlay.AppCompat.Dark" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" /> <android.support.design.widget.TabLayout android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" /> </android.support.design.widget.AppBarLayout> <!-- FAB --> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="@dimen/size_fab" android:layout_height="@dimen/size_fab" android:layout_margin="@dimen/fab_margin" android:onClick="onFabClick" android:src="@drawable/ic_search" app:borderWidth="0dp" app:elevation="@dimen/fab_elevation" app:layout_anchor="@id/pager" app:layout_anchorGravity="bottom|right" /> </android.support.design.widget.CoordinatorLayout>
Paso #20. Ahora abre el archivo MainActivity.java y modifícalo con el código de abajo. Ten en cuenta que debes crear un FragmentPagerAdapter
para el ViewPager
.
Luego relaciona el TabLayout
con el view pager a través del método setupWithViewPager()
para inflar las tres pestañas basada en secciones.
import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.support.design.widget.Snackbar; import android.support.design.widget.TabLayout; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; 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.view.View; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { ViewPager mViewPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setToolbar(); // Añadir la toolbar // Setear adaptador al viewpager. mViewPager = (ViewPager) findViewById(R.id.pager); setupViewPager(mViewPager); // Preparar las pestañas TabLayout tabs = (TabLayout) findViewById(R.id.tabs); tabs.setupWithViewPager(mViewPager); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); MenuItem item = menu.findItem(R.id.action_shop); // Obtener drawable del item LayerDrawable icon = (LayerDrawable) item.getIcon(); // Actualizar el contador Utils.setBadgeCount(this, icon, 3); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_shop) { showSnackBar("Carrito de compras"); return true; } return super.onOptionsItemSelected(item); } /** * Muestra una {@link Snackbar} prefabricada * * @param msg Mensaje a proyectar */ private void showSnackBar(String msg) { Snackbar.make(findViewById(R.id.fab), msg, Snackbar.LENGTH_LONG).show(); } /** * Establece la toolbar como action bar */ private void setToolbar() { Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); final ActionBar ab = getSupportActionBar(); if (ab != null) { // Poner ícono del drawer toggle ab.setHomeAsUpIndicator(R.drawable.ic_menu); ab.setDisplayHomeAsUpEnabled(true); } } /** * Crea una instancia del view pager con los datos * predeterminados * * @param viewPager Nueva instancia */ private void setupViewPager(ViewPager viewPager) { SectionsPagerAdapter adapter = new SectionsPagerAdapter(getSupportFragmentManager()); adapter.addFragment(GridFragment.newInstance(1), getString(R.string.title_section1)); adapter.addFragment(GridFragment.newInstance(2), getString(R.string.title_section2)); adapter.addFragment(GridFragment.newInstance(3), getString(R.string.title_section3)); viewPager.setAdapter(adapter); } /** * Método onClick() del FAB * * @param v View presionado */ public void onFabClick(View v) { showSnackBar("Buscar producto"); } /** * Un {@link FragmentPagerAdapter} que gestiona las secciones, fragmentos y * títulos de las pestañas */ public class SectionsPagerAdapter extends FragmentPagerAdapter { private final List<Fragment> mFragments = new ArrayList<>(); private final List<String> mFragmentTitles = new ArrayList<>(); public SectionsPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return mFragments.get(position); } @Override public int getCount() { return mFragments.size(); } public void addFragment(Fragment fragment, String title) { mFragments.add(fragment); mFragmentTitles.add(title); } @Override public CharSequence getPageTitle(int position) { return mFragmentTitles.get(position); } } }
Paso #21. Por último corre la aplicación en Android Studio para ver nuestra tienda online de tecnología Trick Market.
Conclusión
El TabLayout permite cambiar entre views de forma fácil y amigable.
Hemos visto cómo crear un Grid View e integrar distintos productos de una tienda online en cada sección de un View Pager.
Aunque no es un ejemplo completo, muestra como las tabs pueden optimizar la transición entre cada segmento de contenido.
Lo ideal sería realizar peticiones Http desde Android hacia un servidor externo, que almacene la información de datos reales.
¿Cuál es el siguiente paso?
Aprender a usar transiciones entre actividades y generar animaciones en las interacciones de distintos views.
Únete Al Discord De Develou
Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!