No es un bug, es una característica no documentada

domingo, 25 de octubre de 2015

Programación multimedia. Comunicación entre Activities

En este apartado trataremos como comunicar dos activities dentro del mismo proyecto. Para ello, lo primero es saber cómo generar una nueva actividad. Simplemente en la sección de Project haremos click derecho à New à Activity.



Bien, hecho esto, debemos saber que para llamar desde una activity a otra usaremos la clase Intent. Permite desde una actividad invocar a otra (o varias), y también pasar información a otras actividades y servicios y recibirla.

Existen dos tipos de Itents:
  • Los explícitos, que nombran directamente el componente que se necesita. Por ejemplo, especifican la clase que será utilizada para realizar una determinada tarea.
  • Los implíticitos, que sólo llaman a la actividad, sin indicar que método debe recogerlo.

Para el tipo explícito, en la Activity madre el código que usaremos para abrir una nueva será

public void onClick(View arg0){
     Intent i = new Intent(this, SegundaActividad.class);
     startActivity(i);
}

Método explícito

Vamos a generar una aplicación que, pulsando sobre un botón en la primera activity, se abra la segunda. Algo sencillo.

El Layout de la primera activity es el siguiente


Y el Layout de la segunda actividad, llamada inteligentemente Segunda, es este:


Ahora, como se trata de un método explícito sin más, solo necesito generar una función que agregaré al botón de la primera actividad.
El código es el siguiente:

public void onClick(View arg0){
     Intent i = new Intent(this, Segunda.class);
     startActivity(i);
}

Y al ejecutar el programa vemos el antes y el después (que en imagen estática es pelín complicado de mostrarlo, pero ahí lo dejo por si acaso).



Este programa es muy simple, así que vamos a intentar hacer algo que quede visualmente más atractivo.

Tercera aplicación. Calculadora de Indice de Masa Corporal

Vamos a desarrollar una aplicación que calcule el IMC (sabiendo que IMC = kg/m2) utilizando dos interfaces gráficas distintas.

La primera actividad usa como tema Theme.AppCompat


Y la segunda será Base.Theme.AppCompat.Dialog.FixedSize.


Advertencia: Aunque el enunciado no lo indique se da por supuesto que todo buen programador controla los posibles errores (datos no introducidos, divisiones por cero, edades negativas…).

Bueno, vayamos por pasos. Este es mi layout principal, que he llamado IMC.


Y este el secundario, llamado IMCAyuda.
Dentro de IMCAyuda yendo a la pestaña texto modifico el campo android:theme con el valor @style/Base.Theme.AppCompat.Dialog.FixedSize.


Y ahora vayamos al código. Primero, IMC.java. Como siempre, entre bordes el código creado por mí.

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import java.text.DecimalFormat;

public class IMC extends AppCompatActivity {

