SwipeRefreshLayout setRefreshing () inizialmente non mostra l'indicatore


144

Ho un layout molto semplice, ma quando chiamo setRefreshing(true)a onActivityCreated()mio frammento, ma non mostra inizialmente.

Mostra solo quando faccio un tiro per aggiornare. Qualche idea sul perché inizialmente non si presenta?

Frammento xml:

<android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/swipe_container"
    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">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

        </RelativeLayout>


    </ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>

Codice frammento:

public static class LinkDetailsFragment extends BaseFragment implements SwipeRefreshLayout.OnRefreshListener {

    @InjectView(R.id.swipe_container)
    SwipeRefreshLayout mSwipeContainer;

    public static LinkDetailsFragment newInstance(String subreddit, String linkId) {
        Bundle args = new Bundle();
        args.putString(EXTRA_SUBREDDIT, subreddit);
        args.putString(EXTRA_LINK_ID, linkId);

        LinkDetailsFragment fragment = new LinkDetailsFragment();
        fragment.setArguments(args);

        return fragment;
    }

    public LinkDetailsFragment() {
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        mSwipeContainer.setOnRefreshListener(this);
        mSwipeContainer.setColorScheme(android.R.color.holo_blue_bright,
                android.R.color.holo_green_light,
                android.R.color.holo_orange_light,
                android.R.color.holo_red_light);
        mSwipeContainer.setRefreshing(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        final View rootView = inflater.inflate(R.layout.fragment_link_details, container, false);
        ButterKnife.inject(this, rootView);
        return rootView;
    }

    @Override
    public void onRefresh() {
        // refresh
    }
}

Quale versione usi?
Ahmed Hegazy,

compilare "com.android.support:appcompat-v7:21.0.0"
fragoroso

Confermo che questo problema si è verificato con me da quella versione in corso. Le versioni precedenti non hanno alcun problema. Pubblicherò la soluzione se ne avrò una.
Ahmed Hegazy,

Vorrei provare una versione precedente
fragoroso

Anche per me non funziona su v20. Su quale versione funziona per te?
fragoroso

Risposte:


307

Di fronte allo stesso problema. La mia soluzione -

mSwipeRefreshLayout.post(new Runnable() {
    @Override
    public void run() {
        mSwipeRefreshLayout.setRefreshing(true);
    }
});

1
Funziona bene ma non so perché dobbiamo farlo al posto di mSwipeRefreshLayout.setRefreshing (true);
Cocorico,


1
Questa non è una buona soluzione / soluzione alternativa.Se l'utente è in modalità aereo, refreshLayout inizierà ad aggiornarsi sul proprio thread dopo aver già avviato la richiesta di rete e ricevuto la sua risposta (errore in questo caso). Gestendolo per interrompere refreshLayout, non funzionerebbe poiché non è ancora iniziato! In altre parole, refreshLayout inizierà l'aggiornamento dopo aver ricevuto la risposta. Buona fortuna a fermarlo
Samer,

1
Come ricordo, i "post" sono sincronizzati e vengono eseguiti nell'ordine di aggiunta. Quindi puoi aggiungere un altro post per fermarlo.
Volodymyr Baydalka,

2
Sembra forse più semplice da implementare ma non è carino. La soluzione @ niks.stack di seguito è migliore in quanto non richiede alcuna modifica nel codice che lo utilizza, quindi quando alla fine questo bug viene risolto nella libreria di supporto, si torna semplicemente a supportare la libreria SwipeRefreshLayout
Marcin Orlowski

100

Vedi invece la risposta di Volodymyr Baydalka.

Queste sono le vecchie soluzioni alternative.

android.support.v4Funzionava con una versione precedente di , ma dalla versione 21.0.0 in corso non funziona ed esiste ancora con android.support.v4:21.0.3rilascio il 10-12 dicembre 2014 e questo è il motivo.

L'indicatore SwipeRefreshLayout non viene visualizzato quando setRefreshing(true)viene chiamato prima diSwipeRefreshLayout.onMeasure()

Soluzione:

chiamata setProgressViewOffset() il SwipeRefreshLayoutche invalida la vista circolare del layout causando la SwipeRefreshLayout.onMeasure()chiamata immediata.

mSwipeRefreshLayout.setProgressViewOffset(false, 0,
                (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources().getDisplayMetrics()));
mSwipeRefreshLayout.setRefreshing(true);

AGGIORNAMENTO Soluzione alternativa migliore

Perché la barra delle azioni potrebbe essere più sottile quando l'orientamento cambia o hai impostato manualmente le dimensioni della barra delle azioni. Impostiamo l'offset in pixel dalla parte superiore di questa vista in cui lo spinner di avanzamento dovrebbe venire ripristinato dopo un gesto di scorrimento riuscito alla dimensione della barra delle azioni corrente.

TypedValue typed_value = new TypedValue();
getActivity().getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.actionBarSize, typed_value, true);
mSwipeRefreshLayout.setProgressViewOffset(false, 0, getResources().getDimensionPixelSize(typed_value.resourceId));

