2012-06-26 12 views
5

La idea es tener una lista de elementos donde, después de hacer clic en un elemento, una barra de progreso se llene lentamente a medida que se completa la tarea. Por ejemplo, imagine una lista de archivos, con un botón Descargar por cada uno. Cuando se hace clic en el botón de descarga, el archivo se descarga en segundo plano y se llena una barra de progreso que muestra qué tan cerca está el archivo.Agregar una barra de progreso a un ListView/OnClick llamado una sola vez

Para lograr esto, creo una AsyncTask que ocasionalmente llama a notifyDataSetChanged en el adaptador para volver a dibujarlo. Si bien esto funciona después de hacer clic en un botón, hasta que se complete la tarea AsyncTask, no puedo hacer clic en otros botones en ListView. ¿Puede alguien decirme qué estoy haciendo mal?

Estoy ejecutando esto en el emulador (x86) en Ice Cream Sandwich.

tengo una de Descargar para representar el progreso de una descarga (el código de abajo se simplifica por razones de brevedad):

class DownloadItem { 
    public String name;  // Name of the file being downloaded 
    public Integer progress; // How much is downloaded so far 
    public Integer length; // Size of the file 
} 

entonces tengo una ArrayAdapter que se adapta una lista de de Descargar para el ListView:

class DownloadArrayAdapter extends ArrayAdapter<DownloadItem> { 
    List<DownloadItem> mItems; 
    public DownloadArrayAdapter(List<DownloadItem> items) { 
     mItems = items; 
    } 

    @Override 
    public View getView(int position, View convertView, ViewGroup parent) { 
     View row = convertView; 
     if(row == null) { 
      // Inflate 
      Log.d(TAG, "Starting XML inflation"); 
      LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
      row = inflater.inflate(R.layout.download_list_item, parent, false); 
      Log.d(TAG, "Finished XML inflation"); 
     } 

     DownloadItem item = mItems.get(position); 

     ProgressBar downloadProgressBar = (ProgressBar) row.findViewById(R.id.downloadProgressBar); 
     Button downloadButton = (Button) row.findViewById(R.id.downloadButton); 

     downloadButton.setTag(item); 
     downloadProgressBar.setMax(item.length); 
     downloadProgressBar.setProgress(item.progress); 

     return row; 
    } 
} 

Hasta ahora, muy bien, esto representa la lista. En mi actividad, tengo el OnClickListener:

class DownloadActivity extends Activity { 
    //... 
    public void onDownloadButtonClick(View view) { 
     DownloadItem item = (DownloadInfo)view.getTag(); 
     DownloadArrayAdapter adapter = (DownloadArrayAdapter) view.getAdapter(); 
     new DownloadTask(adapter, item).execute(); 
     //new DownloadTask(adapter, item).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) 
    } 
} 

He intentado esto con executeOnExecutor, así como sólo hay que ejecutar, pero sin suerte. DownloadTask es:

class DownloadTask extends AsyncTask<Void, Integer, Void> { 
    ArrayAdapter<?> mAdapter; 
    DownloadItem mItem; 

    public DownloadTask(ArrayAdapter<?> adapter, DownloadItem item) { 
     mItem = item; 
    } 

    //Dummy implementation 
    @Override 
    public Void doInBackground(Void ... params) { 
     for(int i=0; i<mItem.length; ++i) { 
      Thread.sleep(10); publishProgress(i); 
     } 
     return null; 
    } 

    @Override 
    public void onProgressUpdate(Integer ... values) { 
     mItem.progress = values[0]; 
     mAdapter.notifyDataSetChanged(); 
    } 
} 

Esto funciona - casi. Cuando hago esto, después de hacer clic en un botón, ProgressBar se actualiza normalmente, pero no puedo hacer clic en otros botones en ListView hasta que devuelva AsyncTask. Es decir, onDownloadButtonClick nunca se llama. Si elimino la llamada mAdapter.notifyDataSetChanged() de la función onProgressUpdate, se actualizan varias tareas simultáneamente, pero, por supuesto, la lista no se invalida, por lo que tengo que desplazarme para ver los cambios.

¿Qué estoy haciendo mal y cómo puedo solucionarlo?

Editar: Jugué con esto un poco más, y parece que la frecuencia de las llamadas notifyDataSetChanged afecta si se está llamando a onClick o simplemente se está perdiendo. Con el código anterior, al hacer clic frenéticamente, ocasionalmente puedo obtener una segunda barra de descarga para comenzar. Si aumente el Thread.Sleep a algo mucho más grande, como 2000, la lista funciona como esperaba originalmente.

Nueva pregunta: ¿cómo obtengo que ProgressBars se actualice sin problemas mientras no se bloquea el uso de OnClick?

editar # 2: He empujado a un proyecto de ejemplo de este problema a mi cuenta de GitHub: https://github.com/mdkess/ProgressBarListView

+1

