miércoles, 11 de febrero de 2015

Crear un explorador de Archivos Android

Como vimos en un tutorial pasado aprendimos a buscar y encontrar todas las rutas de almacenamiento interno o sd,en este caso aprenderemos como crear un explorador de archivos partiendo de eso.

Este explorador lo haremos en partes,en este tutorial aprenderemos a crear un explorador minimo,lo suficiente para navegar por directorios y ver archivos,no los podremos abrir ni nada solo ver,pero en otros tutoriales lo mejoraremos.

Bien,si ven el tutorial de como buscar las sd cards o almacenamientos,al final podemos ver una imagen donde se muestran las rutas de los almacenamientos con texto.

http://itimetux.blogspot.mx/2015/02/como-detectar-rutas-de-almacenamiento.html

Justamente partiremos de eso,solo que usaremos diseño grafico,dicen que el diseño grafico hace que la programación se vea agradable y como no soy diseñador grafico vamos a usar iconos de:

iconspedia.com

Estos son los que yo ocupe:

http://www.iconspedia.com/icon/generic-folder-icon-27678.html 
http://www.iconspedia.com/icon/file-blank-icon-47723.html 
http://www.iconspedia.com/icon/explorer-icon-25475.html
http://www.iconspedia.com/icon/hdd-usb-icon-23267.html

Primero vamos a abrir eclipse y crear un nuevo proyecto android,le vamos a poner de nombre,por ejemplo:

Timeexplorer

Ahora vamos a crear un grid para elegir los posibles almacenamientos,pero para crear un grid necesitamos un Adaptador,pero primero vamos a crear un objeto donde guardar la informacion de cada archivo:



1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.itimetux.timeexplorer;

public class Archivo {
 private String Path;
 private String Name;
 private boolean isDir;
 public Archivo(String Name,String Path,boolean dir){
  this.Name = Name;
  this.Path = Path;
  this.isDir = dir;
 }
 public void setName(String Name) {
  this.Name = Name;
 }
 public void setPath(String Path){
  this.Path = Path;
 }
 public void setIsDir(boolean dir){
  this.isDir = dir;
 }
 public String getName() {
  return this.Name;
 }
 public String getPath() {
  return this.Path;
 }
 public boolean isDirectory() {
  return this.isDir;
 }
}

Ese archivo no se tiene que explicar mucho solo son getters y setters que es el objeto mínimo para guardar información,uno para el nombre otro para el path y también para ver si es un directorio,este se podría usar desde la clase File pero nos ahorramos usar mas de una llamada.

Bien ahora si el Adapter,lo explico en el codigo:


1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.itimetux.timeexplorer;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class Adapter extends ArrayAdapter<Archivo> {
 private Context c; // Contexto
 private int id; // Id del resource (layout)
 private ArrayList<Archivo> Archivos; // Todos los archivos

 public Adapter(Context context, int id, ArrayList<Archivo> archivos) {
  super(context, id, archivos);
  c = context; // Definimos las variables globales
  this.id = id;
  Archivos = archivos;
 }

 public Archivo getArchivo(int i) {
  return Archivos.get(i); // Esta funcion regresa un Archivo basado en el
        // index que le pasemos
 }

 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
  View layout = convertView;
  if (layout == null) {
   LayoutInflater Linflated = (LayoutInflater) c
     .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   layout = Linflated.inflate(id, null); // Inflamos la layout
  }

  final Archivo file = Archivos.get(position); // Guardamos el archivo
              // actual en un objeto
  if (file != null) { // Vemos si no hay un error
   TextView Name = (TextView) layout.findViewById(R.id.textViewName); // El
                    // nombre
   TextView Path = (TextView) layout.findViewById(R.id.textViewPath); // El
                    // path
                    // o
                    // ruta
   /*
    * Para el nombre y el path vemos si hay un error y si no es asi le
    * ponemos el texto
    */
   if (Name != null)
    Name.setText(file.getName());
   if (Path != null)
    Path.setText(file.getPath());
   // Vemos si es un directorio
   if (file.isDirectory()) {
    ImageView folder = (ImageView) layout
      .findViewById(R.id.imageViewIcon);
    Drawable image = c.getResources().getDrawable(
      R.drawable.generic256);
    folder.setImageDrawable(image); // Si es asi le ponemos el icono
            // de directorio
   }
   if (!file.isDirectory()) { // Si no es directorio
    ImageView folder = (ImageView) layout
      .findViewById(R.id.imageViewIcon);
    Drawable image = c.getResources().getDrawable(
      R.drawable.fileblanc256);
    folder.setImageDrawable(image); // Le ponemos el icono de
            // archivo
   }
  }
  return layout;
 }
}

Ese seria un adaptador mínimo para usar un grid,ahora lo podríamos llamar definiendo lo necesario en MainActivity y quedaría así:


Se ve mejor con iconos :D

