lunes, 26 de agosto de 2019

Cambiar el color a un checkbox de Android

Aunque no lo parezca, cambiarle el color a un checkbox es muy difícil (de encontrar la solución), pero muy fácil de implementar:

Después de mucho buscar en stackoverflow di con las siguientes instrucciones:

if (Build.VERSION.SDK_INT < 21) {
    CompoundButtonCompat.setButtonTintList(checkbox, ColorStateList.valueOf(color));} else {
    checkbox.setButtonTintList(ColorStateList.valueOf(color));}

Donde checkbox es la view y color es un recurso obtenido a través de uno de los colores de color.xml
color = context.getResources().getColor(R.color.SALUD);

Cabe mencionar que dependiendo la versión de android que se elija, es uno u otro.
Yo recomiendo poner ambos casos para máxima compatibilidad.

Saludos.

Listview personalizado.

Hoy vamos a hacer una lista personalizada.
El ejemplo que haremos será una lista con un texto, una imagen y un checkbox para marcar o desmarcar. Servirá como una lista de favoritos, si el check está marcado, se marca como favorito.

Necesitamos:

1. Layout de la lista.
2. Adaptador de la lista.

1. Layout de la lista.
Irá en la carpeta res/layout, utilizaremos Linear Layout por su simplicidad.

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:orientation="vertical"    >    <View        android:layout_width="match_parent"        android:layout_height="1dp"        android:background="@android:color/darker_gray"/>
    <LinearLayout    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:orientation="horizontal"    >
        <CheckBox            android:id="@+id/checkBoxFavorito"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_weight="3"            android:layout_gravity="center"            android:layout_marginLeft="15dp"            android:buttonTint="@color/rojoWaifu"        />
        <ImageView        android:id="@+id/imageView"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:layout_weight="3"        />
    <TextView        android:id="@+id/textView"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:textColor="#000000"        android:textSize="18sp"        android:layout_weight="1"        android:layout_marginRight="10dp"        android:shadowColor="#606060"        android:shadowDx="1.3"        android:shadowDy="1.1"        android:shadowRadius="1.4"        />    </LinearLayout>
</LinearLayout>

Tenemos un checkbox, un imageview y un text view

2. El Adapter:

