2012-06-20 20 views
11

Mi sono grattato la testa da molto tempo ormai e ho cercato una risposta senza fortuna! Sembra essere banale, ma per quanto ne so, non lo è.Barra di progresso rotante in ogni listview voce

Io uso un controllo ListView nella mia applicazione Android in cui ogni elemento (vista) visualizza un ProgressBar filatura prima che il contenuto viene caricato e visualizzato (il contenuto viene recuperato attraverso chiamate HTTP e JSON, quindi potrebbe volerci un po 'per elaborare) . Il problema è che le barre di avanzamento rotanti ruotano indipendentemente l'una dall'altra, creando così l'effetto di un turbinante caos invece di una fila sincronizzata di indicatori di caricamento di bell'aspetto.

Ho provato tutto quello che potevo venire con ... non riciclare il ProgressBar in GetView() ... hanno una sola istanza della stessa ProgressBar ... resettare l'androide ProgressBars: i progressi ogni volta che le voci di elenco diventa visibile (tramite onScroll() nell'attività) ... ecc., ma poiché iniziano a girare nel tempo di creazione (quando getView viene chiamato nell'adattatore) non avranno mai la stessa sincronizzazione del ciclo.

enter image description here

Eventuali suggerimenti sono i benvenuti!

+1

grande domanda! Sono curioso di me stesso. – st0le

+1

sì ... Grande .... –

+0

(Solo ipotesi) Possibile, a causa del listato disegnato uno per uno sullo schermo. La barra di avanzamento del primo listitem è stata avviata per prima. – user370305

risposta

2

EDIT: Ora funziona perfettamente! - la chiamata del listener di animazione inserita setStartOffset() torna a 0 dopo la prima ripetizione in modo che non continui a "saltare" in modo casuale.


Ho trovato una soluzione funzionante per questo problema, che funziona sincronizzando l'animazione con i millisecondi del sistema corrente. È un po 'un trucco, poiché utilizza il reflection per ottenere il campo mAnimation in ProgressBar. Detto questo, questo campo è rimasto sul posto nelle fonti Android da quando è stato creato (funziona fino a 4.2).

Creare la classe android.widget.SyncedProgressBar e utilizzarlo al posto di ProgressBar nei file .xml. In sostanza, l'animazione inizia all'inizio della durata dell'animazione. Puoi anche giocare con setDuration(500) per verificare che funzioni (la ruota di avanzamento girerà molto rapidamente). Spero che questo ti aiuti!

package android.widget; 

import android.annotation.SuppressLint; 
import android.content.Context; 
import android.util.AttributeSet; 
import android.util.Log; 
import android.view.View; 
import android.view.animation.AlphaAnimation; 
import android.view.animation.Animation; 
import android.view.animation.Animation.AnimationListener; 

import java.lang.reflect.Field; 

/** 
* @author Oleg Vaskevich 
* 
*/ 
public class SyncedProgressBar extends ProgressBar { 

    public SyncedProgressBar(Context context) { 
     super(context); 
     modifyAnimation(); 
    } 

    public SyncedProgressBar(Context context, AttributeSet attrs) { 
     super(context, attrs); 
     modifyAnimation(); 
    } 

    public SyncedProgressBar(Context context, AttributeSet attrs, int defStyle) { 
     super(context, attrs, defStyle); 
     modifyAnimation(); 
    } 

    @Override 
    public void setVisibility(int v) { 
     super.setVisibility(v); 
     modifyAnimation(); 
    } 

    @SuppressLint("NewApi") 
    @Override 
    protected void onVisibilityChanged(View changedView, int visibility) { 
     super.onVisibilityChanged(changedView, visibility); 
     modifyAnimation(); 
    } 

    @Override 
    protected void onAttachedToWindow() { 
     super.onAttachedToWindow(); 
     modifyAnimation(); 
    } 

    @Override 
    public synchronized void setIndeterminate(boolean indeterminate) { 
     super.setIndeterminate(indeterminate); 
     modifyAnimation(); 
    } 

    public void modifyAnimation() { 
     Field mAnim; 
     try { 
      mAnim = ProgressBar.class.getDeclaredField("mAnimation"); 
      mAnim.setAccessible(true); 
      AlphaAnimation anim = (AlphaAnimation) mAnim.get(this); 
      if (anim == null) 
       return; 

      // set offset to that animations start at same time 
      long duration = anim.getDuration(); 
      long timeOffset = System.currentTimeMillis() % duration; 
      anim.setDuration(duration); 
      anim.setStartOffset(-timeOffset); 
      anim.setAnimationListener(new AnimationListener() { 
       @Override 
       public void onAnimationStart(Animation animation) { 
       } 

       @Override 
       public void onAnimationRepeat(Animation animation) { 
        animation.setStartOffset(0); 
       } 

       @Override 
       public void onAnimationEnd(Animation animation) { 
       } 
      }); 
     } catch (Exception e) { 
      Log.d("SPB", "that didn't work out...", e); 
      return; 
     } 
     postInvalidate(); 
    } 

} 
0

Nel vostro adattatore GetView() disabilitare le vostre progressViwe di, e poi per ogni progressView

handler.postAtTime(new Runnable(){ 

public void run(){ 
    progressView.setEnabled(true); 
}}, someTimeInTheFuture 
); 

Che cosa questo farà è consentire a tutti i tuoi progressViews allo stesso tempo. Questo potrebbe funzionare, non ho provato. Se questo non funziona, potresti voler aggiungere progressView dynamically (non incluso nel layout, ma aggiungerlo tramite addView()). Ma fallo nel modo percorribile, la chiave qui è in modo che vengano aggiunti tutti allo stesso tempo.

1

Ecco come farlo funzionare. Usa solo un AnimationDrawable, multiplex il callback. ProgressBar in realtà non aggiunge nulla, si potrebbe anche rimanere con View. Crea una sottoclasse di View che ignori la gestione Drawable.

public class Whirly extends View 
{ 
    Drawable image = null; 

    public Whirly(Context context, AttributeSet attr) 
    { 
     super(context, attr); 
    } 

    @Override 
    public void draw(Canvas canvas) 
    { 
     if (image!=null) 
     image.draw(canvas); 
    } 

    public void setImageDrawable(Drawable image) 
    { 
     this.image = image; 
     invalidate(); 
    } 
} 

poi prendere la vostra attività di tenere traccia di tutte le whirlies in qualche modo.

public class Demo extends Activity implements Drawable.Callback 
{ 
    private Handler h; 
    private AnimationDrawable a; 

    private Whirly w0; 
    private Whirly w1; 
    private Whirly w2; 
    private Whirly w3; 

    public void onCreate(Bundle bundle) 
    { 
     ... 

     h = new Handler(); 

     a = (AnimationDrawable) getResources().getDrawable(R.drawable.mywhirly); 
     int width = a.getIntrinsicWidth(); 
     int height = a.getIntrinsicHeight(); 
     a.setBounds(0, 0, width, height); 

     w0 = (Whirly) findViewById(R.id.whirly0); 
     w1 = (Whirly) findViewById(R.id.whirly1); 
     w2 = (Whirly) findViewById(R.id.whirly2); 
     w3 = (Whirly) findViewById(R.id.whirly3); 

     w0.setImageDrawable(a); 
     w1.setImageDrawable(a); 
     w2.setImageDrawable(a); 
     w3.setImageDrawable(a); 

     a.setCallback(this); 
     a.start(); 
    } 

    public void invalidateDrawable (Drawable who) 
    { 
     w0.invalidate(); 
     w1.invalidate(); 
     w2.invalidate(); 
     w3.invalidate(); 
    } 

    public void scheduleDrawable (Drawable who, Runnable what, long when) 
    { 
     h.postAtTime(what, who, when); 
    } 

    public void unscheduleDrawable (Drawable who, Runnable what) 
    { 
     h.removeCallbacks(what, who); 
    } 
} 
+0

Ho lavorato al tuo codice per un po 'di tempo e sto facendo qualche progresso, almeno sembra essere nella giusta direzione :) Il problema però, è che ho difficoltà ad implementare questo codice nel mio adattatore. Nel tuo esempio, ogni "whirly" è già definito in alcune risorse, e sono tutte impostate per utilizzare lo stesso AnimationDrawable. Voglio avere solo uno definito, che ogni elemento della lista nel mio listino condividerà. – maglar

+0

Nel tuo ListView ogni elemento deve avere un layout completo con le proprie viste. Per ogni grafica animata avrai bisogno di una vista. Un Whirly è solo un involucro molto sottile attorno alla visualizzazione meno costosa (Visualizza). L'XML di layout dell'articolo avrebbe solo un LinearLayout orizzontale, un TextView per "Mar 1" e un Whirly. Quando un oggetto è finito, devi whirly.setImageDrawable (null) e rimuoverlo dall'elenco di callback. onResume sarebbe a.start(), onSuspend sarebbe a.stop(), quando il conteggio di Whirlys nell'elenco di callback si azzera a.stop(); – Renate

0

Scavando attraverso il codice sorgente vedo che l'immagine di filatura è un disegnabile che gira a causa di un privato Animation. Non c'è modo di ottenere la vista o l'animazione, eliminando così ogni idea che avevo di riutilizzare un'animazione su tutte le viste. Direi che la tua unica alternativa è quella che suggeriva Jug6ernaut, anche se non è la più bella delle soluzioni.

+0

getIndeterminateDrawable funziona bene in ogni caso. [collegamento] (http://developer.android.com/reference/android/widget/ProgressBar.html#getIndeterminateDrawable%28%29) – Renate

0

Non sono sicuro che sia possibile dato che ogni riga del tuo ListView viene riciclata quando si sposta fuori dallo schermo. Quando una nuova riga appare sullo schermo, tutte le viste ProgressBar devono essere risincronizzate l'una con l'altra. Mantenere tutte le righe sincronizzate sarà un grande PITA.

Avete considerato di caricare tutti i dati dell'elenco in una richiesta e di memorizzare i dati nella memoria locale? Sarebbe molto più efficiente e risulterebbe in una migliore esperienza utente. Non è molto utile avere un ListView che carica immediatamente con righe vuote. Preferisco aspettare che la lista si completi completamente.

+0

Non molto di un PITA, vedi la mia risposta –

0

1> Crea layout per fila lista

<RelativeLayout 
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:padding="10dip" > 

    <TextView 
     android:id="@+id/word_text" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignParentLeft="true" 
     android:layout_centerVertical="true" 
     android:text="Word 1" /> 

    <ProgressBar 
     android:id="@+id/row_progress" 
     style="@style/Widget.Sherlock.Light.ProgressBar" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignParentRight="true" 
     android:layout_centerVertical="true" 
     android:indeterminate="true" /> 

    <ImageView 
     android:id="@+id/speaker" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignParentRight="true" 
     android:layout_centerVertical="true" 
     android:src="@drawable/speaker_icon" 
     android:visibility="gone"/> 
</RelativeLayout> 

2> Poi nel metodo GetView

if(word.getIsWordSynchedLocally() == 1){ 
       convertView.findViewById(R.id.row_progress).setVisibility(View.GONE); 
       convertView.findViewById(R.id.speaker).setVisibility(View.VISIBLE); 
      }else{ 
       convertView.findViewById(R.id.row_progress).setVisibility(View.VISIBLE); 
       convertView.findViewById(R.id.speaker).setVisibility(View.GONE); 
      } 
Problemi correlati