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!

viernes, 23 de octubre de 2015

Cuatro tipos archivos esenciales de todo desarrollo web.

Hola amigos, últimamente he estado desarrollando muchos proyectos de php y platicando con algunos colegas me he dado cuenta que a veces por hacer rápido algo luego al modificarlo tardamos demasiado tiempo buscando dónde estaba cada cosa, cambiando nombres de variables, etc. Dedicaré este espacio para los que van empezando con el desarrollo web.

Archivos de configuración.
Los archivos de configuración constan de cualquier dato de autenticación, rutas a directorios, puertos, etc. Esto incluye por ejemplo usuario y password de la base de datos, los puertos que utilizará la aplicación. La ruta hacia las imágenes o documentos subidos.
Un ejemplo podría contener lo siguiente:

<?php

//variables de conexion a la bd
$servidor = "localhost";
$usuario = "root";
$password = "1234";
$database = "edmx";

//URL's
$url_alta_tipo_equipos="registro_tipo_equipos.html";
$url_alta_usuarios="alta_users.php";
$url_imagenes="/storage/images/";
?>

Esquema de la base de datos.
En este archivo se especifican los nombres de las bases de datos, las tablas y los campos, esto con el fin de que si el diseño de la base llega a cambiar, basta con modificar el archivo de esquema para actualizar todas las dependencias, además también deberíamos almacenar las sentencias para la creación de la base de datos desde cero y un par de inserts predefinidos por si ocupamos borrarla.

Un ejemplo podría contener lo siguiente:

//TABLAS
$tabla_tipo_de_equipo="tipo_equipo";
$tabla_asesores="asesores";
$tabla_equipos="equipamiento";

//campos
$campo_id_equipo="id_equipo";
$campo_ventas_por_mes="venta_mensual";

Manejadores de la base de datos, de sesión, etc.
Es conveniente crear un archivo que contenga métodos para acciones comunes como inserción, actualización, borrado y selección (CRUD). Otra buena idea es crear un manejador de las operaciones sobre una sesión, en caso de haberla.

Un ejemplo podría contener lo siguiente, nótese que estamos usando las variables de configuracion, y algunas variables de la definición del esquema de la base de datos.

function consulta_simple($sql){
include("config.php");
$conn = new mysqli($servidor, $usuario, $password, $base);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$result = $conn->query($sql);
$resultado=array();
if ($result->num_rows > 0) {
while($row = $result->fetch_row()) {
array_push($resultado, $row);
}
}
else {
}
$conn->close();
return $resultado;
}

function consultarModelos($id_tipo, $marca){
include("config.php");
$sql = "SELECT id_equipo, modelo FROM $tabla_equipos WHERE id_tipo=$id_tipo AND marca='$marca' ORDER BY modelo;";
$marcas=consulta_simple($sql);
return $marcas;
}


Hojas de estilo base.
Crear un archivo de estilos con un diseño "default" nos permitirá modificar las propiedades visuales de muchas páginas con un solo cambio. Aprovechemos la característica de cascada para sobre-escribir los estilos necesarios y mantengamos los que sean más comunes.

Un ejemplo podría contener:

body{
background-color:black;
background-image: url("catbox.jpg");
background-size: 100%;
color:white;
}

//un encabezado genérico
.header{
background:yellow;
width:480px;
height:150px;
border: 2px solid;
    border-radius: 25px;
    border-color:blue;
}

/*contenedor genérico*/
.container{
position:relative;
left:50%;
transform: translate(-50%, 0%);
background:#084D88;
width:350px;
    border-radius: 20px;
    padding:8px;
}

Seguramente hayan usado uno o más de estos tipos de archivos, así que ya sea un recordatorio o algo nuevo, ¡úsenlos que son muy útiles!

miércoles, 21 de octubre de 2015

Guía de mejores monitores económicos 2015

Hola amigos, con la novedad de que he querido actualizar mi monitor a uno que pueda tener al menos 3 ventanas visibles al mismo tiempo me di a la tarea de encontrar monitores relativamente económicos con las mejores características para la multitarea.