public class ListaNormasAdapter extends ArrayAdapter<Norma> {
    private Context context;    private int resourceId;    private List<Norma> items;    private NormasAdapterObserver observer = null;    public static final String TAG = "ListaNormasAdapJass";
    public ListaNormasAdapter(@NonNull Context context, int layoutACargar, ArrayList<Norma> items) {
        super(context, layoutACargar, items);        this.items = items;        this.context = context;        this.resourceId = layoutACargar;
        //El observador necesario para notificar que los objetos/items de la lista han cambiado.        if(context instanceof  NormasAdapterObserver){
            this.observer = (NormasAdapterObserver) context;        }
    }

    @NonNull    @Override    public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        ArrayList<Integer> normasFavoritas = Preferencias.getFavoritos(context, Preferencias.NORMA);        View view = convertView;        try {
            if (convertView == null) {
                LayoutInflater inflater = ((Activity) context).getLayoutInflater();                view = inflater.inflate(resourceId, parent, false);            }

            final Norma normaACargar = getItem(position);
            TextView descripcionNorma = (TextView) view.findViewById(R.id.textView);
            //Establecemos lo que queremos hacer cuando cliqueen en el texto.            descripcionNorma.setOnClickListener(new View.OnClickListener() {
                @Override                public void onClick(View v) {
                    abrirNorma(normaACargar);                }
            });
            ImageView imageView = view.findViewById(R.id.imageView);            //Establecemos lo que queremos hacer cuando cliqueen en la imagen.            imageView.setOnClickListener(new View.OnClickListener() {
                @Override                public void onClick(View v) {
                    abrirNorma(normaACargar);                }
            });

            imageView.setImageResource(normaACargar.getImagen());
            final CheckBox checkbox = view.findViewById(R.id.checkBoxFavorito);
            if(normasFavoritas.contains(normaACargar.getNumeroNorma())){
                checkbox.setChecked(true);            }
            else{
                checkbox.setChecked(false);            }

            checkbox.setOnClickListener(new View.OnClickListener() {
                @Override                public void onClick(View v) {
                    CheckBox checkbox = (CheckBox)v;                    if(!checkbox.isChecked()){
                        Preferencias.borrarFavoritos(context, Preferencias.NORMA,normaACargar.getNumeroNorma());                    }
                    else{
                        Preferencias.agregarAFavoritos(context, Preferencias.NORMA,normaACargar.getNumeroNorma());                    }
                    //Se puso un observer porque se ocupaban hacer más cosas en la activity de la lista, pero no es necesario si pueden manejar el cambio de datos aquí.
                    //Sólo recuerden llamar a setItems y a notify para que les actualice la vista.
                    if(observer != null)
                    {
                        observer.actualizarLista();                    }
                }
            });
            descripcionNorma.setText(normaACargar.getDescripcion());            //Dependiendo el grupo de norma, podemos cambiar los atributos dentro de los elementos de la lista como su color.            int color = 0;            if (normaACargar.getGrupo().equals(Constantes.GRUPO_SALUD)) {
                color = context.getResources().getColor(R.color.SALUD);            }
            else if (normaACargar.getGrupo().equals(Constantes.GRUPO_ORGANIZACION)) {
                color = context.getResources().getColor(R.color.ORGANIZACION);            }
            else if (normaACargar.getGrupo().equals(Constantes.GRUPO_SEGURIDAD)) {
                color = context.getResources().getColor(R.color.SEGURIDAD);            }
            else if (normaACargar.getGrupo().equals(Constantes.GRUPO_ESPECIFICAS)) {
                color = context.getResources().getColor(R.color.ESPECIFICA);            }
            else if (normaACargar.getGrupo().equals(Constantes.GRUPO_PRODUCTO)) {
                color = context.getResources().getColor(R.color.PRODUCTO);            }
            //Asignamos el color del texto y de los checkbox:
            descripcionNorma.setTextColor(color);            if (Build.VERSION.SDK_INT < 21) {
                CompoundButtonCompat.setButtonTintList(checkbox, ColorStateList.valueOf(color));            } else {
                checkbox.setButtonTintList(ColorStateList.valueOf(color));            }

        } catch (Exception e) {
            e.printStackTrace();        }
        return view;    }

    @Nullable    @Override    public Norma getItem(int position) {
        return items.get(position);    }

    @Override    public int getCount() {
        return items.size();    }

    @Override    public long getItemId(int position) {
        return position;    }

    private void abrirNorma(Norma norma){
        //Hacemos cosas con el objeto que cliqueó el usuario...
        //En este caso, abriremos otra actividad, pero podría ser cualqier cosa.
        Intent intent = new Intent(context, VisorPDFActivity.class);        intent.putExtra(Constantes.NUMERO_DE_NORMA, norma.getNumeroNorma());        context.startActivity(intent);    }

    public void setItems(List<Norma> items) {
        this.items = items;    }
}

3. El observer:
No es necesario, pero puede ser útil si necesitan desplegar un anuncio o algo así desde la activity que controla la lista de objetos. En este caso nada más lee de nuevo las normas favoritas y las asigna al adapter para que actualice la vista con los elementos agregados o quitados.

@Overridepublic void actualizarLista() {
    ArrayList<Norma> normasAMostrar = new ArrayList<Norma>();    for (Norma norma : normasCompletas) {
        for (int normaFavorita : Preferencias.getFavoritos(this,Preferencias.NORMA)) {
            if (norma.getNumeroNorma() == normaFavorita) {
                normasAMostrar.add(norma);            }
        }
    }
    normaAdapter.setItems(normasAMostrar);    normaAdapter.notifyDataSetChanged();}

Y el resultado sería este:



Al quitar el checkbox, si la activity que responde al evento es un NormasAdapterObserver, se eliminaría el elemento de la vista.
Por otro lado, si no es de tipo NormasAdapterObserver, podemos elegir que solamente borre de las preferencias los datos, sin quitarlo de la lista.

