sábado, 23 de junio de 2012

Crear tu propia Galería de Imágenes

Android ofrece su propia galería de imágenes por defecto. En este post se va a mostrar una alternativa a la imagen inferior. Ésta, como podemos observar, se compone de un visor de las imágenes y debajo la imagen ampliada. Un ejemplo podría ser imagen siguiente:


Si este método no os gusta, podéis usar el que se presenta en este post. Si queréis que mediante un "gesto en pantalla", cómo podría ser arrastrar el dedo por ella, pueda cambiar la imagen añadid el siguiente código a vuestro proyecto. A continuación un completo ejemplo:

Contiene la image que se cambiara en la vista.
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/vista_fotos"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF"
    android:orientation="vertical" >
    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:scaleType="centerInside" />
</RelativeLayout>

Contiene el código que detecta el movimiento del dedo del usuario. Y posteriormente, ejecuta el cambio de imagen. Esta tiene que implementar un "OnClickListener" porqué así detectamos el contacto del usuario con la vista. En otro caso, no se activaría el "gestureListener".
src/SwypeImagesActivity.java
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.RelativeLayout;

public class SwypeImagesActivity extends Activity implements OnClickListener {

    // Custom settings
    private static final int SWIPE_MIN_DISTANCE = 120;
    private static final int SWIPE_MAX_OFF_PATH = 250;
    private static final int SWIPE_THRESHOLD_VELOCITY = 30;
    private GestureDetector mGestureDetector;
    private View.OnTouchListener mGestureListener;
    private ImageView mImageView;
    private Integer[] mImagesList = { R.drawable.image1, R.drawable.image2,
            R.drawable.image3, R.drawable.image4};
    private int mPhoto;
    private RelativeLayout mView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPhoto = 0;
        setContentView(R.layout.main);
        mView = (RelativeLayout) findViewById(R.id.vista_fotos);
        mImageView = (ImageView) findViewById(R.id.image);

        // initialize with the first image
        mImageView.setImageResource(mImagesList[mPhoto]);

        // Gesture detection
        mGestureDetector = new GestureDetector(new MyGestureDetector());
        mGestureListener = new View.OnTouchListener() {
            public boolean onTouch(View v, MotionEvent event) {
                return mGestureDetector.onTouchEvent(event);
            }
        };
        // prevent the view to be touched
        mView.setOnClickListener(this);
        mView.setOnTouchListener(mGestureListener);
    }

    public class MyGestureDetector extends SimpleOnGestureListener {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                float velocityY) {
            try {
                if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
                    return false;
                if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE
                        && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    mPhoto = (mPhoto + 1) % mImagesList.length;
                    mImageView.setImageResource(mImagesList[mPhoto]);
                } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE
                        && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    mPhoto = (mPhoto - 1) % mImagesList.length;
                    if (mPhoto < 0) {
                        mPhoto = 0;
                    }
                    mImageView.setImageResource(mImagesList[mPhoto]);
                }
            } catch (Exception e) {
                Log.e("SwypeImagesActivity", "error on gesture detector");
            }
            return false;
        }
    }
    @Override
    public void onClick(View v) {
        // Necessary because the view must have on touch listener
    }
}

It works!

Roger Sala,

Personalizar CheckBox

Dependiendo del tipo de aplicaciones que estamos desarrollando podems tener unas necesidades u otras. Una de ellas, y que vamos a ver en este post puede ser: customizar los checkbox que Android nos ofrece. A continuació presento el código para realizarlo:

Esta clase contiene los textos que se van a mostrar en el checkbox.
res/xml/filters_checkbox.xml
 <filterCheckBox>
    <item
        description=""
        title="CheckBox1">
        <item
            description=""
            title="CheckBox2">
            <item
                description=""
                title="CheckBox3">
                <item
                    description=""
                    title="CheckBox4">
                </item>
            </item>
        </item>
    </item>
</filterCheckBox>

Esta clase contiene el estilo de los checkboxes. Para ello modificamos la imagen de cuando esta seleccionado o no. (El drawable que se añade son imagenes que estan en su res/drawable/).
res/drawable/style_checkboxes.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/checkbox_checked" android:state_checked="true"/>
    <item android:drawable="@drawable/checkbox_unchecked" android:state_checked="false"/>
