Android: Repetir un include y encontrar su ID con un string utilizando ciclos

Standard

 

Qué tal, gente, en esta ocasión volvemos con más Android “tricks”.

¿Les ha pasado que se topan de repente con la necesidad de construir un layout que presenta algún componente custom de manera repetitiva y necesitan solucionarlo sin intención de hacer un copy-paste redundante? Pues bien, a mí me pasó hace poco tiempo y, aunque existen varias alternativas diferentes para dar solución a este problema, comparto con ustedes una alternativa que apliqué y que en lo particular considero me ahorró algo de tiempo y esfuerzo.

En mi caso se presentaron en mi proyecto actual un par de situaciones (hasta el momento) en las cuales imperaba la necesidad de mostrar una vista o componente custom en repetidas ocasiones, fuese una gráfica u otro componente creado por mí misma dado que en esos casos no encontré alguno nativo que se adaptara a las necesidades del proyecto. En este contexto, la solución elegida fue la que se explica literalmente en el título de este post: Incluir (mediante el componente <include/>) el mismo layout más de una vez y encontrar su ID con un string (índice) usando ciclos.

En fin, sin mayor preámbulo explico la implementación.

Archivos de Resources

Lo primero es agregar los layouts y resources necesarios para presentar nuestras vistas, para esto incluyo aquí los archivos xml requeridos  en nuestro proyecto, en orden de aparición:

values/strings.xml

<resources>

    <!-- Defaults -->
    <string name="app_name">Custom Components Example</string>
    <string name="default_item">Item</string>
    <string name="default_zero">0</string>

    <string-array name="headers">
        <item>This is a row header</item>
        <item>Another row header</item>
        <item>Header too</item>
        <item>A header more</item>
        <item>The Ultimate Header</item>
    </string-array>

    <string-array name="set_1">
        <item>1</item>
        <item>2</item>
        <item>3</item>
        <item>4</item>
    </string-array>
    <string-array name="set_2">
        <item>5</item>
        <item>6</item>
        <item>7</item>
        <item>8</item>
    </string-array>
    <string-array name="set_3">
        <item>9</item>
        <item>10</item>
        <item>11</item>
        <item>12</item>
    </string-array>
    <string-array name="set_4">
        <item>13</item>
        <item>14</item>
        <item>15</item>
        <item>16</item>
    </string-array>
    <string-array name="set_5">
        <item>17</item>
        <item>18</item>
        <item>19</item>
        <item>20</item>
    </string-array>

</resources>

values/colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Custom palette -->
    <color name="colorPrimaryDark">#222222</color>
    <color name="colorPrimary">#333333</color>
    <color name="colorAccent">#ffffff</color>
    <color name="background_grey_light">#f0f0f0</color>
    <color name="divider">#e6e6e6</color>
    <color name="primary_text">#ffffff</color>
    <color name="secondary_text">#999999</color>
</resources>

drawable/text_field_shape_border.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <stroke
        android:width="1px"
        android:color="@color/divider" />
    <padding
        android:bottom="5dp"
        android:left="10dp"
        android:right="10dp"
        android:top="5dp" />
</shape>

drawable/text_field_shape_grey.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@color/background_grey_light" />
    <padding
        android:bottom="5dp"
        android:left="10dp"
        android:right="10dp"
        android:top="5dp" />
</shape>

layout/item_row.xml: Componente custom que se añadirá mediante include en la vista principal.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:id="@+id/item_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/text_field_shape_grey"
        android:gravity="center"
        android:paddingBottom="5dp"
        android:paddingTop="5dp"
        android:text="@string/default_item"
        android:textSize="20sp" />

    <LinearLayout
        android:id="@+id/item_values_layout"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:baselineAligned="false"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/item_value_1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@drawable/text_field_shape_border"
            android:gravity="center"
            android:text="@string/default_zero"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="@color/secondary_text"
            android:textSize="22sp" />

        <TextView
            android:id="@+id/item_value_2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@drawable/text_field_shape_border"
            android:gravity="center"
            android:text="@string/default_zero"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="@color/secondary_text"
            android:textSize="22sp" />

        <TextView
            android:id="@+id/item_value_3"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@drawable/text_field_shape_border"
            android:gravity="center"
            android:text="@string/default_zero"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="@color/secondary_text"
            android:textSize="22sp" />

        <TextView
            android:id="@+id/item_value_4"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@drawable/text_field_shape_border"
            android:gravity="center"
            android:text="@string/default_zero"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="@color/secondary_text"
            android:textSize="22sp" />
    </LinearLayout>
