Ud3 Actividades+e+Intenciones 13
Ud3 Actividades+e+Intenciones 13
Ud3 Actividades+e+Intenciones 13
CAPÍTULO 3.
Actividades e intenciones
En este capítulo seguiremos trabajando con el diseño de la interfaz de usuario. En lugar de tratar
aspectos de diseño visual, como hemos hecho en el capítulo anterior, vamos a tratar temas más
relacionados con el código. En concreto, nos centraremos en las actividades y las intenciones.
Estudiaremos también dos herramientas de gran utilidad para cualquier aplicación: la barra de
acciones y la definición de las preferencias de configuración. Además, se tratará un tipo de vista
muy práctica, aunque algo compleja de manejar: RecyclerView.
Nos vamos a centrar en los dos ejemplos de aplicaciones que estamos desarrollando,
Asteroides y Mis Lugares, para añadirle diferentes actividades. A continuación, se muestra el
esquema de navegación entre las actividades que queremos crear en Asteroides.
Objetivos:
• Describir el conjunto de actividades que forman la interfaz de usuario en una aplicación
Android.
• Mostrar cómo podemos, desde una actividad, invocar a otras y cómo podemos
comunicarnos con ellas.
• Incorporar a nuestras aplicaciones ciertos elementos prácticos, tales como los menús
o las preferencias.
(actualizado 11/10/23) 1
Aplicaciones para dispositivos móviles
Las aplicaciones creadas en los ejemplos hasta ahora disponían de una única actividad, que
se creaba automáticamente y a la que se asignaba la vista definida en
res/layout/activity_main.xml. Esta actividad era arrancada al comenzar la aplicación. A medida
que nuestra aplicación crezca, será imprescindible crear nuevas actividades. En este apartado
describiremos cómo hacerlo. Este proceso se puede resumir en cuatro pasos:
• Crear un nuevo layout para la actividad.
• Crear una nueva clase descendiente de Activity. En esta clase tendrás que indicar
que el layout a visualizar es el desarrollado en el punto anterior.
• Para que nuestra aplicación sea visible, será necesario activarla desde otra actividad.
• De forma obligatoria tendremos que registrar toda nueva actividad en
AndroidManifest.xml.
Veamos un primer ejemplo de cómo crear una nueva actividad en la aplicación que estamos
desarrollando.
(actualizado 11/10/23) 2
Aplicaciones para dispositivos móviles
android:id="@+id/TextView01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="Este programa ha sido desarrollado como ejemplo en el curso de
Android para demostrar cómo se pueden lanzar nuevas actividades desde la actividad
principal.">
</TextView>
3. Creamos ahora una nueva actividad, que será la responsable de visualizar esta vista. Para
ello crea el fichero AcercaDeActivity.java pulsando con el botón derecho sobre el nombre del
paquete de la aplicación y seleccionando New > Java Class. En el campo Name introduce
AcercaDeActivity y pulsa Finish. Reemplaza el código por:
public class AcercaDeActivity extends AppCompatActivity {
Nota sobre Java: Pulsa Alt-Intro en las dos clases modificadas para que automáticamente
se añadan los paquetes que faltan.
4. Pasemos ahora a crear un método en la actividad principal que se ejecutará cuando se pulse
el botón Acerca de.
public void lanzarAcercaDe(View view){
Intent i = new Intent(this, AcercaDeActivity.class);
startActivity(i);
}
NOTA: En caso de que exista algún recurso alternativo para activity_main.xml o content_main.xml,
repite el mismo proceso.
7. Ejecuta ahora la aplicación y pulsa el botón Acerca de. Observarás que el resultado no es
satisfactorio. ¿Qué ha ocurrido?
El problema es que toda actividad que ha de ser lanzada por una aplicación ha de ser
registrada en el fichero AndroidManifest.xml. Para registrar la actividad, abre
AndroidManifest.xml. Añade el siguiente texto dentro de la etiqueta <application …> </
application>:
<activity android:name=".AcercaDeActivity"
android:label="Acerca de ..."/>
(actualizado 11/10/23) 3
Aplicaciones para dispositivos móviles
(actualizado 11/10/23) 4
Aplicaciones para dispositivos móviles
binding.button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
lanzarAcercaDe(null);
}
});
}
Nota sobre Java: Pulsa Alt-Intro en las dos clases modificadas para que automáticamente
se añadan los paquetes que faltan. Para la clase OnClickListener selecciona
android.view.View.OnClickListener.
2. Elimina el atributo añadido al botón:
android:onClick="lanzarAcercaDe"
3. Ejecuta la aplicación. El resultado ha de ser idéntico al anterior.
Cuando la actividad lanzada (B) termina, si lo desea, podrá enviar datos de vuelta. Para ello
añade en la actividad B el siguiente código:
Intent intent = new Intent();
intent.putExtra("resultado","valor");
setResult(RESULT_OK, intent);
finish();
Es posible que el trabajo realizado en la actividad B sea cancelado. Para este caso añade:
Intent intent = new Intent();
setResult(RESULT_CANCEL, intent);
finish();
(actualizado 11/10/23) 5
Aplicaciones para dispositivos móviles
En la actividad que hizo la llamada (A) has de poder recoger estos datos. Para ello tendremos
que lanzar la actividad usando un objeto de la clase ActivityResultLauncher, en lugar de
startActivity(Intent). Este objeto permite definir un escuchador de evento, que será llamado
cuando la actividad lanzada retorne. Crear este objeto como un atributo de la clase. Para ello
introduce el siguiente código fuera de un método:
ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
//Acciones a realizar
}
}
});
(actualizado 11/10/23) 6
Aplicaciones para dispositivos móviles
La clase ToolBar aparece con la versión 5.0. Cambia el diseño de la barra de acciones para
que siga las especificaciones de Material Design. Puede usarse en versiones anteriores dado
que no se incorpora al API de la versión 5.0, si no a una la librería de compatibilidad appcompat.
La barra de acciones no es incrustada de forma automática, sino que hay que incluirla en el
layout con la etiqueta <Toolbar>. Esto nos permite situarla en la posición que queramos y nos da
más opciones de configuración.
Añadir un ToolBar a la aplicación es muy sencillo. No es necesario realizarlo si al crear un
proyecto has seleccionado Basic Views Activity, dado que en este caso, ya se ha añadido un
Toolbar. Si tu aplicación no dispone de un ActionBar, por ejemplo si ha sido creada con Empty
Views Activity, sigue el siguiente ejercicio para incluirlo.
(actualizado 11/10/23) 7
Aplicaciones para dispositivos móviles
Con los dos últimos atributos podemos controlar el tema aplicado al Toolbar y al menú de
overflow.
3. Si lo añades dentro de un ConstraintLayout Asegúrate de indicar las restricciones de
posición:
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
4. Si estás en MainActivity de Mis Lugares, sitúa el TextView para que se vea debajo del
Toolbar en lugar de en la parte superior del contenedor.
5. Verifica que la actividad donde insertas el Toolbar desciende de AppCompatActivity.
6. Añade en el método onCreate() el siguiente código:
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
Como puedes ver cada ítem de menú tiene cinco atributos: id que permite identificarlo desde
el código; title, para asociarle un texto; icon, para asociarle un icono; orderInCategory,
permite ordenar las acciones según el número indicado. Las acciones con un número más
pequeño se sitúan más a la izquierda. Si no caben todas las acciones en la barra, las que
tienen un número mayor se mueven al menú de Overflow. Finalmente, el atributo
showAsAction permite indicar que acciones son ocultadas en el menú de Overflow y cuales están
(actualizado 11/10/23) 8
Aplicaciones para dispositivos móviles
siempre visibles. Si se indica always se mostrarán siempre, sin importar si caben o no. El uso de
estas acciones debería limitarse, lo ideal es que haya una o dos, ya que al forzar que se visualicen
muchas podrían verse incorrectamente. Las acciones que indiquen ifRoom se mostrarán en la
barra de acciones si hay espacio disponible, y se moverán al menú de Overflow si no lo hay. En
esta categoría se deberían encontrar la mayoría de las acciones. Si se indica never, la acción
nunca se mostrará en la barra de acciones, sin importar el espacio disponible. En este grupo se
deberían situar acciones como modificar las preferencias, que deben estar disponibles para el
usuario, pero no visibles en todo momento.
2. Para activar el menú, has de introducir el siguiente código en la actividad que muestra el
menú:
@Override public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true; /** true -> el menú ya está visible */
}
3. Ejecuta la aplicación. Podrás ver como aparece la barra de acciones en la parte de arriba,
con los botones que hemos definido.
Android Studio incorpora un editor visual de menús que nos permite crear menús sin
necesidad de escribir código xml.
(actualizado 11/10/23) 9
Aplicaciones para dispositivos móviles
(actualizado 11/10/23) 10
Aplicaciones para dispositivos móviles
Circulares - round icons (2): Aparecen en la versión 7.1 (API 25) cuando muchas
distribuciones de Android optaron por utilizar iconos circulares. Algunas, incluso, daban la
posibilidad de escoger al usuario entre iconos normales y circulares:
Para que el icono de nuestra aplicación se vea uniforme con el resto de iconos, es interesante
proporcionar los dos tipos de iconos. Para ello se ha añadido un nuevo atributo en
AndroidManifest:
<application
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"… />
Adaptativos - adaptative icons (3): Aparecen en la versión 8.0 (API 26) para dar mayor
flexibilidad en la representación de los iconos. Para diseñar un icono habrá que proporcionar dos
capas: fondo y primer plano; luego, se aplicará una máscara:
Nosotros proporcionaremos las dos capas, pero la máscara la pondrá el sistema según las
preferencias del usuario:
Verifica como ambos ficheros están dentro de la carpeta drawable. Son definidos de forma
vectorial. Trabajar con un formato vectorial tiene importantes ventajas. Por ejemplo, los iconos
pueden generarse con diferentes dimensiones o pueden girarse. Además, ocupan menos
memoria.
Enlaces de interés:
(actualizado 11/10/23) 11
Aplicaciones para dispositivos móviles
Guía de estilo para iconos: La siguiente página describe las guías de estilo para los iconos en
Material Design:
https://youtu.be/wt0Jzc9UHNw
https://material.io/design/iconography
Recursos de iconos: En las siguientes páginas puedes encontrar gran variedad de iconos:
https://material.io/tools/icons
https://android-material-icon-generator.bitdroid.de/
NOTA: El objetivo de esta unidad no es describir los gráficos vectoriales, pero, si tienes curiosidad, te
damos algunas claves para que entiendas este fichero. Primero se indica el ancho y alto que tendrá el
gráfico y luego, el ancho y alto con el que lo vamos a dibujarlo. La etiqueta path permite realizar un
trazado rellenándolo del color indicado. Nos movemos a la coordenada 0,2 (M0,2); trazamos una línea
hasta 2,2 (L2,2); otra línea hasta 3,0 (L3,0). Se sigue trazando líneas hasta llegar a Z, que significa
trazar una línea hasta el primer punto (0,2) y rellena la figura.
2. Repite el proceso para el fichero fondo.xml.
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="5.0"
android:viewportHeight="5.0">
<path android:fillColor="#FFFFFF"
android:pathData="M0,0h5v5h-5z"/>
<path android:strokeColor="#394077"
android:strokeWidth="0.1"
android:pathData="M0,1L5,1 M0,2L5,2 M0,3L5,3 M0,4L5,4"/>
</vector>
NOTA: Este fichero dibuja dos path. Un rectángulo blanco que ocupa toda el área de dibujo y cuatro
líneas horizontales de color azul.
3. Selecciona File/New/Image Asset. Selecciona en Icon Type: Launcher Icons (Adaptive and
Legacy); en Name: ic_mi_icono; en Asset Type: Image; en Path: pulsa el icono de la carpeta,
en la parte superior pulsa en el icono de Android, para buscar un fichero en tu proyecto.
Selecciona app\ src\ main\ res\ drawable\ estrella.xml. Desplaza la barra Resize hasta
conseguir un tamaño adecuado.
4. Selecciona la lengüeta Background Layer. Selecciona en Asset Type: Image; en Path: pulsa
el icono de la carpeta y repite el proceso anterior para seleccionar fondo.xml. Desplaza la
barra Resize hasta conseguir un tamaño adecuado. El resultado ha de ser similar al
siguiente:
(actualizado 11/10/23) 12
Aplicaciones para dispositivos móviles
5. Pulsa en Next y se mostrará una previsualización de todos los ficheros generados. Pulsa en
Finish para acabar.
6. Verifica como se han creado en res/mipmap/ic_mi_icono cinco gráficos png a partir del
gráfico vectorial. Selecciona el de densidad mdpi. El resultado no ha sido 100 % satisfactorio.
Las líneas horizontales tendrían que ser del mismo grosor. Para ver si el usuario llegará a
apreciarlo, reduce el zoom hasta que el icono tenga un tamaño similar al que tendrá en un
móvil. Si sigues apreciando que las rallas son diferentes, es buena idea retocar este fichero.
Abre el png con un editor gráfico y redibuja las líneas horizontales para que tenga el mismo
grosor.
Aunque este no es el caso, algunos iconos tienen muchos detalles. Cuando se representan
para mdpi con tan solo 48x48 píxeles, puede ocurrir que se vea de forma incorrecta. En baja
densidad se recomienda cambiar a un diseño más simple. Mira el siguiente ejemplo:
7. Verifica como se han creado en res/mipmap/ic_mi_icono_round cinco gráficos png con forma
circular.
8. Verifica como se ha creado el icono adaptativo en res/mipmap/ic_mi_icono.xml. Comprueba
también como se han creado las dos capas que corresponden a versiones redimensionadas
de los ficheros estrella.xml y fondo.xml.
9. Para aplicar este icono como el lanzador de tu aplicación, modifica el fichero
AndroidManifest:
<application
android:icon="@mipmap/ic_mi_icono"
android:roundIcon="@mipmap/ic_mi_icono"… />
(actualizado 11/10/23) 13
Aplicaciones para dispositivos móviles
(actualizado 11/10/23) 14
Aplicaciones para dispositivos móviles
mostrará para introducir el valor. Coinciden con el atributo de EditText. Para ver los posibles
valores consultar developer.
android.com/reference/android/widget/TextView.html#attr_android:inputType.
5. Para almacenar los valores del desplegable, has de crear el fichero /res/values/arrays.xml
con el siguiente contenido. Para ello pulsa con el botón derecho sobre la carpeta res y
selecciona la opción New > Android resource file. Completa los campos File name: arrays y
Resource type: Values.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="tiposOrden">
<item>Creación</item>
<item>Valoración</item>
<item>Distancia</item>
</string-array>
<string-array name="tiposOrdenValores">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
</resources>
(actualizado 11/10/23) 15
Aplicaciones para dispositivos móviles
Desde una actividad podemos visualizar un fragment en tiempo de ejecución. Para ello
utilizamos el manejador de fragments de la actividad ( getSupportFragmentManager()) y
comenzamos una transacción ( beginTransaction()). Una transacción es una operación de
insertado, borrado o reemplazo de fragments. En el ejemplo vamos a reemplazar el
contenido de la actividad por un nuevo fragment de la clase PreferenciasFragment.
Finalmente se llama a commit() para que se ejecute la transacción.
9. No hay que olvidar registrar toda nueva actividad en AndroidManifest.xml.
10. Añade a MainActivity.java el método lanzarPreferencias(). Este método ha de tener el
mismo código que lanzarAcercaDe(), pero lanzando la actividad PreferenciasActivity.
En el layout activity_main.xml añade al botón con el texto «Configurar» en el atributo onClick
el valor lanzarPreferencias.
11. Para activar la configuración desde la opción de menú, añade el siguiente código en el fichero
MainActivity.java en el método onOptionsItemSelected():
if (id == R.id.action_settings) {
lanzarPreferencias(null);
return true;
}
Si has hecho la práctica “Casos de Uso para arrancar actividades” utiliza mejor el código
usoActividades.lanzarPreferencias() para lanzar la actividad.
12. Arranca la aplicación y verifica que puedes lanzar las preferencias mediante las dos
alternativas.
NOTA: Si introduces un valor en el campo donde se ha indicado inputType="number",
comprobarás que no funciona correctamente. Se trata de un bug de Google, todavía sin resolver.
De momento nos recomiendan que usemos la siguiente librería
https://github.com/Gericop/Android-Support-Preference-V7-Fix. Sigue los dos sencillos pasos
indicados en README.md.
(actualizado 11/10/23) 16
Aplicaciones para dispositivos móviles
Otra alternativa para organizar las preferencias consiste en agruparlas por categorías. Con
esta opción se visualizarán en la misma pantalla, pero separadas por grupos. Has de seguir el
siguiente esquema:
<PreferenceScreen>
<CheckBoxPreference …/>
<EditTextPreference …/>
…
<PreferenceCategory android:title=”Modo multijugador”>
<CheckBoxPreference …/>
<EditTextPreference …/>
. . …
</PreferenceCategory>
</PreferenceScreen>
(actualizado 11/10/23) 17
Aplicaciones para dispositivos móviles
Finalmente se visualiza el resultado utilizando la clase Toast. Los tres parámetros indicados
son el contexto (nuestra actividad), el String a mostrar y el tiempo que se estará mostrando esta
información.
(actualizado 11/10/23) 18
Aplicaciones para dispositivos móviles
NOTA: Si trabajas con Asteroides reemplaza "maximo" por "fragmentos" y cambia los textos
necesarios para que correspondan con esta propiedad.
El código comienza obteniendo una referencia de la preferencia, para asignarle un
escuchador que será llamado cuando cambie su valor. El escuchador comienza convirtiendo
el valor introducido a entero. Si se produce un error es porque el usuario no ha introducido
un valor adecuado. En este caso, mostramos un mensaje y devolvemos false para que el
valor de la preferencia no sea modificado. Si no hay error, tras verificar el rango de valores
aceptables, modificamos la explicación de la preferencia para que aparezca el nuevo valor
entre paréntesis y devolvemos true para aceptar este valor. Si no está en el rango,
mostramos un mensaje indicando el problema y devolvemos false.
2. Ejecuta el proyecto y verifica que funciona correctamente.
(actualizado 11/10/23) 19
Aplicaciones para dispositivos móviles
En nuestra aplicación queremos que el usuario disponga de un saldo de puntos, con los
que podrá ir desbloqueando ciertas características especiales. La clase Application es
descendiente de Context, por lo que tendremos acceso a todos los métodos relativos a
nuestro contexto. Entre estos métodos se incluye getSharedPreferences, para acceder a
un fichero de preferencias almacenado en la memoria interna de nuestra aplicación. La
clase Application permite sobrescribir los siguientes:
onCreate() llamado cuando se cree la aplicación. Puedes usarlo para inicializar los datos.
onConfigurationChanged(Configuration nuevaConfig) llamado cuando se realicen
cambios en la configuración del dispositivo, mientras que la aplicación se está
ejecutando.
(actualizado 11/10/23) 20
Aplicaciones para dispositivos móviles
onLowMemory() llamado cuando el sistema se está quedando sin memoria. Trata de liberar
toda la memoria que sea posible.
onTrimMemory(int nivel) (desde nivel API 14) llamado cuando el sistema determina que es
un buen momento para que una aplicación recorte memoria. Esto ocurrirá, por
ejemplo, cuando está en el fondo de la pila de actividades y no hay suficiente memoria
para mantener tantos procesos en segundo plano. Además, se nos pasa como
parámetro el nivel de necesidad. Algunos valores posibles son: TRIM_MEMORY_COMPLETE,
TRIM_MEMORY_BACKGROUND, TRIM_MEMORY_MODERATE, …
o incluso directamente:
int miSaldo = ((Aplicacion) getApplication()).getSaldo();
Nota: Tras incluir nuevas clases tendrás que indicar los imports adecuados. Pulsa «Alt+Intro» en
Android Studio para que lo haga automáticamente.
Nota: Algunas clases no están definidas. Se resuelve en el punto 4.
3. Registra el nombre de la clase en AndroidManifest, añadiendo la línea que aparece en
negrita.
<application
android:name="Aplicacion"
android:allowBackup="true"
(actualizado 11/10/23) 21
Aplicaciones para dispositivos móviles
...
private Saldo() {}
(actualizado 11/10/23) 22
Aplicaciones para dispositivos móviles
(actualizado 11/10/23) 23
Aplicaciones para dispositivos móviles
La vistas RecyclerView no ha sido añadida a ningún API, si no que se añade a una librería
de compatibilidad. En este apartado se hace una introducción de sus funcionalidades básicas.
Sus principales ventajas son:
• Reciclado de vistas (RecyclerView.ViewHolder)
• Distribución de vistas configurable (LayoutManager)
• Animaciones automáticas (ItemAnimator)
• Separadores de elementos (ItemDecoration)
• Trabaja conjuntamente con otros widgets introducidos en Material Design
(CoordinationLayout)
(actualizado 11/10/23) 24
Aplicaciones para dispositivos móviles
NOTA: Si lo deseas, puedes saltarte este paso. Más adelante, cuando aparezca RecyclerView en el código
Java, la clase aparecerá marcada en rojo, al no encontrar su declaración. Al pulsar sobre la clase,
aparecerá una bombilla roja donde podrás elegir la opción Add dependency on
androidx.recyclerview:recyclerview. El mismo añadirá la dependencia, con la ventaja de seleccionar la
última versión disponible.
La clase RecyclerView no ha sido añadida al API de Android, si no que se encuentra en una
librería externa.
1. Reemplaza en el layout activity_main.xml el TextView y los cuatro botones por el siguiente
ReciclerView:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="0dp"
(actualizado 11/10/23) 25
Aplicaciones para dispositivos móviles
android:layout_height="0dp"
/>
2. Ajusta los constraints para que se sitúe debajo del Toolbar, ocupando todo el espacio.
3. En la práctica “Recursos alternativos en Mis Lugares” se crea un recurso alternativo para
este layout en res/layout/activity_main.xml (land). Elimina este recurso alternativo. NOTA: Al
borrarlo has de desactiva la opción Safe delete. Si hay otros recursos alternativos elimínalos
también.
4. En la actividad MainActivity añade el código subrayado:
public class MainActivity extends AppCompatActivity {
…
private RecyclerView recyclerView;
public AdaptadorLugares adaptador;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(…);
…
adaptador = ((Aplicacion) getApplication()).adaptador;
recyclerView = findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adaptador);
}
…
(actualizado 11/10/23) 26
Aplicaciones para dispositivos móviles
<TextView android:id="@+id/direccion"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:maxLines="1"
android:text="dirección del lugar"
app:layout_constraintTop_toBottomOf="@id/nombre"
app:layout_constraintStart_toEndOf="@+id/foto"
app:layout_constraintEnd_toEndOf="parent"/>
<RatingBar android:id="@+id/valoracion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/ratingBarStyleSmall"
android:isIndicator="true"
android:rating="3"
app:layout_constraintTop_toBottomOf="@id/direccion"
app:layout_constraintLeft_toRightOf="@+id/foto"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
(actualizado 11/10/23) 27
Aplicaciones para dispositivos móviles
Un adaptador es un mecanismo estándar en Android que nos permite crear una serie de vistas
que han de ser mostradas dentro de un contenedor. Con RecyclerView has de heredar de la clase
RecyclerView.Adapter para crear el adaptador.
En el constructor se inicializa el conjunto de datos a mostrar (en el ejemplo lugares) y otras
variables globales a la clase. El objeto inflador nos va a permitir crear una vista a partir de
su XML.
Luego se crea la clase ViewHolder, que contendrá las vistas que queremos modificar de un
elemento (en concreto: dos TextView con el nombre y la dirección, un ImageView con la
imagen del tipo de lugar y un RatingBar). Esta clase es utilizada para evitar tener que crear
las vistas de cada elemento desde cero. Lo va a hacer es utilizar un ViewHolder que
contendrá las cuatro vistas ya creadas, pero sin personalizar. De forma que, gastará el mismo
ViewHolder para todos los elementos y simplemente lo personalizaremos según la posición.
Es decir, reciclamos el ViewHolder. Esta forma de proceder mejora el rendimiento del
RecyclerView, haciendo que funcione más rápido.
El método onCreateViewHolder() devuelve una vista de un elemento sin personalizar. Podríamos
definir diferentes vistas para diferentes tipos de elementos utilizando el parámetro viewType.
Usamos el método inflate() para crear una vista a partir del layout XML definido en
elemento_lista. Como segundo parámetro indicamos el layout padre donde se va a insertar la
vista. El tercer parámetro del método permite indicar si queremos que la vista sea insertada en el
padre. Indicamos false, dado que esta operación la va a hacer el RecyclerView.
El método onBindViewHolder() personaliza un elemento de tipo ViewHolder según su
posición. A partir del ViewHolder que personalizamos ya es el sistema quien se encarga de
crear la vista definitiva que será insertada en el RecyclerView. Finalmente, el método
getItemCount() se utiliza para indicar el número de elementos a visualizar.
9. Ejecuta la aplicación y verifica el resultado.
(actualizado 11/10/23) 28
Aplicaciones para dispositivos móviles
3. Solo nos queda aplicar este escuchador a cada una de las vistas creadas. Añade la línea
subrayada en el método onCreateViewHolder():
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
…
view.setOnClickListener(onClickListener);
return new AdaptadorLugares.ViewHolder(view);
}
4. Desde la clase MainActivity vamos a asignar un escuchador. Para ello añade el siguiente
código al método onCreate():
adaptador.setOnItemClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos= recyclerView.getChildAdapterPosition(v);
mostrarLugar(pos);
}
});
(actualizado 11/10/23) 29
Aplicaciones para dispositivos móviles
(actualizado 11/10/23) 30
Aplicaciones para dispositivos móviles
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="tipo del lugar" />
</LinearLayout>
…
<RatingBar
android:id="@+id/valoracion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:rating="3" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/foto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:contentDescription="fotografía"
android:src="@drawable/foto_epsg" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right" >
<ImageView
android:id="@+id/camara"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="logo cámara"
android:src="@android:drawable/ic_menu_camera" />
<ImageView
android:id="@+id/galeria"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="logo galería"
android:src="@android:drawable/ic_menu_gallery" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
</ScrollView>
(actualizado 11/10/23) 31
Aplicaciones para dispositivos móviles
3. Reemplaza los puntos suspensivos por los elementos que faltan para obtener la apariencia
mostrada al principio de este punto. Utiliza los recursos del sistema mostrados en la siguiente
tabla. Identifica cada TextView con el id que se indica.
Recurso para ImageView Id para TextView
@android:drawable/ic_menu_myplaces @+id/direccion
@android:drawable/ic_menu_call @+id/telefono
@android:drawable/ic_menu_mapmode @+id/url
@android:drawable/ic_menu_info_details @+id/comentario
@android:drawable/ic_menu_my_calendar @+id/fecha
@android:drawable/ic_menu_recent_history @+id/hora
nombre.setText(lugar.getNombre());
logoTipo.setImageResource(lugar.getTipo().getRecurso());
tipo.setText(lugar.getTipo().getTexto());
direccion.setText(lugar.getDireccion());
telefono.setText(Integer.toString(lugar.getTelefono()));
url.setText(lugar.getUrl());
comentario.setText(lugar.getComentario());
fecha.setText(DateFormat.getDateInstance().format(
new Date(lugar.getFecha())));
hora.setText(DateFormat.getTimeInstance().format(
new Date(lugar.getFecha())));
valoracion.setRating(lugar.getValoracion());
valoracion.setOnRatingBarChangeListener(
new OnRatingBarChangeListener() {
@Override public void onRatingChanged(RatingBar ratingBar,
float valor, boolean fromUser) {
lugar.setValoracion(valor);
}
});
}
}
(actualizado 11/10/23) 32
Aplicaciones para dispositivos móviles
Nota: Pulsa Alt-Intro para que automáticamente se añadan los imports con los paquetes que faltan. Dos
clases aparecen en varios paquetes. Selecciona java.text.DateFormat y java.util.Date.
Se definen varias variables globales para que se pueda acceder a ellas desde cualquier método
de la clase: lugares corresponde con el repositorio de lugares, cuya referencia se obtiene desde
Application; pos es la posición del elemento que vamos a visualizar; lugar es el elemento en
sí.
El método onCreate() se ejecutará cuando se cree la actividad. Tras llamar al super se asocia
el layout de la actividad con setContentView(). A continuación, obtiene el repositorio de lugares
desde Aplicacion y se averigua la posición del lugar a mostrar, que ha sido pasado en un extra.
A partir de la posición obtenemos el objeto lugar a mostrar. Se crea un objeto para acceder a
los casos de uso y se llama a actualizaVistas() donde se inicializa el contenido de cada vista
según el lugar a mostrar.
Observa cómo se obtiene una referencia a cada uno de los elementos del layout utilizando
el objeto findViewById(). Al final se realiza una acción especial con el objeto valoracion,
utilizando el método setOnRatingBarChangeListener() para asignarle un escuchador de
eventos al RatingBar que se crea allí mismo. Este escuchador de evento se activará cuando
el usuario modifique la valoración. El código a ejecutar consiste en modificar la propiedad
valoracion del objeto lugar con la nueva valoración.
Nota sobre Java / Kotlin: Es posible crear un objeto sin que este disponga de un
identificador de objeto. Este tipo de objeto se conoce como objeto anónimo. El código mostrado a
continuación a la derecha es equivalente al de la izquierda.
Clase objeto = new Clase(); new Clase().metodo();
objeto.metodo();
Un objeto anónimo no tiene identificador, por lo que solo puede usarse donde se crea. En el método
anterior se ha creado un objeto anónimo de la clase AlertDialog.Builder. Observa cómo no se
llama a un método, si no a una cadena de métodos. Esto es posible porque los métodos de la clase
AlertDialog.Builder retornan el objeto que estamos creando. Por lo tanto, cada método se aplica
al objeto devuelto por el método anterior.
(actualizado 11/10/23) 33
Aplicaciones para dispositivos móviles
(actualizado 11/10/23) 34
Aplicaciones para dispositivos móviles
android:orderInCategory="30"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/accion_borrar"
android:title="borrar"
android:icon="@android:drawable/ic_menu_delete"
android:orderInCategory="40"
app:showAsAction="ifRoom"/>
</menu>
5. Ejecuta la aplicación y borra un lugar. Verifica que, si tratas de visualizar el mismo id, ahora
se muestra el siguiente lugar. Más adelante trataremos de actualizar la lista, tras borrar un
lugar.
(actualizado 11/10/23) 35
Aplicaciones para dispositivos móviles
6. Puedes eliminar el resto del código de este método , que hace referencia a logo_tipo, tipo,
fecha, hora y valoracion.
7. Crea un caso de uso, con la función editar(pos) que abra la actividad que acabas de crear.
Usa mostrar(pos) como referencia.
8. En la clase VistaLugarActivity, dentro del método onOptionsItemSelected(), añade el
código necesario para que se llame a esta función.
9. Ejecuta el proyecto. Pero antes, piensa si falta alguna acción por realizar. Si la aplicación
falla abre el Logcat y comprueba el error que has cometido. Si funciona correctamente,
podrás comprobar que los valores pueden ser editados, pero no son almaceados. Lo
haremos más adelante.
(actualizado 11/10/23) 36
Aplicaciones para dispositivos móviles
Como has podido verificar en la ejecución anterior, el Spinner (lista desplegable) no muestra
ningún valor. En este ejercicio trataremos de que funcione correctamente:
Para inicializar los valores que puede tomar un Spinner, necesitamos una clase especial
conocida como Adapter. Esta clase se estudiará en la siguiente unidad. De momento, solo
adelantamos que un Adapter va a crear una lista de vistas, inicializándolas con unos valores
determinados. La clase ArrayAdapter<String> es un tipo de Adapter que permite inicializar
sus valores a partir de un array de String. Su constructor necesita tres parámetros: un
contexto (usamos la actividad actual), una vista para mostrar elemento (usamos un vista
definida en el sistema) y un array de String. Para el último parámetro necesitamos un array
con todos los valores, que puede tomar el enumerado TipoLugar. Para obtener este array,
se define un nuevo método, que se muestra a continuación.
El siguiente método, setDropDownViewResource(), permite indicar una vista alternativa que
se usará cuando se despliegue el Spinner. En la versión 4.x, esta vista es un poco más
grande que la usada en el método anterior, para poder seleccionarla cómodamente con el
dedo. Este código concluye asignando el adaptador al Spinner y poniendo un valor inicial
según el tipo actual de lugar.
2. Añade el siguiente método a la clase TipoLugar:
public static String[] getNombres() {
String[] resultado = new String[TipoLugar.values().length];
for (TipoLugar tipo : TipoLugar.values()) {
resultado[tipo.ordinal()] = tipo.texto;
}
return resultado;
}
(actualizado 11/10/23) 37
Aplicaciones para dispositivos móviles
6. Ejecuta la aplicación. Modifica algún lugar y pulsa Guardar. Al regresar a la actividad anterior,
los valores permanecen sin variación. Sin embargo, si pulsas la tecla de volver y entras a
visualizar el mismo lugar, los cambios sí que se actualizan. ¿Qué puede estar pasando?
(actualizado 11/10/23) 38
Aplicaciones para dispositivos móviles
puedes ver en el código del paso 1, solo nos interesa actualizar las vistas con RESULT_OK.
Para indicar de forma adecuada el resultado, añade el código subrayado en
EdiciónLugarActivity:
case R.id.accion_cancelar:
setResult(RESULT_CANCELED);
finish();
return true;
case R.id.accion_guardar:
…
setResult(RESULT_OK);
finish();
return true;
5. Ejecuta la aplicación y verifica que al editar un lugar y volver a VistaLugarActivity los datos
son actualizados.
1 https://devexperto.com/clean-architecture-android/
(actualizado 11/10/23) 39
Aplicaciones para dispositivos móviles
Capa de Modelo
También se utiliza el nombre de Dominio o Lógica de Negocio. Está formada por las clases que
representan la lógica interna de la aplicación y cómo representamos los datos con los que vamos
a trabajar. Muchas clases de esta capa se conocen como POJO (Plain Old Java Object) al
tratarse de clases Java puras. Ejemplos de POJO serían las clases Lugar o GeoPunto. No es
conveniente que en estas clases se utilicen APIs externos. Si abres las clases que hemos
indicado, podrás comprobar que no necesitan ningún import.
Capa de Datos
En esta capa estarían las clases encargadas de guardar de forma permanente los datos y cómo
acceder a ellos. Suelen representar bases de datos, servicios Web, preferencias, ficheros
JSON… También es conocida como capa de Almacenamiento o Persistencia.
Capa de Presentación
Representa la interfaz de usuario, por lo que está formada por las actividades, fragments, vistas
y otros elementos con los que interactúa el usuario.
Una de las características más importantes de esta arquitectura es la regla de dependencia
entre capas. Para representar las dependencias se suelen usar un diagrama en forma de
círculos concéntricos. Las capas más internas son aquellas que están más cercanas a nuestra
lógica de dominio, no deben depender de las capas más externas del software, aquellas que
están más cerca a los agentes externos como el framework, o el interfaz de usuario.
Por ejemplo, la clase Lugar que pertenecería a la capa de Modelo, va a poder ser utilizada
por el resto de las capas y no puede usar otras clases que no sean de su capa. Por el contrario,
una actividad perteneciente a la capa de presentación no debería usarse por el resto de las capas
y puede usar cualquier capa interior.
Nota: En la literatura la capa de casos de uso es más interna que la de datos. En esta implementación se
ha realizado al revés. Reamente no vamos a seguir la Arquitectura Clean de forma estricta. En nuestra
implementación la capa de Usos de Datos va a tener dependencias con la capa de Presentación, cosa
que habría que evitar.
(actualizado 11/10/23) 40
Aplicaciones para dispositivos móviles
// OPERACIONES BÁSICAS
public void mostrar(int pos) {
Intent i = new Intent(actividad, VistaLugarActivity.class);
i.putExtra("pos", pos);
actividad.startActivity(i);
}
}
Dentro de esta clase vamos a añadir diferentes funciones que ejecutarán distintos casos de uso
referentes a un lugar. Por ejemplo, compartir un lugar, borrarlo, … De momento solo añadimos
un caso de uso, mostar(pos), que arrancará una actividad mostrando la información del lugar
según su posición en el RecyclerView.
(actualizado 11/10/23) 41
Aplicaciones para dispositivos móviles
Se han añadido dos propiedades a la clase. La primera, actividad, nos va a permitir extraer el
contexto o lanzar otras actividades. Nota: No se cumple la regla de dependencias de la arquitectura
Clean, se añade porque va a simplificar el código. La segunda, lugares, nos va a permitir acceder
al repositorio de los lugares.
2. Añade en MainActivity las siguientes propiedades:
private RepositorioLugares lugares;
private CasosUsoLugar usoLugar;
Cada vez que queramos ejecutar este caso de uso usaremos el código:
usoLugar.mostrar(pos)
Este código será usado en el en siguiente ejercicio. Para evitar que de error puedes comentar
el contenido de la función mostrar().
Una vez que completes la aplicación Mis Lugares, las clases para casos de uso acaban
conteniendo decenas de métodos. De esta forma, va a ser muy sencillo saber qué hace cada método,
donde está y las dependencias que necesita. De lo contrario, todo este código acabaría en las
actividades, que contendría cientos de líneas de código, siendo muy difíciles de mantener.
(actualizado 11/10/23) 42
Aplicaciones para dispositivos móviles
Constante Acción
Visualiza un contacto, mapa, página Web,…
ACTION_VIEW
o reproduce música o vídeo,
ACTION_INSERT Inserta un contacto o cita en calendario
ACTION_EDIT Edita un contacto o cita en calendario
ACTION_MAIN Arranca como actividad principal de una tarea
ACTION_CALL Inicializa una llamada de teléfono
ACTION_DIAL Introduce un número sin llegar a realizar la llamada
ACTION_SEND Manda un correo o SMS
ACTION_SET_TIMER Programa un temporizados
ACTION_SET_ALARM Programa una alarma.
ACTION_IMAGE_CAPTURE Tomar una foto
ACTION_VIDEO_CAPTURE Grabar un vídeo
ACTION_GET_CONTENT Seleccionar un tipo de archivo específico
ACTION_OPEN_DOCUMENT Abrir un tipo de archivo específico
ACTION_CREATE_DOCUMENT Crear un tipo de archivo específico
ACTION_PICK Seleccionar un contacto o fichero
También puedes definir tus propias acciones. En ese caso has de indicar el paquete de tu
aplicación como prefijo. Por ejemplo: org.example.asteroides.MUESTRA_PUNTUACIONES.
Categoría: Complementa a la acción. Indica información adicional sobre el tipo de componente
que ha de ser lanzado. El número de categorías puede ampliarse arbitrariamente. No
(actualizado 11/10/23) 43
Aplicaciones para dispositivos móviles
obstante, en la clase Intent se definen una serie de categorías genéricas que podemos
utilizar.
Constante Significado
(actualizado 11/10/23) 44
Aplicaciones para dispositivos móviles
(actualizado 11/10/23) 45
Aplicaciones para dispositivos móviles
Indica que se ha intentado llamar por teléfono sin tener concedido el permiso para ello.
NOTA: En el capítulo 7 estudiarás el tema de la seguridad. Aprenderás cómo has de solicitar el
permiso adecuado si quieres que tu aplicación pueda llamar por teléfono.
9. Vamos a ver una alternativa que te permitirá resolver el problema sin tener que pedir este
permiso. En el método llamarTelefono(), reemplaza ACTION_CALL por ACTION_DIAL y vuelve
a ejecutar la aplicación. Ahora no se producirá el error, si no que se abrirá la aplicación de
llamar por teléfono con el número marcado. Es el usuario quien decidirá si es seguro llamar
a este número.
10. Verifica que el resto de los botones funcionan correctamente.
11. Trata de personalizar los valores que son enviados o visualizados en cada intención y vuelve
a ejecutar la aplicación.
12. Ejercicio Voluntario: Serías capaz de modificar el código del botón de Streetview para que
muestre el cartel que aparece en la puerta de la cafetería de la EPSG, desde la entrada de
la calle. Ha de aparecer con el zoom máximo (3) y lo más centrado que puedas tanto
horizontal como vertical.
(actualizado 11/10/23) 46
Aplicaciones para dispositivos móviles
El primer método crea una intención implícita con acción ACTION_SEND. Mandaremos un texto
plano formado por el nombre del lugar y su URL. Esta información podrá ser recogida por
cualquier aplicación que se haya registrado como enviadora de mensajes (WhatsApp, Gmail,
SMS, etc.).
El segundo método realiza una llamada telefónica al número del lugar. El tercero abre su
página Web. El cuarto obtiene la latitud y longitud del lugar. Si alguna de las dos es distinta
de cero, consideraremos que se ha introducido esta información y crearemos una URI
basada en estos valores. Si son cero, consideraremos que no se han introducido, por lo que
crearemos una URI basándonos en la dirección del lugar.
(actualizado 11/10/23) 47
Aplicaciones para dispositivos móviles
2. En la clase VistaLugarActivity hay que llamar a dos de estos métodos desde el menú. En
el método onOptionsItemSelected() añade el código subrayado:
@Override public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.accion_compartir:
usoLugar.compartir(lugar);
return true;
case R.id.accion_llegar:
usoLugar.verMapa(lugar);
return true;
…
Nota: Si no has hecho el ejercicio Casos de Uso para lugares, elimina usoLugar.
3. Ejecuta la aplicación, selecciona alguno de los lugares y utiliza la barra de acciones para verificar
estas opciones. Para la segunda opción, es importante que el terminal disponga de algún software
de mapas (como Google Maps).
4. Abre el layout vista_lugar.xml. Localiza el LinearLayout que contiene el icono y el texto con
la dirección. Añade el atributo onClick como se muestra a continuación:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:onClick="verMapa">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="logo de la dirección"
android:src="@android:drawable/ic_menu_myplaces" />
…
5. Añade también el atributo onClick en los LinearLayout que contienen el teléfono y la URL
utilizando el método adecuado.
6. Añade a VistaLugarActivity los siguientes métodos:
public void verMapa(View view) {
usoLugar.verMapa(lugar);
}
Nota: Si no has hecho el ejercicio Casos de Uso para lugares, elimina usoLugar.
7. Verifica el funcionamiento de las nuevas intenciones implícitas.
(actualizado 11/10/23) 48
Aplicaciones para dispositivos móviles
android:onClick="galeria"
Este método crea una intención indicando que queremos seleccionar contenido. El contenido
será proporcionado por el Content Provider MediaStore; además le indicamos que nos
interesan imágenes del almacenamiento externo. Típicamente se abrirá la aplicación galería
de fotos (u otra similar). Observa, que se usan dos acciones diferentes:
ACTION_OPEN_DOCUMENT solo está disponible a partir del API 19, tiene la ventaja que no
requiere que la aplicación pida permiso de lectura. Cuando el usuario selecciona un fichero
el Content Provider, dará a nuestra aplicación permiso de lectura (o incluso de escritura) pero
solo para el archivo solicitado. No se considera una acción peligrosa dado que es el usuario
quien selecciona el archivo a compartir con la aplicación. Si se ejecuta en un API anterior al
19, tendremos que usar ACTION_PICK, que sí que requiere dar permisos de lectura en la
memoria externa. Una vez concedido este permiso la aplicación podría aprovechar y leer
otros ficheros sin la intervención directa del usuario.
Nota: En el ejercicio Creación de la aplicación Mis Lugares se proponía usar como API mínima 19.
Si has mantenido este valor, puedes eliminar la sección else del código anterior. Tampoco va a ser
necesario que realices el siguiente punto.
Como necesitamos que la actividad lanzada nos indique una respuesta (la foto
seleccionada), usamos un objeto de tipo ActivityResultLauncher para lanzarla.
3. Si la versión mínima de API es anterior a 19, vamos a tener que pedir permiso para leer
ficheros de la memoria externa. En AndroidManifest.xml añade dentro de la etiqueta
<manifest …> </manifest> el siguiente código:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Esta variable nos permite lanzar una intención de la que esperamos una respuesta. Una vez
termine la actividad lanzada, se llamará al método onActivityResult(). Aquí, verificamos que
el usuario no ha cancelado la operación. Si ha cancelado se muestra un Toast. En caso
contrario, nos tiene que haber pasado en la intención de respuesta, result.getData(), una
URI con la foto seleccionada. Esta URI puede ser del tipo file://…, content://… o
(actualizado 11/10/23) 49
Aplicaciones para dispositivos móviles
http://… según qué aplicación haya resuelto esta intención. El siguiente paso consiste en
modificar el contenido de la vista que muestra la foto, binding.foto, con esta URI. Lo
hacemos en el método ponerFoto(): Nota: Mejor llamarle visualizarFoto()
5. Añade el siguiente método:
protected void ponerFoto(ImageView imageView, String uri) {
if (uri != null && !uri.isEmpty() && !uri.equals("null")) {
imageView.setImageURI(Uri.parse(uri));
} else {
imageView.setImageBitmap(null);
}
}
Este método comienza verificando que nos han pasado algún archivo válido en uri. Si es
así, lo asigna a imageView. En caso contrario, se le asigna un Bitmap igual a null, que es
equivalente a que no se represente ninguna imagen.
6. Ya puedes ejecutar la aplicación. Si añades una fotografía a un lugar, esta se visualizará.
Sin embargo, si vuelve a la lista de lugares y seleccionas el mismo lugar al que asignaste la
fotografía, ésta ya no se representa. La razón es que no hemos visualizado la foto al crear la
actividad.
7. En el método actualizarVistas() añade la siguiente línea al final:
ponerFoto(binding.foto, lugar.getFoto());
Este método crea una intención indicando que queremos capturar una imagen desde el
dispositivo. Típicamente se abrirá la aplicación cámara de fotos. A esta intención vamos a
añadirle un extra con una URI al fichero donde queremos que se almacene la fotografía. Para
crear el fichero, se utiliza createTempFile() indicando nombre, extensión y directorio. El
método currentTimeMillis() nos da el número de milisegundos transcurridos desde 1970.
(actualizado 11/10/23) 50
Aplicaciones para dispositivos móviles
Al dividir entre 1000, tenemos el número de segundos. El objetivo que se persigue es que,
al crear un nuevo fichero, su nombre nunca coincida con uno anterior. El directorio del fichero
será el utilizado para almacenar fotos privadas en la memoria externa. Estos ficheros serán
de uso exclusivo para tu aplicación. Además, si desinstalas la aplicación, este directorio será
borrado. Si quieres que los ficheros sean de acceso público utiliza
getExternalStoragePublicDirectory().
Una vez tenemos el fichero hay dos alternativas para crear la URI. Si el API de Android donde
se ejecuta la aplicación es 24 o superior, podemos crear el fichero asociado a un Content
Provider nuestro. Esta acción no requiere solicitar permiso de escritura. Si el API es anterior al
24, no se dispone de esta acción, y el fichero será creado de forma convencional en la memoria
externa. El inconveniente es que para realizar esta acción tendremos que pedir al usuario
permiso de escritura en la memoria externa. Al concedernos este permiso, también podremos
borrar o sobreescribir cualquier fichero que el usuario tenga en esta memoria. Por lo que
muchos usuarios no querrán darnos este permiso. En la última línea usamos un objeto de tipo
ActivityResultLauncher para lanzar la actividad.
4. Si la versión mínima de API es anterior a 24, vamos a tener que pedir permiso para leer
ficheros de la memoria externa. En AndroidManifest.xml añade dentro de la etiqueta
<manifest …> </manifest> el siguiente código:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
NOTA: Observa cómo hemos tenido que solicitar permiso para acceder a la memoria externa, pero
no es necesario solicitar permiso para tomar una fotografía. La razón es que realmente nuestra
aplicación no toma la fotografía directamente, si no que por medio de una intención lanzamos otra
aplicación, que sí que tiene este permiso.
5. En un punto anterior hemos utilizado un Content Provider para almacenar ficheros. Para
crearlo añade en AndroidManifest.xml dentro de la etiqueta <application …>
</application> el siguiente código:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="es.upv.jtomas.mislugares.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
6. Crea un nuevo recurso con nombre "file_paths.xml" en la carpeta res/ xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path name="my_images" path="/" />
</paths>
Has de cambiar es.upv.jtomas por un identificador unido que incluya tu nombre o empresa.
Ha de coincidir con el valor indicado en la función tomarFoto().
7. Añade la variable tomarFotoLauncher al comienzo de la clase:
ActivityResultLauncher<Intent> tomarFotoLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode()==Activity.RESULT_OK && uriUltimaFoto!=null) {
lugar.setFoto(uriUltimaFoto.toString());
ponerFoto(binding.foto, lugar.getFoto());
} else {
Toast.makeText(VistaLugarActivity.this,
"Error en captura", Toast.LENGTH_LONG).show();
}
}
});
(actualizado 11/10/23) 51
Aplicaciones para dispositivos móviles
Como se ha explicado en el ejercicio anterior, esta variable nos permite lanzar una intención, de
forma que cuando termine se llamará al método onActivityResult().
Comenzamos verificando que el usuario no ha cancelado la operación y que los objetos con
los que trabajamos siguen existiendo (NOTA: En la siguiente unidad veremos que es posible que
una actividad sea reiniciada). En este caso, se nos pasa información en la intención de
respuesta, pero sabemos que en uriUltimaFoto está almacenada la URI con el fichero
donde se ha almacenado la foto. Guardamos esta URI en el campo adecuado de lugar e
indicamos que se represente en imageView.
8. Verifica de nuevo el funcionamiento de la aplicación.
NOTA: En algunos dispositivos puede aparecer un error de memoria si la cámara está configurada
con mucha resolución. Entonces puedes probar con la cámara delantera.
9. Si te animas puedes tratar de convertir la acción de poner una foto de la galería y tomar foto,
en dos casos de uso. Cuidado que la cosa puede liarse.
2 https://developer.android.com/topic/performance/graphics/load-bitmap
(actualizado 11/10/23) 52
Aplicaciones para dispositivos móviles
(actualizado 11/10/23) 53
Aplicaciones para dispositivos móviles
(actualizado 11/10/23) 54