AGGIORNAMENTO 20 novembre 2014

Se non è molto importante per la tua app mostrare SwipeRefreshLayout una volta avviata la visualizzazione. Puoi semplicemente pubblicarlo in un momento in futuro utilizzando i gestori o qualsiasi cosa tu voglia.

come esempio.

handler.postDelayed(new Runnable() {

    @Override
    public void run() {
        mSwipeRefreshLayout.setRefreshing(true);
    }
}, 1000);

o come menzionato nella risposta di Volodymyr Baydalka.

Ecco il problema nel tracker dei problemi Android. Si prega di votarlo per mostrare loro che abbiamo bisogno di essere riparato.


Hai fatto molti test con delay time? Sto usando 500sul mio Galaxy S4. Non sono sicuro se questo sarebbe un problema su un altro dispositivo.
theblang,

Non ho fatto molti test con il ritardo, ma penso che 500andrebbe bene. L'ho provato sul emulator. Volevo solo stare safecon i 1000millisecondi
Ahmed Hegazy,

3
Non è necessario pubblicare post in ritardo. puoi semplicemente postare. postare senza indugio significa semplicemente "fare questa cosa una volta che hai finito con quello che stai facendo ora". e quello che sta facendo ora è misurare e definire l'interfaccia utente.
Oren,

47

La mia soluzione è quella di sostituire SwipeRefreshLayout:

public class MySwipeRefreshLayout extends SwipeRefreshLayout {

    private boolean mMeasured = false;
    private boolean mPreMeasureRefreshing = false;

    public MySwipeRefreshLayout(final Context context) {
        super(context);
    }

    public MySwipeRefreshLayout(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (!mMeasured) {
            mMeasured = true;
            setRefreshing(mPreMeasureRefreshing);
        }
    }

    @Override
    public void setRefreshing(final boolean refreshing) {
        if (mMeasured) {
            super.setRefreshing(refreshing);
        } else {
            mPreMeasureRefreshing = refreshing;
        }
    }
}

3
Il vantaggio della tua soluzione è di mantenere intatto l'offset della vista! In questo modo continuiamo in conformità con il modello di progettazione specificato qui: google.com/design/spec/patterns/… . Grazie!
Igor de Lorenzi,

Puoi spiegare come funziona? Funziona ma non capisco bene i meccanismi interni. Mi sto perdendo qualcosa di banale?
Sree,

setRefreshing funziona solo dopo la chiamata onMeasure, quindi memorizziamo il flag di aggiornamento locale e alla prima chiamata onMeasure applicarlo
nikita.zhelonkin

5
Adoro questa soluzione perché significa che il codice che chiama SwipeRefreshLayout può essere esattamente come lo desideriamo, senza complicazioni. Fondamentalmente risolto il bug in SwipeRefreshLayout. SwipeRefreshLayout dovrebbe davvero essere implementato in questo modo.
DataGraham,

