Práctica 3. Android. Tutorial appfotovoz José Antonio Larrubia García José Miguel Navarro Moreno Índice: 1.- Introducción. 2.- Descripción de la solución y problemas encontrados. 3.- Manual de uso. 4.- Referencias.
1.- Introducción. La aplicación consiste en una brújula, a la que se le indica mediante voz un punto cardinal al que tiene que apuntar. 2.- Descripción de la solución y problemas encontrados. Partiendo de un proyecto Blank Activity de Android Studio, lo primero que hacemos es usar los sensores Acelerometro y Magnetic Field para que señale al norte: Para ello la clase debe implementar la clase SensorEventListener y definimos las variables necesarias: public class MainActivity extends AppCompatActivity implements SensorEventListener { private ImageView imgbrujula; private ImageView flecha; private TextView txtangle; private TextView textorec; // guarda el angulo (grado) actual del compass private float currentdegree = 0f; // El sensor manager del dispositivo private SensorManager msensormanager; // Los dos sensores que son necesarios private Sensor accelerometer; private Sensor magnetometer; // Los angulos del movimiento respecto al norte float degree; // Guarda el valor del azimut float azimut; // Guarda los valores que cambián con las variaciones del sensor TYPE_ACCELEROMETER float[] mgravity; // Guarda los valores que cambián con las variaciones del sensor TYPE_MAGNETIC_FIELD Ahora inicializamos los sensores en el método oncreate(): protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); Toolbar toolbar = (Toolbar) findviewbyid(r.id.toolbar); setsupportactionbar(toolbar); //imagen que estara orientada hacia el norte imgbrujula = (ImageView) findviewbyid(r.id.imagebrujula); txtangle= (TextView) findviewbyid(r.id.textview); // Se inicializa los sensores del dispositivo msensormanager = (SensorManager) getsystemservice(sensor_service); accelerometer =msensormanager.getdefaultsensor (Sensor.TYPE_ACCELEROMETER); magnetometer = msensormanager.getdefaultsensor (Sensor.TYPE_MAGNETIC_FIELD); mgravity = null; mgeomagnetic = null;
En los métodos onpause() y onresumen() se paran los sensores y se reanudad de nuevo para no estén activos cuando la aplicación este en segundo plano: protected void onresume() { super.onresume(); // Se registra un listener para los sensores del accelerometer y el magnetometer msensormanager.registerlistener (this, accelerometer, SensorManager.SENSOR_DELAY_UI); msensormanager.registerlistener(this, magnetometer, SensorManager.SENSOR_DELAY_UI); protected void onpause() { super.onpause(); // Se detiene el listener para no malgastar la bateria msensormanager.unregisterlistener(this); En el método onsensorchanged se obtiene la orientación del dispositivo, se crea la animación de rotación y se aplica al imageview correspondiente que puede contener una imagen con una flecha o una brújula cuya dirección sea hacia arriba: public void onsensorchanged(sensorevent event) { //Referencia: http://agamboadev.esy.es/como-crear-un-brujula-en-android/ // Se comprueba que tipo de sensor está activo en cada momento switch (event.sensor.gettype()) { case Sensor.TYPE_ACCELEROMETER: mgravity = event.values; break; case Sensor.TYPE_MAGNETIC_FIELD: mgeomagnetic = event.values; break; //Si los sensores estan activos, se obtiene el valor de la orientación del dispositivo en grados. if ((mgravity!= null) && (mgeomagnetic!= null)) { float RotationMatrix[] = new float[16]; boolean success= SensorManager.getRotationMatrix( RotationMatrix, null, mgravity, mgeomagnetic); if (success) { float orientation[] = new float[3]; SensorManager.getOrientation(RotationMatrix, orientation); azimut = orientation[0] * (180 / (float) Math.PI); degree = azimut; //se muestra el angulo del norte respecto a la dirección del dispositivo. txtangle.settext("ángulo: " + Float.toString(degree) + " grados"); // se crea la animacion de la rottacion (se revierte el giro en grados, negativo) RotateAnimation ra = new RotateAnimation( currentdegree, degree, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
// el tiempo durante el cual la animación se llevará a cabo ra.setduration(1000); // establecer la animación después del final de la estado de reserva ra.setfillafter(true); // Inicio de la animacion imgbrujula.startanimation(ra); currentdegree = -degree; Con esto ya tenemos el una brújula que apunta hacia el norte, ahora solo nos queda usarel detector de voz para indicarle una dirección: Primero añadimos las variables necesarias: //------------------------------------------- //Referencia: https://github.com/zoraidacallejas/sandra/tree/master/apps/asrwithintent //Default values for the language model and maximum number of recognition results // They are shown in the GUI when the app starts, and they are used when the user selection is not valid private final static int DEFAULT_NUMBER_RESULTS = 10; private final static String DEFAULT_LANG_MODEL = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM; private int numberrecoresults = DEFAULT_NUMBER_RESULTS; private String languagemodel = DEFAULT_LANG_MODEL; private static int ASR_CODE = 123; //----------------------------------------------------------------------- //String en el que se va a almacenar la dirección obtenida por la captación de voz private String direccion=null; private int margen_error=30; //Para el ángulo de la dirección que se le indica. float angulo_flecha; float angulo_ant=0f; Después, en el método oncreate() se añade lo siguiente para el botón que iniciara la captación de voz: //botón que inicia el detector de voz cuando lo pulsas FloatingActionButton fab = (FloatingActionButton) findviewbyid(r.id.fab); fab.setonclicklistener(new View.OnClickListener() { @Override public void onclick(view v) { //----------------------------------------------------------- //Referencia: https://github.com/zoraidacallejas/sandra/tree/master/apps/asrwithintent //Speech recognition does not currently work on simulated //devices, it the user is attempting to run the app in a //simulated device they will get a Toast if("generic".equals(build.brand.tolowercase())){ Toast toast = Toast.makeText(getApplicationContext(), "ASR is not supported on virtual devices", Toast.LENGTH_SHORT); toast.show(); Log.d(LOGTAG, "ASR attempt on virtual device"); else{ numberrecoresults = 10;
//Read speech recognition parameters from GUI languagemodel = DEFAULT_LANG_MODEL; Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); // Specify language model intent.putextra(recognizerintent.extra_language_model, languagemodel); // Specify how many results to receive intent.putextra(recognizerintent.extra_max_results, numberrecoresults); // Start listening startactivityforresult(intent, ASR_CODE); //----------------------------------------------------------- ); //imagen que indica la dirección captada por voz flecha=(imageview) findviewbyid(r.id.imageflecha); Ahora se crea el método onactivityresult() en el que se obtendrán los resultados de la captación de voz: /** * Se recoge los datos del evento de captación de voz */ @SuppressLint("InlinedApi") @Override protected void onactivityresult(int requestcode, int resultcode, Intent data) { //Referencia: https://github.com/zoraidacallejas/sandra/tree/master/apps/asrwithintent if (requestcode == ASR_CODE) { if (resultcode == RESULT_OK) { if(data!=null) { //Retrieves the N-best list and the confidences from the //ASR result ArrayList<String> nbestlist = data.getstringarraylistextra(recognizerintent.extra_results); //Para obtener la direccion y el margen de error obtenerdireccion(nbestlist); textorec= (TextView) findviewbyid(r.id.textrec); textorec.settext("dirección: "+direccion + " " + margen_error); else { //Reports error in recognition error in log Log.e(LOGTAG, "Recognition was not successful"); Solo falta el método obtenerdireccion() usado en el método onactivityresult():
/** * Método para obtener la dirección y el margen de error de los resultados * de la captación de voz. * @param a Lista con les resultados del reconocedor de voz */ private void obtenerdireccion(arraylist<string> a){ //Lista con los posibles puntos cardinales ArrayList<String> puntos_car=new ArrayList<String>(); puntos_car.add("norte"); puntos_car.add("sur"); puntos_car.add("este"); puntos_car.add("oeste"); boolean coincide=false; direccion=null; // se comprueba de la lista de string de la detección de voz for(int i=0;i<puntos_car.size() &&!coincide;i++){ for(int j=0;j<a.size() &&!coincide;j++){ if(a.get(j).length() >= puntos_car.get(i).length()) { //Si coincide con un punto cardinal se guarda en dirección if (puntos_car.get(i).equals((a.get(j).substring( 0, puntos_car.get(i).length())).tolowercase())) { coincide = true; direccion = puntos_car.get(i); //Ahora hay que obtener el margen de error String numero; boolean es_num=false; //se obtiene el margen de error if(direccion!=null) { //limpiamos el string obtenido por el detector de voz para que //solo contenga el número eliminado todas las letras Pattern pat=pattern.compile("[a-za-záéíóú]*(\\s)*[a-za-záéíóú]*"); Matcher mat; for (int i = 0; i < a.size() &&!es_num; i++) { mat= pat.matcher(a.get(i)); //quitamos las letras del string numero=mat.replaceall(""); //si en el string se obtiene un número ese será el margen //de error. if(numero.length()>0){ margen_error=integer.parseint(numero); es_num=true; Ya reconocemos por voz un punto cardinal seguido de un número que sera el margen de error. Lo que vamos a hacer ahora es mostrar la dirección con una flecha, para ello añadimos en el método onsensorchanged una animación para la imagen correspondiente y hacer que cambie de color si el dispositivo apunta hacia esa dirección:
//Si se le ha pasado una dirección por voz //Se calcula el ángulo adecuado en función del ángulo en el que se //encuentra el norte. if(direccion!=null){ if(direccion.equals("sur")){ if(degree>0) angulo_flecha=degree-180; else angulo_flecha=degree+180; else if(direccion.equals("norte")){ angulo_flecha=degree; else if(direccion.equals("este")){ if(degree-90<-180) angulo_flecha=degree-90+360; else angulo_flecha=degree-90; else if(direccion.equals("oeste")){ if(degree+90>180) angulo_flecha=degree+90-360; else angulo_flecha=degree+90; //Se crea la animación RotateAnimation ra2 = new RotateAnimation( angulo_ant, angulo_flecha, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); // el tiempo durante el cual la animación se llevará a cabo ra2.setduration(1000); // establecer la animación después del final del estado de reserva ra2.setfillafter(true); // Inicio de la animacion flecha.startanimation(ra2); angulo_ant=-angulo_flecha; //Si el dispositivo apunta hacia la dirección especificada se cambia //el color de la flecha if(angulo_flecha<margen_error && angulo_flecha>-margen_error){ flecha.setimageresource(r.drawable.flechag); else flecha.setimageresource(r.drawable.flechar); Pues con esto ya tenemos la aplicación appfotovoz, el código completo se puede encontrar en: https://github.com/jmnm/appfotovoz 3.- Manual de uso. El uso de la aplicación es muy sencillo, una vez iniciada solo hay que pulsar el botón de la esquina inferior derecha he indicarle un punto cardinal seguido de un número, entonces la flecha roja señalara la dirección indicada, y esta flecha se pondrá verde cuando el dispositivo apunte hacia esa dirección.
4.- Referencias. [1] http://agamboadev.esy.es/como-crear-un-brujula-en-android/ [2] https://github.com/zoraidacallejas/sandra/tree/master/apps/asrwithintent