</selector>

Esta clase contiene el estilo de cada checkbox. Es aqui donde le aplicamos el estilo anterior.
res/layout/filters_multiple_choice_list.xml
<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/text1"
    android:background="#FFFFFF"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:checkMark="@drawable/style_checkboxes"
    android:enabled="true"
    android:checked="true"
    android:gravity="center_vertical"
    android:paddingLeft="6dip"
    android:paddingRight="6dip"
    android:textAppearance="?android:attr/textAppearanceLarge"
    android:textColor="#FF0000" />

Esta clase es al vista principal. Contiene la lista de checkboxes.
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <ListView
        android:id="@+id/listview_filters_checkbox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:cacheColorHint="#000000"
        android:focusable="false"
        android:focusableInTouchMode="false"
        android:scrollbars="none" >
    </ListView>
</RelativeLayout>

Esta clase une a todas la anteriores. Es donde se muestran los datos.
src/CustomCheckBoxes.java
import java.io.IOException;
import java.util.ArrayList;
import org.xmlpull.v1.XmlPullParserException;
import android.app.Activity;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class CustomCheckBoxes extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // declare variables
        ListView checkBoxListView;
        String[] checkBoxNameList;
        ArrayList<String> checkBoxList;
        // get data
        checkBoxListView = (ListView) findViewById(R.id.listview_filters_checkbox);
        checkBoxList = PrepareListFromXml();
        checkBoxNameList = (String[]) checkBoxList.toArray(new String[0]);
        // set data to list
        checkBoxListView.setAdapter(new ArrayAdapter<String>(
                CustomCheckBoxes.this, R.layout.filters_multiple_choice_list,
                checkBoxNameList));
        checkBoxListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
    }

    private ArrayList<String> PrepareListFromXml() {
        ArrayList<String> preferenceItems = new ArrayList<String>();
        XmlResourceParser preferencelistXml;
        preferencelistXml = getResources().getXml(R.xml.filters_checkbox);
        int eventType = -1;
        while (eventType != XmlResourceParser.END_DOCUMENT) {
            if (eventType == XmlResourceParser.START_TAG) {

                String strNode = preferencelistXml.getName();
                if (strNode.equals("item")) {
                    preferenceItems.add(preferencelistXml.getAttributeValue(
                            null, "title"));
                }
            }
            try {
                eventType = preferencelistXml.next();
            } catch (XmlPullParserException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return preferenceItems;
    }
}

Si ejecutamos el proyecto se nos mostraría la siguiente imagen:

It works!

Roger Sala,

sábado, 16 de junio de 2012

Realizar una captura de pantalla

En este post vamos a explicar una forma fácil y rápida de añadir esta funcionalidad a nuestra aplicación. En el código que se muestre se realiza la captura de toda la pantalla. No obstante, si se desea se puede realizar de una vista en concreto ya sea un layout contenido por el layout general de la vista, una imageView, TableView etc...cualquier elemento del xml (layout/xml). El código es el siguiente:

AndroidManifest.xml (Añadir el siguiente permiso)
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_to_capture"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ImageView
        android:id="@+id/my_image_capture"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:src="@drawable/ic_launcher" />

    <Button
        android:id="@+id/button_screen_capture"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/my_image_capture"
        android:text="capture Screen" />

</RelativeLayout>

src/ScreenCaptureActivity.java
public class ScreenCaptureActivity extends Activity {

    private String SCREEN_CAPTURE_PATH = Environment
            .getExternalStorageDirectory() + File.separator + "ScreenCaptureFolder";

    private RelativeLayout layoutToCapture;
    private Button buttonScreenCapture;
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        buttonScreenCapture = (Button) findViewById(R.id.button_screen_capture);
        layoutToCapture = (RelativeLayout) findViewById(R.id.layout_to_capture);

        buttonScreenCapture.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                buttonScreenCapture.setVisibility(View.GONE);
                captureScreen();
                buttonScreenCapture.setVisibility(View.VISIBLE);
            }
        });
    }

    private void captureScreen() {
       
        layoutToCapture.setDrawingCacheEnabled(true);
        layoutToCapture.buildDrawingCache();
        Bitmap croppedBitmap = Bitmap.createBitmap(layoutToCapture
                .getDrawingCache());
        savePhotoToSDCard(croppedBitmap, false);
        layoutToCapture.setDrawingCacheEnabled(false);
    }

    protected boolean savePhotoToSDCard(Bitmap bitmapToSave, boolean needNumFoto) {
        try {
            File directory;
            if (isSDCARDMounted()) {

                ByteArrayOutputStream bytes = new ByteArrayOutputStream();
                bitmapToSave.compress(Bitmap.CompressFormat.PNG, 90, bytes);

                directory = new File(SCREEN_CAPTURE_PATH);
                directory.mkdirs();

                directory.createNewFile();

                FileOutputStream fo = null;
                fo = new FileOutputStream(directory + "myScreenCapture.png");

                fo.write(bytes.toByteArray());

                updateGallery();

                Toast.makeText(getApplicationContext(), "Photo saved ok",
                        Toast.LENGTH_LONG).show();

                return true;
            } else {
                Toast.makeText(getApplicationContext(), "no SDCARD found",
                        Toast.LENGTH_LONG).show();
                return false;
            }
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(), "error processing photo",
                    Toast.LENGTH_LONG).show();
            return false;
        }
    }

    private boolean isSDCARDMounted() {
        String status = Environment.getExternalStorageState();
        if (status.equals(Environment.MEDIA_MOUNTED))
            return true;
        return false;
    }

    private void updateGallery() {
        sendBroadcast(new Intent(
                Intent.ACTION_MEDIA_MOUNTED,
                Uri.parse("file://" + Environment.getExternalStorageDirectory())));
    }
}