Bien aun no lo podemos usar primero tenemos que manejar el evento de clic sobre los elementos de almacenamiento que están en el grid,en mi caso 2,asi queda el MainActivity:



1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package com.itimetux.timeexplorer;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;


public class MainActivity extends ActionBarActivity {
 private GridView SDS; // Grid
 private ArrayList<String> SD_CARDS; // Rutas de almacenamiento

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); // Asignamos el layout principal
        SDS = (GridView) findViewById(R.id.sdcards); // Definimos el grid
        SD_CARDS = getAllsds();  // Definimos las rutas de almacenamiento
        final GridAdapter gadapter = new GridAdapter(this,
    R.layout.sdcards, SD_CARDS); // Creamos un adaptador
        SDS.setAdapter(gadapter); // Le asignamos el adaptador al grid
        SDS.setOnItemClickListener(new OnItemClickListener() { 
         // Manejamos los clic sobre los elementos del grid
   @Override
   public void onItemClick(AdapterView<?> a, View v,
     final int position, long id) {
    // Vamos a iniciar una nueva actividad pasando como extra la ruta de almacenamiento
    Intent in = new Intent(MainActivity.this, Explorador.class);
    in.putExtra("SD", SD_CARDS.get(position));
    startActivity(in); // Inicamos la actividad
   }
  });
    }

    private ArrayList<String> getAllsds() {
  ArrayList<String> tmp = new ArrayList<String>(); // Array de todas las rutas

  String rutas[] = { "/mnt/", "/storage/" }; // Rutas posibles

  for (int i = 0; i < rutas.length; i++) {
   File file = new File(rutas[i]);// Ruta actual ( listamos de uno a uno )
   if (!file.exists()) // Si no existe pasamos a la siguiente
    continue;
   System.out.println("Ruta : " + rutas[i]); // Imprimimos para "depurar"
   String f[] = file.list(); // Contenido de ruta actual
   for (int o = 0; o < f.length; o++) { // Recorremos el contenido de la ruta actual
    if (f[o].indexOf("sd") != -1) { // Vemos si contiene sd en el nombre si es asi es almacenamiento
     String ruta = rutas[i] + f[o]; // Creamos una Ruta,esta ruta es la original
     File fd = new File(ruta); // Creamos un nuevo File para evitar rutas repetidas basadas en symlinks
     String toAdd;
     try {
      System.out.println("Canocial :"
        + fd.getCanonicalPath().toString()); // Esta seria la ruta original
      toAdd = fd.getCanonicalPath().toString();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      toAdd = null;
      continue;
     }
     if (!tmp.contains(toAdd)) { // Si se repitiera no lo añadimos
      System.out.println("A añadir :" + toAdd);
      tmp.add(toAdd);
     }
    }
   }
  }

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

    @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();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

El menú no lo tocamos,tan solo trabajamos en el onCreate,vamos a definir los .xml que hasta ahora necesitamos:

sdcards.xml


1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/tarjeta"
        android:layout_width="100dp"
        android:layout_height="100sp"
        android:gravity="center"
        android:src="@drawable/hddusb256" />

    <TextView
        android:id="@+id/nombretarjeta"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#000"
        android:textStyle="bold"
        />

</LinearLayout>

El activity_main.xml:



1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<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"
    tools:context="com.itimetux.timeexplorer.MainActivity" >

    <GridView
        android:id="@+id/sdcards"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center"
        android:numColumns="auto_fit"
        android:stretchMode="columnWidth" >
    </GridView>

</RelativeLayout>

Solo falta crear el explorador,para ello definiremos una activitad que se extienda de ListActivity y a la cual es la que llamamos cuando le damos clic a una ruta de almacenamiento:



1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package com.itimetux.timeexplorer;

import java.io.File;
import java.util.ArrayList;

import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.Toast;

public class Explorador extends ListActivity {
 private Adapter adapter;
 private File FullDir;
 private String SDCARD_PATH = "";

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  Intent intent = getIntent();
  Bundle extras = intent.getExtras(); // Llamamos los extras
  SDCARD_PATH = extras.getString("SD"); // Llamamos la ruta de
            // almacenamiento que mandamos
            // en el main
  File Directorio = new File(SDCARD_PATH); // Creamos un objeto File con
             // la ruta de almacenamiento
  if (Directorio.exists()) { // Primero vemos si existe,por si algo
         // saliera mal
   try { // Iniciamos un try,por si algo saliera mal de nuevo
    startList(Directorio); // Mandamos la ruta inicial a la funcion
          // que creara la vista
   } catch (Exception e) {
    e.printStackTrace();
    Toast.makeText(
      getApplicationContext(),
      "No puedes acceder en este momento a " + " "
        + SDCARD_PATH, Toast.LENGTH_LONG).show(); 
    // Sino podemos acceder lo mostramos
   }
  }
 }

 private void startList(File Directorio) {
  String titulo = Directorio.getName(); // Obtenemos el nombre de la ruta actual
  this.setTitle(titulo); // Colocamos el titulo a la vista
  FullDir = Directorio; // Asignamos esta variable extra que usaremos despues

  File Allfiles[] = Directorio.listFiles(); // Listamos todos los archivos del directorio actual
  if (Directorio.listFiles() == null) { // Si es null,esta vacio y lo mostramos
   Toast.makeText(getApplicationContext(), "Carpeta vacia",
     Toast.LENGTH_SHORT).show();
   return;
  }
  // Creamos un Array para los directorios
  ArrayList<File> Dirs = new ArrayList<File>();
  // Creamos un Array para los archivos
  ArrayList<File> Files = new ArrayList<File>();
  //Creamos un array para guardar Archivos y Directorios
  //Esto servira para ordenarlos
  ArrayList<File> AllFilesGood = new ArrayList<File>();
  // Creamos un Array de Archivos que guardara la informacion de archivos y directorios
  ArrayList<Archivo> Archivos = new ArrayList<Archivo>();
  for (int i = 0; i < Allfiles.length; i++) { // Recorremos la ruta actual
    // Si es .android_secure no la usamos ya que no nos deja Android
   if (Allfiles[i].getName().equals(".android_secure"))
    continue;
   // Si es un directorio lo ponemos en Dirs
   if (Allfiles[i].isDirectory()) {
    Dirs.add(Allfiles[i]);
   // Si es un Archivo lo ponemos en Files
   } else if (Allfiles[i].isFile()) {
    Files.add(Allfiles[i]);
   } else {
    // Si no es directorio ni archivo 
    // Continuamos(podemos omitir esta linea de codigo)
    continue; 
   }
  }
  // Podemos poner directorios y luego archivos,o viceversa
  AllFilesGood.addAll(Dirs); // Ponemos directorios primero
  AllFilesGood.addAll(Files); // Pondemos Archivos despues
  // Vemos si es la raiz(la ruta del almacenamiento)
  if (!Directorio.getAbsolutePath().equals(SDCARD_PATH)) 
   Archivos.add(new Archivo("...", "", true)); // Si no es la raiz agregamos una forma de regresar
  for (int i = 0; i < AllFilesGood.size(); i++) { // Recorremos Todos los archivos
   File tmp = AllFilesGood.get(i); // Creamos un objeto que se destruira muy rapidamente
   boolean flag = false; // Usamos esta variable para ver si es un directorio
   if (tmp.isDirectory()) // Vemos is es un directorio
    flag = true;
   Archivos.add(new Archivo(tmp.getName(), tmp.getPath(), flag)); // Agregamos un nuevo objeto
  }
  // Asignamos al adaptador el context,el layout y un array de archivos
  adapter = new Adapter(Explorador.this, R.layout.explorer, Archivos);
  this.setListAdapter(adapter); // Asignamos el adaptador
 }

 @Override
 protected void onListItemClick(ListView l, View v, int position, long id) {
  // Manejamos los clic
  super.onListItemClick(l, v, position, id);
  Archivo archivo = adapter.getArchivo(position); // Creamos un archivo temporal
  if (archivo.isDirectory()) { // Vemos si es directorio
   if (archivo.getName().equals("...")) { // Si es el regreso regresamos al path anterior
    // Con estas lineas de codigo obtenemos el path anterior
    String[] segments = FullDir.getAbsolutePath().split("/");
    String lastPath = "";
    for (int i = 1; i < segments.length; i++) {
     if (i != segments.length - 1) {
      lastPath = lastPath + "/" + segments[i];
     }
    }
    startList(new File(lastPath)); // Recargamos la vista
    return;
   }
   File Directorio = new File(archivo.getPath()); // Obtenemos el path
   startList(Directorio); // Recargamos la vista
  }
  // Aqui manejariamos los archivos
 }

}

También necesita de un xml llamado explorer.xml:


1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

    <ImageView
        android:id="@+id/imageViewIcon"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginTop="10dp"
        android:maxHeight="5dp"
        android:maxWidth="5dp"
        android:src="@drawable/generic256" />

    <TextView
        android:id="@+id/textViewName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/imageViewIcon"
        android:layout_toRightOf="@+id/imageViewIcon"
        android:lines="1"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/textViewPath"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/imageViewIcon"
        android:layout_alignLeft="@+id/textViewName"
        android:lines="1" />

</RelativeLayout>

Por ultimo necesitamos Modificar el Manifiest,necesitamos añadir el Activity de exploración y añadir el permiso de leer la memoria:



1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.itimetux.timeexplorer"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
         <activity
            android:name=".Explorador"
            android:label="@string/app_name" >
        </activity>
    </application>

</manifest>

También para que funcione necesitamos tener soporte para ActionBarActivity v7,esto es largo de explicar en este tutorial así que pueden pedir ayuda en google.

Y listo asi queda:



Eso es todo :D
Leer más...