Parece que utilice la vista de lista? Si es así, y si tiene tiempo, creo que vale la pena ver esto: [Google I/O 2010 - Vista de lista] (http://www.youtube.com/watch?v=wDBM6wVEO70). Una sugerencia: puede usar 'Thread.sleep (800)' (por ejemplo), '10' no es necesario. –

+1

¿Dónde configura el oyente de clic? Extrañas esto en el código ficticio. – reTs

+0

@reTS: lo configuré en XML, con el parámetro android: onClick para el botón. Definitivamente se llama al controlador, una vez, de todos modos. Ver mi edición a la pregunta también. – mindvirus

Respuesta

5

I han descubierto esto.

En lugar de llamar a notifyDataSetChanged(), guardé una referencia a cada ProgressBar en el objeto DownloadItem. Luego, al desplazarme por el ListView, cuando se pasaron los objetos antiguos como convertView, eliminé ProgressBar del DownloadInfo anterior y lo puse en el nuevo.

Por lo tanto, getView para mi adaptador de serie a continuación, se convirtió en:

@Override 
public View getView(int position, View convertView, ViewGroup parent) { 
    View row = convertView; 
    final DownloadInfo info = getItem(position); 
    // We need to set the convertView's progressBar to null. 

    ViewHolder holder = null; 

    if(null == row) { 
     LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
     row = inflater.inflate(R.layout.file_download_row, parent, false); 

     holder = new ViewHolder(); 
     holder.textView = (TextView) row.findViewById(R.id.downloadFileName); 
     holder.progressBar = (ProgressBar) row.findViewById(R.id.downloadProgressBar); 
     holder.button = (Button)row.findViewById(R.id.downloadButton); 
     holder.info = info; 

     row.setTag(holder); 
    } else { 
     holder = (ViewHolder) row.getTag(); 

     holder.info.setProgressBar(null); 
     holder.info = info; 
     holder.info.setProgressBar(holder.progressBar); 
    } 

    holder.textView.setText(info.getFilename()); 
    holder.progressBar.setProgress(info.getProgress()); 
    holder.progressBar.setMax(info.getFileSize()); 
    info.setProgressBar(holder.progressBar); 

    holder.button.setEnabled(info.getDownloadState() == DownloadState.NOT_STARTED); 
    final Button button = holder.button; 
    holder.button.setOnClickListener(new OnClickListener() { 
     @Override 
     public void onClick(View v) { 
     info.setDownloadState(DownloadState.QUEUED); 
     button.setEnabled(false); 
     button.invalidate(); 
     FileDownloadTask task = new FileDownloadTask(info); 
     task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 
     } 
    }); 
    return row; 
    } 

El AsyncTask descarga establecería entonces los avances en la progressBar, si no fuera nula, y esto funcionó como se esperaba.

he subido el código corregido a GitHub, se puede ver aquí: https://github.com/mdkess/ProgressBarListView

+0

+1 por el rendimiento increíble. ¡¡¡¡aclamaciones!!!! – skygeek

+0

Hola ... Tu código fue de gran ayuda para mí ... Pero tengo un problema. Cuando ejecuto su código en una actividad, funciona demasiado bien. Cuando ejecuto su código en un fragmento, obtengo el objeto de barra de progreso nulo. ¿Por que es esto entonces? –

+0

¿Qué sucede si elimina un artículo cuando uno o dos más están en progreso (descarga por ejemplo)? porque lo intento, pero cuando elimino un elemento, el error progressBars. – Cocorico

1

¿Usted intentó establecer detector de clics para descargar artículo propio adaptador como siguiente:

@Override 
public View getView(int position, View convertView, ViewGroup parent) { 
    View row = convertView; 
    if(row == null) { 
     // Inflate 
     Log.d(TAG, "Starting XML inflation"); 
     LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
     row = inflater.inflate(R.layout.download_list_item, parent, false); 
     Log.d(TAG, "Finished XML inflation"); 
    } 

    final DownloadItem item = mItems.get(position); 

    ProgressBar downloadProgressBar = (ProgressBar) row.findViewById(R.id.downloadProgressBar); 
    Button downloadButton = (Button) row.findViewById(R.id.downloadButton); 

    downloadButton.setTag(item); 
    downloadProgressBar.setMax(item.length); 
    downloadProgressBar.setProgress(item.progress); 

    downloadButton.setOnClickListener(new View.OnClickListener() { 

     @Override 
     public void onClick(View v) { 
      new DownloadTask(DownloadArrayAdapter.this, item).execute(); 
     } 
    }); 

    return row; 
} 
+0

Por desgracia, eso no sirvió de nada, ni siquiera está llegando a onClick. Creo que tiene que ver con llamar notifyDataSetChanged con demasiada frecuencia, por lo que estoy experimentando con mantener una referencia a la barra de progreso actual. Buena idea, sin embargo. (Por cierto, no te devolví). – mindvirus

+0

Gracias. No importa quién me votó. En realidad, no leí tu pregunta correctamente. Pensé, estableciste la etiqueta en rowView ... Pero estableciste la etiqueta para descargar el botón. Así que te di esta solución. Sí, pasar la referencia de la barra de progreso y actualizar la barra de progreso directamente debería funcionar. Por cierto, hiciste la pregunta en muy buena manera y me gusta su implementación también :-) .. – Veer

Cuestiones relacionadas