It works!

Roger Sala,

Refrescar la galería de imágenes

Si estás realizando una aplicación que guarda imágenes en la galería y quieres que el usuario al acceder a ella vea las imágenes sin necesidad de apagar/sincronizar de nuevo el teléfono, ésta es la función que estás buscando:

private void updateGallery() {
        sendBroadcast(new Intent(
                Intent.ACTION_MEDIA_MOUNTED,
                Uri.parse("file://" + Environment.getExternalStorageDirectory())));
}

It works!

Roger Sala,

Comprobar si la targeta SD está disponible

Algunas aplicaciones es necesario guardar datos en la tarjeta SD. Nunca se puede suponer que estará montada, aunque en la mayoría de las veces es así. Para ello antes de escribir en la SD hay que comprobar que es posible. Primero de todo añadimos los permisos  necesarios en nuestro AndroidManifest.xml:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Luego podemos usar la siguiente función:

private boolean isSDCARDMounted() {
        String status = Environment.getExternalStorageState();
        if (status.equals(Environment.MEDIA_MOUNTED))
            return true;
        return false;
}

It works!

Roger Sala,

Personaliza tus listas (ListView)

En la mayoría de las aplicaciones se requiere de crear listas personalizadas. Android ofrece diferentes layouts que podemos usar, pero cuando estos no son suficientes para nuestro gusto tenemos que implementar nuestro propio layout y BaseAdapter. En este post vamos a ver un ejemplo de ello:

Contiene la lista que se muestra al usuario. Vista principal de la Activity.
res/layout/main.xml
 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <ListView
        android:id="@+id/listView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

Contiene el formato que tendrá cada elemento de la lista. Usado la clase CustomAdapter.
res/layout/custom_list.xml
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >
    <LinearLayout
        android:id="@+id/linearLayout_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dip"
        android:orientation="horizontal"
        android:paddingTop="7dip" >
        <TextView
            android:id="@+id/textView_date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >
        </TextView>
    </LinearLayout>
    <TextView
        android:id="@+id/textView_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/linearLayout_1"
        android:layout_marginLeft="5dip"
        android:layout_marginTop="5dip"
        android:textSize="15sp"
        android:textStyle="bold" >
    </TextView>
    <TextView
        android:id="@+id/textView_description"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView_title"
        android:layout_marginLeft="12dip"
        android:layout_marginTop="5dip"
        android:textSize="12sp"
        android:textStyle="italic" >
    </TextView>
</RelativeLayout>

