sábado, 23 de junio de 2012

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