Bloquear el DrawerLayout
Podemos bloquear el estado de un Drawer Layout para que se quede cerrado o abierto según nos convenga con el métododrawerLayout.setDrawerLockMode()
. Este método recibe el mo-do en que queremos que se quede bloqueado el drawer.
- Siempre cerrado
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
- Siempre abierto
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
- Desbloqueado
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
Árbol de dependencias
Cuando tenemos un conflicto con las dependencias de gradle, ejecutamos el informe de dependencias de gradle estando en el directorio raíz del proyecto, es decir, la carpeta que contiene los módulos: gradle -q app:dependencies «app
» es el nombre del módulo del proyecto cuyas dependencias queramos ver. Normalmente se llama «app
«, pero podemos haberle cambiado el nombre.
Al ejecutar esto se mostrará un arbol de dependencias donde podemos ver las versiones de cada dependencia. Cuando encontremos la que nos da conflicto y el motivo (la que no es la misma que las demás, o la que se mencione en el mensaje de error) y excluirla. Por ejemplo, si hay dos dependencias que usan la biblioteca de anotaciones de support, excluye la que creas que causa el conflicto de la dependencia que la posee. En este caso, la dependencia que la posee es espresso-contrib:
1 2 3 |
androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.0') { exclude group: 'com.android.support', module: 'support-annotations'} } |
Convertir píxeles en dpi
Para algunos aspectos tendremos que indicar medidas que solo tenemos en dpi en px y viceversa. Estos dos métodos sirven para realizar esa conversión.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/** * Convierte dpi en píxeles. * * @param dp Valor en dp * @param context Necesario para obtener los recursos y medidas específicas del dispositivo. * @return La cantidad de dpi convertida en píxeles, calculada en base a la densidad del dispositivo. */ public static float convertirDPEnPixeles(float dp, Context context){ Resources resources = context.getResources(); DisplayMetrics metrics = resources.getDisplayMetrics(); float px = dp * ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); return px; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/** * Convierte una cantidad de píxeles en un dispositvo en dpi. * * @param px Cantidad de píxeles que queremos convertir en dpi. * @param context Necesario para obtener los recursos y medidas específicas del dispositivo. * @return La cantidad de píxeles convertida en dpi. */ public static float convertirPixelesEnDP(float px, Context context){ Resources resources = context.getResources(); DisplayMetrics metrics = resources.getDisplayMetrics(); float dp = px / ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); return dp; } |
Botón sin estilo
Cuando en nuestro layout utilizamos una vista Button, este tiene por defecto un estilo intrínseco: texto en mayúsculas, un ancho y alto mínimos, y un borde con sombra. Si queremos aplicarle nuestro propio fondo o simplemente queremos eliminar este estilo, esto es lo que debemos añadir al botón en el layout:
1 2 3 4 5 |
<Button android:minHeight="0dp" android:minWidth="0dp" android:textAllCaps="false" android:background="?android:attr/selectableItemBackground" /> |
1 2 3 4 5 |
<style name="BotonSinEstilo" parent="Widget.AppCompat.Button.Borderless"> <item name="android:minHeight">0dp</item> <item name="android:minWidth">0dp</item> <item name="android:textAllCaps">false</item> </style> |
Añadir gradle-wrapper
Si nuestro proyecto no tiene gradle wrapper, podemos añadirlo abriendo la pestaña Terminal y ejecutando: gradlew wrapper --gradle-version 2.14.1 . Cambiaríamos la versión por la que fuera necesario en ese momento. Esto creará los siguientes archivos:- proyecto/
- gradlew
- gradlew.bat
- gradle/wrapper/
- gradle-wrapper.jar
- gradle-wrapper.properties
Texto de derecha a izquierda (RTL Support)
Para que nuestra aplicación invierta el layout de sus activities para mostrarlo de derecha a izquierda en teléfonos configurados con un idioma en el que se escriba así, como el árabe, el hebreo o el persa (el chino no porque en el mundo digital se escribe de izquierda a derecha), hay que añadir el siguiente atributo al elemento<application>
:
1 2 |
<application android:supportsRtl="true"> |
left
/right
, debemos cambiarlos o añadir la misma propiedad con los valores start
/end
. Si usamos targetSdkVersion
o minSdkVersion
por debajo de la 17, debes añadir los dos. Si no, puedes usar solo start
/end
.
Percátate además de que el texto se alineará a la izquierda o a la derecha pero siempre dentro de los límites de su TextView. Es decir, que si quieres que el texto pase a la derecha de la pantalla, el TextView tiene que tener una anchura match_parent
.
Fuente: http://android-developers.blogspot.com.es/2013/03/native-rtl-support-in-android-42.html
También se pueden crear layouts y otros recursos únicamente para lectura de derecha a izquierda añadiendo al nombre del directorio de dichos recursos el calificador «ldrtl
«.
Cancelar servicios de Retrofit
La biblioteca Retrofit no puede cancelar las llamadas que realiza a los servicios web. Por ello, cuando se hace una llamada, si la activity en la que se recibe el resultado de la llamada se ha cerrado y aún no se había recibido la respuesta del servicio web, al llegar ocasionará problemas, ya que la activity no existirá y sus campos y sus vistas serán nulas. Como el resultado de la llamada al servicio llega en un callback, este código se ejecutará aunque la activity se haya cerrado. Si en base al resultado de la llamada al servicio web modificamos algo de la activity y esta ya se ha cerrado, recibiremos una NullPointerException. Para evitar esto, antes de hacer ningún cambio habrá que comprobar si la activity no es nula. En el caso de que la llamada se realice en un fragment, tendremos que comprobar además si el fragment está añadido. Esto lo podemos hacer fácilmente llamando a este método:
1 2 3 |
public boolean isFragmentValid() { return getActivity() != null && isAdded() && !isRemoving(); } |
Temas
Un estilo se define en values/style.xml con el elemento<style>
.
Cuando un estilo se utiliza AndroidManifest.xml en el atributo android:theme
del elemento en <application>
o de un elemento <activity>
hablamos de tema.
El tema que declaremos en cada activity sobrescribe al tema de la aplicación.
Cuando tienes una biblioteca de Android y esta tiene estilos aplicados a sus vistas, el estilo de la aplicación:
- Sobrescribe el estilo de la aplicación de la biblioteca.
- No sobrescribe el estilo de cada activity de la biblioteca.
- No sobrescribe el estilo de cada vista dentro de la activity.
Cambiar el puntero y el cursor en un EditText
Se puede cambiar el color del cursor de un EditText aplicándole un estilo con el atirbutotextCursorDrawable
.
<item name="android:textCursorDrawable">@color/bg_blue</item>
Se le puede asignar un color o un drawable. También podemos cambiar el puntero y los indicadores de selección, pero para estos es necesario indicar un drawable:
1 2 3 |
<item name="android:textSelectHandle">@drawable/manejador_completo</item> <item name="android:textSelectHandleRight">@drawable/manejador_derecho</item> <item name="android:textSelectHandleLeft">@drawable/manejador_izquierdo</item> |
Pantalla de bienvenida
Para crar una pantalla de bienvenida o splash screen solo debemos definir comoLAUNCHER
activity una que muestre un logo y que lance la MainActivity en dos segundos en el método onCreate().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class SplashActivity extends Activity { private final static int TIEMPO_ESPERA = 2000; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash); new Handler().postDelayed(new Runnable() { public void run() { Intent intent = new Intent(SplashActivity.this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); } }, TIEMPO_ESPERA); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
public class MainActivity { private Handler sleepingHandler; private Runnable launcAcitivtyRunnable; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash); sleepingHandler = new Handler(); launchActivityRunnable = new Runnable() { public void run() { abrirActividadPrincipal(); } }; sleepingHandler.postDelayed(launchActivityRunnable, TIEMPO_ESPERA); llFondo.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { sleepingHandler.removeCallbacks(launchActivityRunnable); abrirActividadPrincipal(); } }; } public void abrirActividadPrincipal() { Intent intent = new Intent(SplashScreen.this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); } } |
Rutas muy largas
Si los nombres de los archivos son muy largos, intenta reducir el nombre de los archivos o de las carpetas intermedias. Si no puedes hacer esto porque no debes cambiar la estructura o porque son bibliotecas de terceros, puedes cambiar tu proyecto de ubicación a una ubicación con un nombre más corto en tu ordenador. Esto puedes hacerlo cortando y copiando la carpeta del proyecto o puedes hacerlo de forma virtual. Abre la consola de comandos y ejecuta: subst J: ruta/absoluta/a/la/carpeta/de/tu/proyecto Ahora el proyecto seguirá estando en C:/ruta/absoluta/a/la/carpeta/de/tu/proyecto pero también estará virtualmente en la unidad J:. Cierra el proyecto de Android Studio y ábrelo desde esta unidad. Todo lo que hagas en este proyecto se hará en la carpeta real, ya que esto solo es una forma de referenciar al directorio, no una copia. Para eliminar la unidad temporal ejecuta: subst J: /dCambio de nombre de paquete
Para cambiar el nombre del paquete de la aplicación (no elapplicationId
, sino el nombre del paquete) tienes que refactorizar el nombre de cada paquete por separado. Puedes hacerlo en el AndroidManifest.xml. En el nombre del paquete (propiedad package
) pon el cursor en cada nombre por separado y utiliza refactorizar (Shift + F6).
En el build.gradle el applicationId
tendrás que cambiarlo tú mismo si así lo quieres.
o.
Límite de métodos
Como sabemos, existe una limitación para la cantidad de métodos y clases de campos que puede haber en nuestra aplicación: 65536. Cuando se sobrepasa, recibiremos la siguiente excepción durante la compilación:
1 2 |
UNEXPECTED TOP-LEVEL EXCEPTION: com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536 |
1 2 3 4 5 |
android { defaultConfig{ multiDexEnabled true } } |
Fechas inexistentes
Podemos comprobar si una fecha recibida en formato String existe o no gracias al métodosetLenient()
de SimpleDateFormat.
1 2 3 4 5 6 7 8 9 |
String fecha = "15/13/1994"; // Esta fecha no existe SimpleDateFormar sdf = new SimpleDateFormat("dd/MM/yyyy"); sdf.setLenient(true); try { Date date = sdf.parse(fecha); } catch (Exception e) { Log.e(TAG, "La fecha no existe."); } |
setLenient(true)
, no se habría producido una excepción porque SimpleDateFormat intentaría convertir la fecha en una fecha existente. El mes 13 habría pasado a ser el més 1 y sumaría un año más a la fecha y no se lanzaría una excepción.
RecyclerView.Adapter con más de un ViewHolder
Podemos hacer que un RecyclerView.Adapter utilice tantos ViewHolders como queramos indicando que hereda de RecyclerView.Adapter<RecyclerView.ViehHolder> en lugar de indicar que contiene solo un tipo concreto de ViewHolder. Después solo tenemos que sobrescribir el métodogetViewType()
para que devuelva un tipo de vista vista u otro en función de la posición cuya vista se esté creando en ese momento y así, en el método onCreateViewHolder()
sabremos qué ViewHolder tendremos que cargar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
public class AdaptadorParImpar extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final Integer PAR = 1; private static final Integer IMPAR = 2; private List<MyData> mData; public class ViewHolderImpar extends RecyclerView.ViewHolder{ //Código del ViewHolderA } public class ViewHolderPar extends RecyclerView.ViewHolder{ //Código del ViewHolderB } @Override public int getItemViewType(int position) { if (position % 2 == 0) { return PAR; } else { return IMPAR; } } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext(); View root = null; if (viewType == PAR) { root = inflate(R.layout.par_layout, parent, false); return new ViewHolderPar(root); } else { root = inflate(R.layout.impar_layout, parent, false); return new ViewHolderImpar(root); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (position % 2 == 0) { ((ViewHolderPar).holder).bindView(mData.get(position)); } else { ((ViewHolderImpar).holder).bindView(mData.get(position)); } } } |
Problema con el margen de los FloatingActionButtons
En versiones pre Lollipop (API<21), al alinear el FAB abajo y a la derecha, el sistema le mete un margen automáticamente, mientras que en versiones Lollipop y siguientes (API>=21) no. Esto quiere decir que hay que crear una carpeta values-v21 y dentro poner un archivo dimens.xml con el siguiente código:
1 2 3 |
<resources> <dimen name="fab_margin">16dp</dimen> </resources> |
1 2 3 |
<resources> <dimen name="fab_margin">0dp</dimen> </resources> |
layout_marginBottom
y el layout_marginRight
del FAB.