Esta clase sirve para rellenar los datos de cada elemento de la lista.
src/CustomAdapter.java
public class CustomAdapter extends BaseAdapter {

    private Activity mActivityAct;
    private LayoutInflater mInflater;
    private ArrayList<Notice> mLItems;

    public CustomAdapter(Activity a, ArrayList<Notice> it) {
        mActivityAct = a;
        mLItems = it;
        mInflater = (LayoutInflater) mActivityAct
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    public static class VistaH {
        public TextView date;
        public TextView title;
        public TextView description;
    }

    @Override
    public int getCount() {
        return mLItems.size();
    }

    @Override
    public Object getItem(int position) {
        return position;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View vi = convertView;
        VistaH vh = null;

        if (vi == null) {
            vi = mInflater.inflate(R.layout.custom_list, null);
            vh = new VistaH();
            vh.date = (TextView) vi.findViewById(R.id.textView_date);
            vh.title = (TextView) vi.findViewById(R.id.textView_title);
            vh.description = (TextView) vi
                    .findViewById(R.id.textView_description);
            vi.setTag(vh);
        }

        vh = (VistaH) vi.getTag();

        Notice notice = mLItems.get(position);
        vh.date.setText(notice.getDate());
        vh.title.setText(notice.getTitle());
        vh.description.setText(notice.getDescription());

        return vi;
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) {
        if (observer != null) {
            super.unregisterDataSetObserver(observer);
        }
    }
}

Vista principal. Contiene la lista. Modifica el adapter del listView por el CustomAdapter. Le pasamos el contexto y los datos (en este caso Notice (elemento del modelo)).
res/MyActivity.java
public class MyActivity extends Activity {

    private ListView mListView;
    private ArrayList<Notice> mList = new ArrayList<Notice>();
    private Activity mActivity;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mListView = (ListView) findViewById(R.id.listView);
        mActivity = this;

        getDataFromWebService();

        CustomAdapter actualitatAdapter = new CustomAdapter(this, mList);
        mListView.setAdapter(actualitatAdapter);
        mListView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> av, View view, int index,
                    long arg3) {
                Toast.makeText(mActivity,
                        "My title is: " + mList.get(index).getTitle(),
                        Toast.LENGTH_LONG).show();
            }
        });

    }

    private void getDataFromWebService() {

        // p.e: get Notices from rss
        Notice n = new Notice("16/06/2012", "Notice 1",
                "This is the description from notice 1");
        mList.add(n);
        n = new Notice("14/06/2012", "Notice 2",
                "This is the description from notice 2");
        mList.add(n);
        n = new Notice("13/06/2012", "Notice 3",
                "This is the description from notice 3");
        mList.add(n);
        n = new Notice("13/06/2012", "Notice 4",
                "This is the description from notice 4");
        mList.add(n);
        n = new Notice("12/06/2012", "Notice 5",
                "This is the description from notice 5");
        mList.add(n);
        n = new Notice("11/06/2012", "Notice 6",
                "This is the description from notice 6");
        mList.add(n);
        n = new Notice("09/06/2012", "Notice 7",
                "This is the description from notice 7");
        mList.add(n);
        n = new Notice("09/06/2012", "Notice 8",
                "This is the description from notice 8");
        mList.add(n);
        n = new Notice("03/06/2012", "Notice 9",
                "This is the description from notice 9");
        mList.add(n);

    }
}


Elemento del modelo. Contiene los atributos que se muestran en la lista.
res/Notice.java
public class Notice {
    private String date;
    private String title;
    private String description;