    private Button btnCalcula;
    private Button btnAyuda;
    private EditText txtKilos;
    private EditText txtAltura;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_imc);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_imc, menu);
        return true;
    }

    public void calcularIMC(View arg0){

        btnCalcula = (Button)findViewById(R.id.btnCalcula);
        btnAyuda = (Button)findViewById(R.id.btnAyuda);
        txtKilos = (EditText)findViewById(R.id.txtKilos);
        txtAltura = (EditText)findViewById(R.id.txtAltura);

        if (txtKilos.getText().toString().equals("") || txtAltura.getText().toString().equals("")){
            Toast.makeText(this, "Debe rellenar los campos de peso y altura", Toast.LENGTH_SHORT).show();
        }
        else{
            double peso = Double.parseDouble(txtKilos.getText().toString());
            double altura = Double.parseDouble(txtAltura.getText().toString());

            if (peso <= 0  || altura <= 0){
                Toast.makeText(this, "El valor 0 no es admisible. Usted ocupa más volumen que eso", Toast.LENGTH_SHORT).show();
            }
            else{
                altura = altura / 100; // Debo calcular en metros, no centímetros
                double imc = peso / (altura * altura);
                DecimalFormat f = new DecimalFormat("#.##");
                String resultado = "Indice de masa corporal: " + f.format(imc);

                if(imc < 16.00){
                    resultado = resultado + ". \nDelgadez severa";
                    Toast.makeText(this, resultado, Toast.LENGTH_SHORT).show();
                }

                if(imc >= 16.00 && imc < 17.00){
                    resultado = resultado + ". \nDelgadez moderada";
                    Toast.makeText(this, resultado, Toast.LENGTH_SHORT).show();
                }

                if(imc >= 17.00 && imc < 18.50){
                    resultado = resultado + ". \nDelgadez leve";
                    Toast.makeText(this, resultado, Toast.LENGTH_SHORT).show();
                }

                if(imc >= 18.00 && imc < 25.00){
                    resultado = resultado + ". \nIMC normal";
                    Toast.makeText(this, resultado, Toast.LENGTH_SHORT).show();
                }

                if(imc >= 25.00 && imc < 30.00){
                    resultado = resultado + ". \nTiene sobrepeso";
                    Toast.makeText(this, resultado, Toast.LENGTH_SHORT).show();
                }

                if(imc >= 30.00 && imc < 35.00){
                    resultado = resultado + ". \nObesidad leve";
                    Toast.makeText(this, resultado, Toast.LENGTH_SHORT).show();
                }

                if(imc >= 35.00 && imc < 40.00){
                    resultado = resultado + ". \nObesidad media";
                    Toast.makeText(this, resultado, Toast.LENGTH_SHORT).show();
                }

                if(imc >= 40.00){
                    resultado = resultado + ". \nObesidad mórbida";
                    Toast.makeText(this, resultado, Toast.LENGTH_SHORT).show();
                }

            }
        }
    }

    public void onClick(View v){
        Intent i = new Intent(this, IMCAyuda.class);
        startActivity(i);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Como se puede ver, tengo dos funciones, calcularIMC(View arg0) que lo que hace es coger los datos de los EditText y calcular el IMC correspondiente, controlando los errores de campos vacíos o con números negativos, y otra función onClick(View v) que simplemente hace la llamada a la segunda Activity.

Veamos ahora IMCAyuda.java

import android.support.v4.view.MotionEventCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;

public class IMCAyuda extends AppCompatActivity {

    public static final String DEBUG_TAG = "IMCAyuda";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_imcayuda);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_imcayuda, menu);
        return true;
    }

    public boolean onTouchEvent(MotionEvent event){
        int action = MotionEventCompat.getActionMasked(event);

        switch(action){
            case(MotionEvent.ACTION_DOWN):
                this.finish();
                return true;
            case(MotionEvent.ACTION_UP):
                this.finish();
                return true;
            case(MotionEvent.ACTION_MOVE):
                this.finish();
                return true;
            default:
                return super.onTouchEvent(event);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Con el evento OnTouchEvent(MotionEvent event) hago que cualquier acción de presionar, mover el dedo y levantarlo quede capturado y cierre el segundo activity. Realmente con ACTION_DOWN ya valdría, pero por cerciorarme que no sea.

Al ejecutar el programa podemos ver dos pantallas, una en la que calcula el IMC y la de ayuda, tal que así:


Método implícito

O como pasar datos de una actividad a otra.

Deberemos enviar un “extra” cuando generemos el Intent, y capturarlo en la segunda actividad con la clase Bundle. Esta clase recibe del Intent un elemento identificado con dos valores, el primero la clave del dato y el segundo el valor del dato, que puede usarse con los elementos de su activity haciendo la llamada.

Pongamos que tenemos Activity A y B.
El código del activity A para enviar un dato sería

public void onClick(View v){
     Intent i = new Intent(this, SegundaActividad.class);
     // Envía un único dato
     i.putExtra("nombreDelDatoAEnviar", txtInfo.getText().toString());
     startActivity();
}

Con este código estamos mandando la información contenida en el elemento txtInfo en una cadena de texto (porque lo he querido así, Bundle admite todo tipo de datos). Y para recibirla en la activity B, por ejemplo en un EditText haremos lo siguiente.

EditText txtPrueba = (EditText)findViewById(R.id.txtPrueba);
Bundle bundle = getIntent().getExtras();
txtPrueba.setText(bundle.getString("nombreDelDatoAEnviar"));

Lo que hacemos es crear un Bundle y almacenar ahí la información pasada del Intent. Luego referenciamos con el nombre de la clave de dato en el elemento que queremos cargar la información.

También podemos pasar más de un valor, en cuyo caso en la actividad que envía lo haríamos de la siguiente manera:

Bundle bundle = new Bundle();
Bundle.putString("nombre", txtNombre.getNombre().toString);
Bundle.putInt("edad", txtEdad.getEdad());
intent.putExtras(bundle);

Es decir, generamos un Bundle y almacenamos ahí la información que queramos. Luego insertamos el propio Bundle en un Intent y lo pasamos a la segunda Activity. Ahí lo manejaremos tal cual el primer ejemplo, pues siempre hay que referenciar por la clave del dato.

Cuarta aplicación. “Navegador web” con WebView

WebView es usado para cargar páginas web dentro de una aplicación Android. No es un navegador web como tal pues no tiene cargada la barra de direcciones, pero sí permite mostrar páginas (y creo que con paciencia se podría conseguir un estilismo similar a un navegador web. Tal vez no todas sus funcionalidades, pero podría intentarse).

El ejercicio será crear una activity con un EditText en el que se escribirá una dirección web, y pulsando un botón que se mande a una segunda actividad donde se cargará dicha dirección en el WebView.

Hay que tener en cuenta que habrá que dar permisos en el Manifest para que nuestra aplicación tenga acceso a Internet.

Y un último dato. El uso de WebView está desansejado, a partir de Enero de 2015, para las versiones de Android inferiores a KitKat (API 17).

Bien, dicho esto, estos son los diseños de mis Layouts.
El principal, Navegador,simplemente recoge la URL en ese EditText y se envía al pulsar el botón.


El segundo Layout, Web, que contiene el WebView, queda tal que así


Y su configuración XML para conseguir ocupar toda la pantalla correctamente es la siguiente:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="0dp"
    android:paddingRight="0dp"
    android:paddingTop="0dp"
    android:paddingBottom="0dp"
    tools:context="es.claver.inazio.navegadorweb.Web"
    android:background="#0288d1">

    <WebView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/pagWeb"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />
</RelativeLayout>

En el Manifiest escribimos los permisos justo después de <manifiest>:


Y ahora a por el código.
En Navegador.java la función programada que asigno al botón es:

public void enviarPagina(View v){
     btnGo = (Button)findViewById(R.id.btnGo);
     txtURL = (EditText)findViewById(R.id.txtURL);
     Intent i = new Intent(this, Web.class);

     // Envío el dato
     i.putExtra("pagina", txtURL.getText().toString());
     startActivity(i);
}

Y en Web.java creo la función cargarWeb() y la añado al onCreate().

protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_web);
     cargarWeb();
}

public void cargarWeb(){
     pagWeb = (WebView)findViewById(R.id.pagWeb);
     pagWeb.setWebViewClient(new WebViewClient());
     bundle = getIntent().getExtras();
     pagWeb.loadUrl("http://" + bundle.getString("pagina"));

     // Habilitar JavaScript
     WebSettings webSettings = pagWeb.getSettings();
     webSettings.setJavaScriptEnabled(true);
}

En cargarWeb() con el método loadUrl() hago una concatenación del protocolo “http://” más la página web que ha escrito el usuario.
Y en las siguientes líneas habilito el JavaScript del WebView por si acaso se requiere para la página web a visitar.

Al lanzar la aplicación el resultado es el siguiente:


0 comentarios:

Publicar un comentario