Tengo que puntualizar que la tecnología escogida en todos los monitores es IPS, debido a mis requerimientos de color y visualización desde diferentes ángulos, prefiriendo los monitores de tipo "matte" debido a que reflejan menor cantidad de luz.

Las opciones no tienen un orden específico, simplemente trataré de agruparlos por tarea o utilidad, otra cosa por mencionar es que como yo casi siempre compro en amazon por la seguridad que me han proporcionado, pondre los enlaces a su sitio (no, no me dan comisión :( ).

Monitores Ultra Wide: WFHD con un formato 21:9 y resolución 2560 x 1080 son la mejor opción para mantener al menos 3 documentos abiertos al mismo tiempo. Existen diversas opciones, pero estos son los más asequibles:

Para los usuarios de monitores de 19 pulgadas o menos, el salto menos drástico es a un monitor de 25 pulgadas como el siguiente:



LG Electronics UM57 25-Inch Screen LED-lit Monitor:
Luminosidad: 250 cd.
Conectores:  2 entradas HDMI.
Controlador dual (puedes visualizar 2 entradas de video, por ejemplo una pc y un xbox al mismo tiempo) y, este monitor es una buena opción.
Precio: entre los 170 y los 200 usd en Amazon.

Si podemos aumentar nuestro presupuesto hasta el rango de los 300 usd, encontramos monitores de 29 pulgadas como:



AOC Q2963PM 29-inch IPS (21:9) LED-Lit Monitor.
Luminosidad: 300 cd.
Conectores: VGA, DVI-D HDCP, HDMI (MHL), Display Port.
2 bocinas estéreo de 3W integradas.
Montaje VESA para utilizar diferentes bases, es una excelente opción, este tamaño es ideal para los usuarios acostumbrados a monitores de 24 pulgadas.
Precio: 330 usd en Amazon.



Otro modelo asequible es el hermano mayor del monitor de 25 pulgadas, se trata del monitor
LG Electronics 29UM67 29-Inch Screen LED-lit Monitor: Con 300 cd de luminosidad, con un puerto DVI extra a los HDMI del modelo de 25 pulgadas, cabe mencionar que este monitor deberá utilizarse la base que ya tiene incorporada.
Precio: 350 USD en Amazon.


A pesar de que los monitores WFHD proveen de un área de trabajo extendida, muchos usuarios no logran acostumbrarse a ese aspecto, por lo que regresando a los monitores clásicos 16:9 consideramos la siguiente lista:

Los monitores WQHD con resolución 2560x1440 nos brindan hasta 50% más espacio de trabajo que un monitor full hd, por lo que los detalles serán mejor perceptibles. En estos modelos, el tamaño ideal es el de 25 pulgadas, con relación de aspecto 16:10, cambiando el tamaño común de 24 y 27 pulgadas que se suelen utilizar en los full hd, esto debido a la mayor densidad de píxeles que se pueden contener en los monitores de 25 y 27 pulgadas.

El modelo más económico de este tipo de monitores es el


Acer G257HU smidpx 25-Inch WQHD
Luminosidad: 350 nit.
Conectores: 1 conector DVI (HDCP), 1 HDMI y 1 Display Port.
Es una actualización económica desde un monitor full hd. Cuenta con 2 altavoces integrados.
Precio: entre 240 y 260 USD en Amazon.

Por otra parte, con un poco más de presupuesto, podemos adquirir el monitor


Monitor Dell UltraSharp de 25 pulgadas - U2515H. Este monitor cuenta con un panel que cubre el 99% de colores sRGB, por lo que es especialmente útil para trabajos de diseño gráfico o animación, cuenta con garantía de 3 años y es extensible hasta 5 años.

Luminosidad: 350cd.
Conectores: 2 conectores HDMI (MHL), 1 Mini DisplayPort, 1 DisplayPort (versión 1.2), 1 salida de DisplayPort (MST), 1 salida de línea de audio, 5 puertos USB 3.0: Flujo descendente (1 con carga de batería) y 1 puerto USB 3.0 de subida
Montaje VESA.
Base de altura ajustable, inclinación, movimiento giratorio y pivotante.
Precio: entre 4700 y 5000 pesos en DELL México. En mi opinión, el mejor monitor de 25 pulgadas que se puede adquirir en este rango de precio.

Mención especial

A pesar de que este artículo está dedicado a monitores con resoluciones mayores a 1080p debo mencionar que he adquirido un monitor Full HD del que no he tenido queja alguna. El monitor en cuestión es económico al punto de no haber competidores en su rango de precio, tal vez agarré una oferta, o no lo sé pero me salió en 2400 pesos con envío incluido. Se trata de:

Monitor AOC de 23 pulgadas i2369V
Luminosidad: 250cd.
Conectores: DVI-HDCP RGB D-Sub.
Precio: entre 2400 y 2700 pesos en Amazon México.


viernes, 12 de junio de 2015

Simular eventos, acciones, entradas y comandos en un emulador Android,

Cuando comienzas con nuevos proyectos, muchas veces no tienes los dispositivos físicos para hacer las pruebas o no quieres arriesgar información valiosa en tu dispositivo, por lo que tienes que recurrir a emuladores. Además, si tienes un pc potente, puede que incluso sea más cómodo realizar las pruebas en emuladores.

Pues bien, para realizar esto basta con ejecutar nuestra aplicación, seleccionar el AVD (Android virtual device, alias el emulador) que querramos y dentro de android studio ir a la pestaña de terminal.


Después de eso, deberán utilizar el comando

telnet localhost 5554

Si la consola les marca un error de que no se reconoce el comando, es porque no tienen telnet activado en su windows, para lo cual sigan los siguientes pasos

Para instalar el cliente Telnet

  1. Haga clic en el botón InicioImagen del botón Inicio, haga clic en Panel de control y, a continuación, en Programas.
  2. En Programas y características, haga clic en Activar o desactivar las características de Windows. Se requiere permiso de administrador Si se le solicita una contraseña de administrador o una confirmación, escriba la contraseña o proporcione la confirmación.
  3. En el cuadro de diálogo Características de Windows, active la casilla Cliente Telnet.
  4. Haga clic en Aceptar. La instalación puede durar varios minutos.
Una vez conectados podemos usar distintas funciones emuladas en nuestro dispositivo virtual como por ejemplo la función:

geo fix 19.282754, -99.676644

Que simulará que el dispositivo se encuentra en esas coordenadas (Facultad de Ingeniería de la UAEMex) por ejemplo, para utilizar en una applicación de geolocalización.

O por ejemplo una simular una llamada del 123-4567

gsm call 1234567

Entre otras funciones. Saludos.

Consultar estructura de las tablas en una base de datos de Android

Qué tal, les cuento que ahora que estuve realizando el tutorial de udacity, me atoré con un problema de llaves primarias, según el tutorial que seguí de la BD, se recomendaba poner la llave primaria en autoincrementar, y como ya saben que me gusta llevarle la contra a todo, yo hice mi tabla sin el autoincremento, simplemente agregando filas sin el id a ver qué pasaba.

Por alguna razón insensata de mi parte, supuse que los registros empezaban de 0, y cuando hacía querys para ver mi información pues me tiraba errores, sin embargo la consola marcaba que era la tabla la que no existía y no el registro, así que me pasé algunas horas peleando con por qué no aparecía la tabla si yo la creaba como debía.

Para no hacer el cuento largo, android usa bases de datos sqlite, por lo que para hacer pruebas me bajé el sqlite3 de acá.

http://sqlite.org/2015/sqlite-shell-win32-x86-3081002.zip

En esta aplicación basta con utilizar el comando .schema para ver la sentencia SQL con la que se han creado las tablas de nuestra base, sin embargo, si intentan hacer este query desde android, les va a dar error, el query respectivo para lograr esto es:

  1. String queryschema = "SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type!='meta' ORDER BY tbl_name, type DESC, name";
  2. Cursor cursor = db.rawQuery(queryschema , null);




Con este query simplemente tendrán que utilizar el método de rawquery y listo, tendrán la estructura de las tablas de su BD.

Hay que recordar que la trabla de sqlite_master contiene toda la información de nuestra BD, por lo que podemos obtener otro tipo de información aparte de las tablas.

Saludos.