    public Notice(String date, String title, String description) {
        this.date = date;
        this.title = title;
        this.description = description;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

Si ejecutamos el proyecto se nos mostraría la siguiente imagen:

 

It works!

Roger Sala,

martes, 12 de junio de 2012

Galería de Imágenes

En algunas de mis apps (no publicadas en el market todavía) he tenido que obtener todas las imágenes que hay guardas en la galería del dispositivo. Así que en este post les voy a compartir el código que he usado para ello.

private void getImagesFromGallery() {
        // propiedades que queremos hacer la query
        String[] projection = new String[] { MediaStore.Images.Media._ID,
                MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
                MediaStore.Images.Media.DATA };

        //Donde saco la info
        Uri images = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;

        // Obtenemos la info
        Cursor cur = managedQuery(images, projection, // Which columns to return
                "",
                null,
                ""
        );

        if (cur.moveToFirst()) {
            String bucket;
            String path;
            int bucketColumn = cur
          .getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME);
            int dataColumn = cur.getColumnIndex(MediaStore.Images.Media.DATA);
            do {
                bucket = cur.getString(bucketColumn); //nombre de la carpeta donde esta la img
                path = cur.getString(dataColumn);  // path de la imagen para luego mostrarla
                Log.i("imageInfo", "bucket=" + bucket + " path=" +path);
            } while (cur.moveToNext());
        }
    }

Con esta función ya pueden obtener las imágenes. Para decodificarla pueden usar la función que esta disponible en el post de Evitando OOM.

It works!

Roger Sala,


Sensores - Acelerómetro

Algunos dispositivos Android disponen de varios sensores que cómo developers podemos sacar provecho. En este post nos vamos a centrar en el Acelerómetro. Este nos permite detectar con gran sensibilidad los movimientos de desplazamiento y giro que se aplican en el teléfono. A partir, de esta definición podemos empezar a pensar en distintas aplicaciones que podríamos desarrollar.

A continuación les dejo el código base que se podría usar para empezar a sacarle provecho:

public class MySensor extends Activity implements SensorEventListener {
    private SensorManager mSensorManager;
    private Sensor mAccelerometer;
    private boolean isEnabledSensor = false;

    @Override
    public final void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mAccelerometer = mSensorManager
                .getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

        if (mAccelerometer != null) {
            isEnabledSensor = true;
          } else {
            Toast.makeText(getApplicationContext(),
                    "Not accelerometer detected", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public final void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Do something here if sensor accuracy changes.
    }

    @Override
    public final void onSensorChanged(SensorEvent event) {
        //event contiene un vector que nos permite obtener las 3 coordenadas
        float x_value = event.values[0];
        float y_value = event.values[1];
        float z_value = event.values[2];
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (isEnabledSensor) {
            mSensorManager.registerListener(this, mAccelerometer,
                    SensorManager.SENSOR_DELAY_NORMAL);
        }

    }

    @Override
    protected void onPause() {
        super.onPause();
        if (isEnabledSensor) {
            mSensorManager.unregisterListener(this);
        }
    }

Como podemos ver, al iniciar la app tenemos que comprobar si esta disponible o no, el acelerómetro en este dispositivo. Igualmente, se tiene que aplicar en las otras funciones (onResume, onPause).

En la función "onSensorChanged" es donde tenemos que realizar la acción que queremos llevar a cabo cuando se produzca el evento deseado.

It works!

Roger Sala

lunes, 11 de junio de 2012

Evitando OOM procesando bitmaps

Cuando trabajamos con el procesamiento de imágenes en Android, uno de los principales problemas que nos encontramos es el ya (para mí) típico: "java.lang.OutOfMemoryError: bitmap size exceeds VM budget". Normalmente, este error suele pasar cuando estamos realizando la siguiente acción:
   Bitmap bitmap = BitmapFactory.decodeFile(fileName);
Una opción que en mis proyectos ha funcionado es implementar este método de la siguiente forma:
    private Bitmap decodeFile(File f){
        try {
            //Decode image size
            BitmapFactory.Options o=new BitmapFactory.Options();
            o.inSampleSize = 8;
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(f),null,o);

            //The new size we want to scale to
            final int REQUIRED_SIZE=70;

            //Find the correct scale value. It should be the power of 2.
            int scale=1;
            while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale  /2>=REQUIRED_SIZE)
                scale*=2;

            //Decode with inSampleSize
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize=scale;
            return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
        } catch (FileNotFoundException e) {}
        return null;
    }