Questa risposta potrebbe non sembrare così semplice, ma risolve bene il problema attraverso molte versioni della libreria di supporto (nel mio caso è il 23.1.1). Secondo il ticket questo problema non è stato risolto @ 23.2. code.google.com/p/android/issues/detail?id=77712
Robert

20
mRefreshLayout.getViewTreeObserver()
                .addOnGlobalLayoutListener(
                        new ViewTreeObserver.OnGlobalLayoutListener() {
                            @Override
                            public void onGlobalLayout() {
                                mRefreshLayout
                                        .getViewTreeObserver()
                                        .removeGlobalOnLayoutListener(this);
                                mRefreshLayout.setRefreshing(true);
                            }
                        });

3
Questa risposta è l'unica che non è un hack quindi dovrebbe essere accettata
Heinrich

1
Solo una piccola cosa: .removeGlobalOnLayoutListener dovrebbe essere .removeOnGlobalLayoutListener
mkuech

1
@mkeuch dipende dall'API del tuo targeting. Se il tuo targeting in API16, devi eseguire il controllo della versione dell'API e utilizzare entrambi.
Marko,

Questa mi piace un po 'di più rispetto alla risposta più votata perché è più chiaro il motivo per cui viene utilizzata.
Marcel Bro,

Devo correggermi, questa soluzione NON funziona sempre per me. In alcuni casi la chiamata in setRefreshing(false)wrapping onGlobalLayoutListener()non disattiva l'indicatore di caricamento.
Marcel Bro,


4

Basato sulla risposta di Volodymyr Baydalka , questa è solo una piccola idea che ti aiuterà a mantenere pulito il codice. Merita un post, credo: sarai in grado di ripristinare facilmente il comportamento dalla pubblicazione alla chiamata diretta al metodo, una volta risolto il bug.

Scrivi una classe di utilità come:

public class Utils
{
    private Utils()
    {
    }

    public static void setRefreshing(final SwipeRefreshLayout swipeRefreshLayout, final boolean isRefreshing)
    {
        // From Guava, or write your own checking code
        checkNonNullArg(swipeRefreshLayout);
        swipeRefreshLayout.post(new Runnable()
        {
            @Override
            public void run()
            {
                swipeRefreshLayout.setRefreshing(isRefreshing);
            }
        });
    }
}

Nel tuo codice sostituisci mSwipeContainer.setRefreshing(isRefreshing)con Utils.setRefreshing(mSwipeContainer, isRefreshing): ora solo un punto nel codice deve essere modificato una volta corretto il bug, la Utilsclasse. Il metodo può anche essere integrato (e rimosso da Utils).

Di solito non ci saranno differenze visive evidenti. Tieni presente, tuttavia, che l'aggiornamento in sospeso potrebbe mantenere in vita le tue vecchie Activityistanze, mantenendo le SwipeRefreshLayoutgerarchie nella loro vista. Se questo è un problema, modifica il metodo per usare WeakReferences, ma normalmente non blocchi comunque il thread dell'interfaccia utente e quindi ritarda gc solo di un paio di millisecondi.


2

Inoltre puoi chiamare questo metodo prima di setRefreshing ..

    swipeRefreshLayout.measure(View.MEASURED_SIZE_MASK,View.MEASURED_HEIGHT_STATE_SHIFT);
swipeRefreshLayout.setRefreshing(true);

Mi preoccupo.


1

Ho usato AppCompat Library com.android.support:appcompat-v7:21.0.3, usando il tuo stesso approccio e ha funzionato. Quindi, aggiorni la versione di quella libreria.

Consiglio: RelativeLayoutnon supporta l'orientamento, è un attributo per LinearLayout.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, 
                                                 Bundle savedInstanceState) {       
  ViewGroup view = (ViewGroup) inflater.inflate(R.layout.license_fragment, 
           container, false);ButterKnife.inject(this, view);
    // Setting up Pull to Refresh
    swipeToRefreshLayout.setOnRefreshListener(this);
    // Indicator colors for refresh
    swipeToRefreshLayout.setColorSchemeResources(R.color.green, 
                R.color.light_green);
}