</LinearLayout>

activity_main.xml: Layout principal, correspondiente a nuestro Activity default.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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.hunabsys.customcomponentsapp.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <include
            android:id="@+id/include_item_1"
            layout="@layout/item_row"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <include
            android:id="@+id/include_item_2"
            layout="@layout/item_row"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <include
            android:id="@+id/include_item_3"
            layout="@layout/item_row"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <include
            android:id="@+id/include_item_4"
            layout="@layout/item_row"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <include
            android:id="@+id/include_item_5"
            layout="@layout/item_row"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

</RelativeLayout>

Una vez agregados nuestros layouts y resources, si ejecutamos nuestra aplicación veremos algo muy similar a la primera captura de pantalla que se presenta al inicio de este post. Ya que nos aseguremos de que va todo bien, es momento de incluir la lógica de nuestra aplicación, por medio de la cual se llevará a cabo toda la magia.

La siguiente clase implementa un método en el cual se ejecutan dos ciclos FOR. El primero de estos ciclos, se encarga de encontrar y modificar cada uno de los includes utilizados para agregar nuestra vista custom al layout principal, es decir, cada una de las veces que se repite nuestra vista en el layout. Para que esto sea posible, los identificadores de cada uno de esos includes fueron asignados con la misma nomenclatura, utilizando como único distintivo un número consecutivo al final de cada uno de ellos, para facilitar su búsqueda mediante índice en nuestros ciclos o bucles.

A su vez, fueron creados en el archivo strings.xml un conjunto de arrays de strings que contienen sets de datos que serán los valores a asignar a los TextViews que componen nuestra vista custom, con la finalidad de manipular las vistas y observar el resultado al cambiar sus valores.

Clase Java

java/MainActivity.java

package com.hunabsys.customcomponentsapp;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setUpData();
    }

    private void setUpData() {
        // Obtener arreglo de headers (strings) desde Resources para usarlo 
        // como variable.
        String[] headers = getResources().getStringArray(R.array.headers);

        // Ciclo para obtener y modificar las vistas agregadas mediante 
        // include en nuestro layout principal.
        for (int i = 1; i < 6; i++) {
            // Encontrar la vista incluida mediante su identificador numerico
            // (id), el cual sera correspondiente al indice de nuestro ciclo.
            String strIncludeId = "include_item_" + i;
            int includeId = getResources().getIdentifier(strIncludeId, "id",
                getPackageName());
            View includeLayout = findViewById(includeId);

            // Si el layout fue encontrado, modificar su header con el valor
            // correspondiente.
            if (includeLayout != null) {
                TextView textHeader = (TextView) 
                    includeLayout.findViewById(R.id.item_title);
                textHeader.setText(headers[i - 1]);
            }

            // Encontrar el arreglo de strings con el set de valores que 
            // corresponde a la vista. En este caso, en el tipo de resource 
            // se especifica "array" para encontrar su ID.
            String strValuesArrayId = "set_" + i;
            int arrayId = getResources().getIdentifier(strValuesArrayId, 
                "array", getPackageName());
            String[] strArray = getResources().getStringArray(arrayId);

            // Ciclo para obtener y modificar los textViews que representaran
            // valores numericos.
            for (int ii = 0; ii < 4; ii++) {
                // Se suma uno a la posicion del indice para que coincida 
                // con el valor numerico del ID.
                int index = ii + 1;
                String strTextViewId = "item_value_" + index;
                int textViewId = getResources().getIdentifier(strTextViewId, 
                    "id", getPackageName());

                // Si el layout existe, se modifica el textView 
                // correspondiente a la posicion del indice.
                if (includeLayout != null) {
                    TextView textValue = (TextView) 
                        includeLayout.findViewById(textViewId);
                    textValue.setText(strArray[ii]);
                }
            }
        }
    }
}

Y bien, al ejecutar la aplicación podemos observar algo muy similar a la segunda captura de pantalla que se muestra al inicio de este post, podemos ver los valores de nuestro archivo strings.xml asignados finalmente a las vistas en la pantalla principal.

Espero haya sido de ayuda este ejemplo básico en el cual se implementan algunas ideas que pueden ser de utilidad en diferentes contextos, comparto también el enlace al código completo del proyecto en GitHub y las referencias en las cuales me basé para construir la aplicación. ¡Hasta luego!

 

Github:

Referencias:

Leave a Reply

Your email address will not be published. Required fields are marked *