 Esta función, funciona en la mayoría de los casos y terminales. En caso que tengáis algun problema, se puede solucionar cambiando los valores de los parámetros:
 - o.inSampleSize;
 - REQUIRED_SIZE;


It works!


Roger Sala,

Facebook y Twitter en tus aplicaciones

Twitter y facebook son las 2 redes sociales que no pueden faltar en tu aplicación si quieres que la gente comparta información des de ella. A parte, de la publicidad que se genera de tu app.
En este post voy a mostrar la forma más sencilla de que tu app permita compartir mediante estas dos redes sociales. Conozco y he probado la APIs de Facebook, Twitter y varias librerías, pero la forma más fácil de hacerlo es abriendo la aplicación y añadirle el texto que quieres que se muestre. Puedes pensar, ¿y si no tengo la app instalada? Muy sencillo, se puede abrir un webview directamente a Facebook o Twitter.
No obstante, se que no se puede generalizar pero en este caso es practicamente absurdo no hacerlo, y es que, todo usuario Android que tiene cuenta a Facebook/Twitter tiene su aplicación instalada, entonces, este método pasa a ser el más fácil y rápido de añadir a tu app.

a continuación les dejo el código a añadir:

public void shareTwitter(Context context, String what)
{
       try {
            Intent sharingIntent = new Intent(Intent.ACTION_SEND);
            sharingIntent.setClassName("com.twitter.android",
                    "com.twitter.android.PostActivity");
            sharingIntent.putExtra(Intent.EXTRA_TEXT, what);
            context.startActivity(sharingIntent);
        } catch (Exception e) {
           //web
            Intent i = new Intent();
            i.putExtra(Intent.EXTRA_TEXT, "Mi primer tweet!");
            i.setAction(Intent.ACTION_VIEW);
            i.setData(Uri.parse("https://mobile.twitter.com/"));
            context.startActivityForResult(i, TWITTER);
       }
}



public static void shareFacebook(Context context, String what) 
{
       try {
            Intent intentF = new Intent(Intent.ACTION_VIEW);
            intentF.setType("text/plain")
                .setAction("android.intent.action.SEND")
                .setFlags(0x3000000)
                .setClassName("com.facebook.katana","com.facebook.katana.ShareLinkActivity")
                                 .putExtra(Intent.EXTRA_TEXT, what);

               context.startActivity(intentF);
       } catch (ActivityNotFoundException ex) {
         // web
           context.startActivity(new Intent(Intent.ACTION_VIEW, Uri
           .parse("http://m.facebook.com/sharer.php?u=" + what)));
}


It works!

Roger Sala,

domingo, 10 de junio de 2012

Testing de la aplicación

Parte del éxito de una aplicación pasa por asegurarse que la aplicación es fiable y robusta. Para publicar nuestra aplicación en Google Play tenemos que estar al 100% seguros que no tiene errores, que no se fuerza el cierre de la aplicación y que está testeada por el máximo de dispositivos Android posibles. Este último punto es el más complicado. ¿Porqué? Actualmente hay más de 800 dispositivos distintos que tienen el sistema operativo Android instalado. A pesar que Android ha intentado crear unos estándares con la resolución de pantalla siempre hay dispositivos que se resisten a estos. ¡Cuántos más hayamos podido probar mejor!

No obstante, cuando no podemos probar todo lo que queríamos la aplicación tenemos que recurrir a otras alternativas. Una de ellas es incorporar la librería BugSense. Esta librería es muy fácil e útil de usar. La gran ventaja es que es totalmente transparente al usuario, con lo cuál los "crashes" de nuestra app se nos informan vía email sin necesidad de la colaboración del usuario. Con ello podemos recibir y solucionar todos los problemas que la aplicación puede generar y que des de nuestros recursos no hemos podido tratar. A parte, entre otras cosas genera estadísticas, agrupa los errores, los podemos clasificar en resueltos/no resueltos.

Su uso está explicado en la web, no obstante la idea principal es:

1.- Crear su propia cuenta de BugSense.
2.- Descargarse el archivo .jar
3.- Incorporarlo al proyecto. (carpeta /libs + configure Build Path + Add to build Path).
4.- Crear nuestra aplicación en la web de BugSense.
5.- Recomendación: Tan pronto como se ejecute la app añadir la linea de código siguiente:
       BugSenseHandler.setup(this, "ID de la app en BugSense");

6.- Intentar crear un crash de la app y ver que se recibe el correo.
7.- Preparada para publicar con la tranquilidad que todos los "crashes" podrán ser tratados.

It works!

Roger Sala,

Google Analytics

Google Analytics para desarrolladores es un servicio gratuito que nos proporciona Google que podemos usar para gestionar/tener estadísticas/controlar los accesos a nuestra aplicación.

Para añadirlo en nuestra aplicación podemos seguir los siguientes pasos:

1.- Creamos una cuenta de Analytics a partir de nuestra cuenta de gmail. 

2.- Una vez creada la cuenta, podemos añadir el nombre de nuestra aplicación. Una vez añadida obtenemos un UID que nos vamos a guardar para añadirlo en nuestra aplicación.

3.- Volviendo a Eclipse... Añadimos el archivo GoogleAnalytics.jar a la carpeta /libs de nuestro proyecto. Click derecho + Configure Build Path + Add to Build Path.

4.- Añadimos los permisos necesarios en el AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

5.- Una vez añadido ya podemos usar el Tracker (método de Google Analytics). A continuación un ejemplo fácil de como hacerlo.

public class Example extends Activity{

       GoogleAnalyticsTracker mTracker = GoogleAnalyticsTracker.getInstance();

      @Override
      public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
         mtracker.startNewSession(/*UID de Google Analytics*/, this);
         mtracker.trackPageView("nombre que aparecerá en las estadísticas");
      }

    @Override
    protected void onPause() {
        mTracker.dispatch();
        super.onPause();
    }
   
    @Override
    public void onDestroy() {
        super.onDestroy();
        mTracker.stopSession();
    }

}


Si accedemos ahora a nuestra cuenta de GoogleAnalytics ya podemos ver nuestra página indexada en las estadísticas!

It works!

Roger Sala,


Enviar correo

Muchas veces des de nuestra aplicación queremos facilitar al usuario la posibilidad de mandar un correo des de nuestra aplicación. Por ejemplo, añadir la opción de contactar y/o sugerirnos mejoras/críticas en nuestra aplicación. Para llevarlo a cabo basta con añadir el siguiente fragmento de código.

Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); 
emailIntent.setType("message/rfc822");
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[]{getResources().getString(R.string.contactMail) }); 
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, getString(R.string.mailSubject)); 
startActivity(Intent.createChooser(emailIntent, getString(R.string.mailSend)));