XML layout:

<android.support.v4.widget.SwipeRefreshLayout>

<ScrollView
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:paddingBottom="@dimen/activity_margin_vertical"
                    android:paddingTop="@dimen/activity_margin_vertical">

    <!-- Content -->
</ScrollView>

</android.support.v4.widget.SwipeRefreshLayout>

1

Oltre a Volodymyr Baydalka usa anche il seguente codice:

swipeContainer.post(new Runnable() {
        @Override
        public void run() {
            swipeContainer.setRefreshing(false);
        }
    });

Spiegazione Ho implementato la soluzione fornita da Volodymyr Baydalka (usando i frammenti) ma dopo l'avvio di SwipeReferesh non è mai andata via nemmeno su chiamata, swipeContainer.setRefreshing(false); quindi ho dovuto implementare il codice sopra indicato che ha risolto il mio problema. altre idee sul perché ciò accada sono le benvenute.

Saluti,

'Com.android.support:appcompat-v7:22.2.1'


1

@ niks.stack In risposta alla sua risposta, vorrei mostrare la ruota di avanzamento dopo che onLayout()è stata eseguita. Quando l'ho usato subito dopo onMeasure(), non avrebbe rispettato alcuni degli offset, ma lo ha onLayout()fatto dopo .

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    if (!mLaidOut) {
        mLaidOut = true;
        setRefreshing(mPreLayoutRefreshing);
    }
}

@Override
public void setRefreshing(boolean refreshing) {
    if (mLaidOut) {
        super.setRefreshing(refreshing);
    } else {
        mPreLayoutRefreshing = refreshing;
    }
}

Stavo cercando un buon callback dopo onMeasure! Thnx. Era una seccatura per me il fatto che durante la corsa iniziale l'offset fosse spento ...
xdbas

0

La mia soluzione (senza supporto v7) -

TypedValue typed_value = new TypedValue();
getTheme().resolveAttribute(android.R.attr.actionBarSize, typed_value, true);
swipeLayout.setProgressViewOffset(false, 0, getResources().getDimensionPixelSize(typed_value.resourceId));

if(!swipeLayout.isEnabled())
     swipeLayout.setEnabled(true);
swipeLayout.setRefreshing(true);

0

Sto usando 'com.android.support:appcompat-v7:23.1.1'

swipeRefreshLayout.post(new Runnable() {
        @Override
        public void run() {
            swipeRefreshLayout.setRefreshing(true);
            getData();
        }
    });

precedentemente stavo usando il metodo swipeRefreshLayout.setRefreshing(true);inside getData(), quindi non funzionava. Non so perché non funziona all'interno del metodo.

Anche se ho usato swipeRefreshLayout.setRefreshing(true);solo una volta nel mio frammento.


0

Prova questo

mSwipeRefreshLayout.setNestedScrollingEnabled (true);


-1

Un'altra soluzione alternativa è creare un nuovo controllo e derivare da SwipeRefreshLayout. Sostituisci la funzione OnMeasure e attiva nuovamente l'aggiornamento, se l'aggiornamento è attivato. Uso questa soluzione in un progetto Xamarin e funziona bene. Ecco alcuni esempi di codice C #:

class MySwipeRefreshLayout : SwipeRefreshLayout
{
    /// <summary>
    /// used to indentify, if measure was called for the first time
    /// </summary>
    private bool m_MeasureCalled;

    public MvxSwipeRefreshLayout(Context context, IAttributeSet attrs)
        : base(context, attrs)
    {
    }

    public MvxSwipeRefreshLayout(Context context)
        : base(context)
    {
    }

    public override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        base.OnMeasure(widthMeasureSpec, heightMeasureSpec);

        if (!m_MeasureCalled)
        {
            //change refreshing only one time
            m_MeasureCalled = true;

            if (Refreshing)
            {
                Refreshing = false;
                Refreshing = true;
            }
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.