Espero que les sirva.
¡Saludos!

viernes, 16 de agosto de 2019

Guardar ArrayList de Strings o ints en SharedPreferences

Hola, hoy creamos un método que guarde un ArrayList de Strings o números en las preferencias.
Necesitaremos:

1. Clase estática que contenga las constantes a utilizar.

Yo creé una clase que se llama GuardarPreferences, es necesario que sea estática porque la idea es que pueda se llamada desde cualquier parte de nuestra App con solo enviarle un contexto, pues las shared preferences requieren uno.

Las constantes indicarán qué tipo de campo quiero guardar, en este caso "NORMAS" significa que quiero guardar un número de norma x, podrían ponerle por ejemplo, NOMBRES, COLORES, o lo que ustedes quieran, más que nada es para identificar qué cosa voy a guardar.
la variable numeroDeNorma, es el valor a guardar.

El apartado de IDENTIFICADOR, no es más que un string para saber qué shared preferences abrirá, podrían poner el nombre de su paquete, o cualquier otra cosa que tenga sentido para ustedes.

Las preferences no pueden guardar ArrayList, pero sí un HashSet, por eso indicaba que podíamos guardar strings o enteros, porque podemos cambiarlos fácilmente.

En este ejemplo guardaré Strings, pero recibiré enteros, para demostrar la conversión.
El código completo:

public class GuardarPreferences {

    public static final String NORMA = "NORMAS";
    private static final String IDENTIFICADOR  = "Normas favoritas";
    public static void agregarAFavoritos(Context contexto, String campo, int numeroDeNorma){
        SharedPreferences preferencias = contexto.getSharedPreferences(IDENTIFICADOR,Context.MODE_PRIVATE);        SharedPreferences.Editor editor = preferencias.edit();
        ArrayList<String> normasFavoritas = new ArrayList<String>();
        if(campo.equals(NORMA)){
            normasFavoritas = new ArrayList<String>(preferencias.getStringSet(NORMA, new HashSet<String>()));            if(!normasFavoritas.contains(numeroDeNorma)) {
                normasFavoritas.add(numeroDeNorma + "");                editor.putStringSet(NORMA,new HashSet<String>(normasFavoritas));      editor.clear();
          editor.commit();            }
        }

    }

    public static void borrarFavoritos(Context contexto, String campo, int valor){
        SharedPreferences preferencias = contexto.getSharedPreferences(IDENTIFICADOR,Context.MODE_PRIVATE);        Set<String> normasFavoritas = preferencias.getStringSet(campo, new HashSet<String>());        normasFavoritas.remove(valor+"");        SharedPreferences.Editor editor = preferencias.edit();        editor.putStringSet(NORMA,new HashSet<String>(normasFavoritas));    editor.clear();
    editor.commit();    }

    public static ArrayList<Integer> getFavoritos(Context contexto, String campo){
        SharedPreferences preferencias = contexto.getSharedPreferences(IDENTIFICADOR,Context.MODE_PRIVATE);
        Set<String> normasFavoritas = preferencias.getStringSet(campo, new HashSet<String>());        ArrayList<Integer> normasFavoritas2 = new ArrayList<Integer>();        for(String normaGuardada:normasFavoritas){
            normasFavoritas2.add(Integer.parseInt(normaGuardada));        }
        return normasFavoritas2;    }

}

Para guardar otro dato, sólo tenemos que crear una constante nueva y agregarla al IF, por ejemplo para guardar nombres, podemos hacer un nuevo método igual, que en vez de recibir un int en numeroDeNorma, contenga String nombre, y nos ahorramos el casteo.

Usando los métodos desde una clase de tipo Activity:

GuardarPreferences.agregarAFavoritos(this,NORMA,15);GuardarPreferences.borrarFavoritos(this,NORMA,15);GuardarPreferences.getFavoritos(this,NORMA);

¡Recuerden siempre llamar a editor.clear();de lo contrario las preferencias se resetearán si la app se fuerza a cerrar o se reinicia el teléfono!

Espero que les sirva.
¡Saludos!