Esto es todo!

It works!

Roger Sala,

sábado, 2 de junio de 2012

Navigator GPS

Desde nuestra aplicación podemos abrir otras aplicaciones como por ejemplo el correo, facebook, twitter, etc. En este caso vamos a ver como abrir la aplicación Navigator de nuestro dispositivo y mostrar la indicaciones para llegar hasta un punto concreto.

Para ello añadimos el siguiente código ya sea en un OnClickListener de ListView, ImageButton, Button, etc.

startActivity(new Intent(Intent.ACTION_VIEW, Uri
                        .parse("google.navigation:q=" + siteLatitude + ","
                                + siteLongitude)));

Espero que os sirva y a disfrutar!


It works!

Roger Sala 

Custom Splash

Android por defecto no añade a sus proyectos una vista de Splash dónde se inicie la aplicación. No obstante, mediante Thread tenemos la posibilidad de implementar nuestra propia vista de Splash. Para ello podemos crear Activity siguiente:

public class Splash extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.splash);

        try {
            Thread splashThread = new Thread() {
                @Override
                public void run() {
                    try {
                        int waited = 0;
                        while (waited < 800) {
                            sleep(80);
                            waited += 100;
                        }
                        // perquè aixi quan premin back no apareixi aquesta
                        // vista
                        finish();
                        Intent intent = new Intent(Splash.this,
                                ClassToGo.class);
                        startActivity(intent);
                    } catch (InterruptedException e) {
                    }
                }
            };
            splashThread.start();
        } catch (Exception e) {
        }

    }
}

A continuación añadimos en la carpeta de res/layout/splash.xml:

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@android:color/white">

    <ImageView
        android:id="@+id/splash"
        android:layout_alignParentTop="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:src="@drawable/ic_launcher" />

</RelativeLayout>

Y ya tenemos nuestra propia vista splash. Obviamente, se puede variar el formato y aprovechar el Splash para cargar la información de los webservices. Esta funcionalidad ya depende de cada uno!


It works!


Roger Sala