SwipeRefreshLayout setRefreshing () n'affiche pas l'indicateur au départ

144

J'ai une disposition très simple , mais quand je l' appelle setRefreshing(true)dans onActivityCreated()mon fragment, il ne montre pas au départ.

Cela ne s'affiche que lorsque je fais une traction pour actualiser. Des idées pourquoi cela n'apparaît pas au départ?

Fragment 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>

Code de fragment:

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
    }
}
tonitruant Ninja
la source
Quelle version utilise-tu?
Ahmed Hegazy
compilez "com.android.support:appcompat-v7:21.0.0"
thunderousNinja
Je confirme que ce problème s'est produit avec moi à partir de cette version en cours. Les versions antérieures n'ont aucun problème avec cela. Je posterai la solution si j'en ai.
Ahmed Hegazy
Laissez-moi essayer une version antérieure
thunderousNinja
Ne fonctionne pas non plus sur la v20 pour moi. Sur quelle version fonctionne-t-il pour vous?
thunderousNinja

Réponses:

307

Face au même problème. Ma solution -

mSwipeRefreshLayout.post(new Runnable() {
    @Override
    public void run() {
        mSwipeRefreshLayout.setRefreshing(true);
    }
});
Volodymyr Baydalka
la source
1
Fonctionne bien mais je ne sais pas pourquoi nous devons faire cela au lieu de mSwipeRefreshLayout.setRefreshing (true);
Cocorico
1
Ce n'est pas une bonne solution / solution de contournement.Si l'utilisateur est en mode avion, votre refreshLayout commencerait à se rafraîchir sur son propre thread après avoir déjà lancé la requête réseau et reçu sa réponse (échec dans ce cas). En le manipulant pour arrêter le refreshLayout, cela ne fonctionnerait pas car il n'a pas encore démarré! En d'autres termes, refreshLayout commencerait à s'actualiser après avoir reçu votre réponse. Bonne chance pour l'arrêter
Samer
1
Si je me souviens bien, les "posts" sont synchronisés et s'exécutent dans l'ordre d'ajout. Vous pouvez donc ajouter un autre message pour l'arrêter.
Volodymyr Baydalka
2
Cela semble peut-être plus simple dans la mise en œuvre mais ce n'est pas agréable. La solution @ niks.stack ci - dessous est meilleure car ne nécessite aucune modification du code en l'utilisant, donc lorsque finalement ce bogue est corrigé dans le support lib, vous revenez simplement au support lib SwipeRefreshLayout
Marcin Orlowski
100

Voir plutôt la réponse de Volodymyr Baydalka.

Ce sont les anciennes solutions de contournement.

Cela sert à travailler sur la version antérieure du android.support.v4, mais de la version 21.0.0 en cours , il ne fonctionne pas et existe encore avec android.support.v4:21.0.3sortie au 10-12 Décembre, 2014 et c'est la raison.

L'indicateur SwipeRefreshLayout n'apparaît pas lorsque le setRefreshing(true)est appelé avant leSwipeRefreshLayout.onMeasure()

Solution de contournement:

appel setProgressViewOffset()sur le SwipeRefreshLayoutqui invalide la vue circulaire de la mise en page provoquant SwipeRefreshLayout.onMeasure()un appel immédiat.

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

MISE À JOUR Meilleure solution de contournement

Parce que la barre d'action peut devenir plus mince lorsque l'orientation change ou que vous avez défini la taille de la barre d'action manuellement. Nous définissons le décalage en pixels à partir du haut de cette vue à laquelle la flèche de progression doit se réinitialiser après un geste de balayage réussi à la taille actuelle de la barre d'action.

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));

MISE À JOUR 20 novembre 2014

S'il n'est pas très important pour votre application d'afficher le SwipeRefreshLayout une fois la vue démarrée. Vous pouvez simplement le publier à un moment dans le futur en utilisant des gestionnaires ou tout ce que vous voulez.

par exemple.

handler.postDelayed(new Runnable() {

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

ou comme la réponse de Volodymyr Baydalka l'a mentionnée.

Voici le problème dans le suivi des problèmes Android. Veuillez voter pour leur montrer que nous avons besoin de le corriger.

Ahmed Hegazy
la source
Avez-vous fait beaucoup de tests avec le delay time? J'utilise 500sur mon Galaxy S4. Je ne sais pas si cela poserait un problème sur un autre appareil.
theblang
Je n'ai pas fait beaucoup de tests avec le temps de retard, mais je pense 500que ça irait bien.Je l'ai testé sur le emulator.Je voulais juste être safeavec les 1000millisecondes
Ahmed Hegazy
3
Il n'est pas nécessaire de publier en retard. vous pouvez simplement poster. publier sans délai signifie simplement "faire cette chose une fois que vous avez terminé ce que vous faites maintenant". et ce qu'il fait maintenant, c'est mesurer et mettre en page votre interface utilisateur.
Oren
47

Ma solution est de remplacer 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;
        }
    }
}
nikita.zhelonkin
la source
3
L'avantage de votre solution est de garder le décalage de vue intact! De cette façon, nous continuons en conformité avec le modèle de conception spécifié ici: google.com/design/spec/patterns/… . Merci!
Igor de Lorenzi
Pouvez-vous expliquer comment cela fonctionne? Cela fonctionne mais je ne comprends pas tout à fait le fonctionnement interne. Suis-je juste en train de manquer quelque chose d'insignifiant?
Sree
setRefreshing ne fonctionne qu'après l'appel onMeasure, nous stockons donc l'indicateur de rafraîchissement local, et lors du premier appel onMeasure, appliquez-le
nikita.zhelonkin
5
J'adore cette solution car cela signifie que le code appelant le SwipeRefreshLayout peut être exactement comme nous le souhaitons, sans aucune complication. Il a essentiellement corrigé le bogue dans SwipeRefreshLayout. SwipeRefreshLayout devrait vraiment être implémenté de cette façon.
DataGraham
Cette réponse peut ne pas sembler si simple, mais elle résout bien le problème grâce à de nombreuses versions de bibliothèques de support (dans mon cas, c'est 23.1.1). Selon le ticket, ce problème n'est pas résolu @ 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);
                            }
                        });
Baoyz
la source
3
Cette réponse est la seule qui ne soit pas un hack, elle devrait donc être acceptée
Heinrich
1
Juste une petite chose: .removeGlobalOnLayoutListener devrait être .removeOnGlobalLayoutListener
mkuech
1
@mkeuch dépend de l'API de votre ciblage. Si votre ciblage est sous API16, vous devez vérifier la version de l'API et utiliser les deux.
Marko
J'aime celui-ci un peu plus que la réponse la plus votée car il est plus clair pourquoi elle est utilisée.
Marcel Bro
Je dois me corriger, cette solution ne fonctionne PAS toujours pour moi. Dans certains cas, l'appel setRefreshing(false)enveloppé onGlobalLayoutListener()ne supprime pas l'indicateur de chargement.
Marcel Bro
4

Basé sur la réponse de Volodymyr Baydalka , ce n'est qu'une petite idée qui vous aidera à garder le code propre. Cela mérite un post, je crois: vous pourrez facilement inverser le comportement de la publication à l'appel direct de méthode, une fois le bogue corrigé.

Écrivez une classe utilitaire comme:

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);
            }
        });
    }
}

Dans votre code, remplacez mSwipeContainer.setRefreshing(isRefreshing)par Utils.setRefreshing(mSwipeContainer, isRefreshing): maintenant, un seul point du code doit être modifié une fois le bogue corrigé, la Utilsclasse. La méthode peut également être insérée ensuite (et supprimée de Utils).

Il n'y aura généralement aucune différence visuelle notable. Gardez toutefois à l'esprit que l'actualisation en attente pourrait maintenir vos anciennes Activityinstances en vie, en conservant SwipeRefreshLayoutleurs hiérarchies de vues. Si c'est un problème, modifiez la méthode pour utiliser WeakReferences, mais normalement vous ne bloquez pas le thread de l'interface utilisateur de toute façon, et ne retardez donc gc que de quelques millisecondes.

Gil Vegliach
la source
2

Vous pouvez également appeler cette méthode avant setRefreshing ..

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

Cela fonctionne pour moi.

Leonardo Roese
la source
1

J'ai utilisé la bibliothèque AppCompat com.android.support:appcompat-v7:21.0.3 , en utilisant votre même approche et cela a fonctionné. Donc, vous mettez à jour la version de cette bibliothèque.

Conseil: RelativeLayoutne prend pas en charge l'orientation, c'est un attribut pour 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);
}

Mise en page XML:

<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>
Jesús Castro
la source
1

En plus de Volodymyr Baydalka, utilisez également le code suivant:

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

Explication J'ai implémenté la solution donnée par Volodymyr Baydalka (en utilisant des fragments) mais après le démarrage de swipeReferesh, il n'est jamais parti même en appelant swipeContainer.setRefreshing(false); donc j'ai dû implémenter le code donné ci-dessus qui a résolu mon problème. toutes les autres idées pourquoi cela se produit sont les bienvenues.

Cordialement,

"com.android.support:appcompat-v7:22.2.1"

Junaid
la source
1

@ niks.stack En réponse à sa réponse, je montrerais la roue de progression après onLayout()avoir été fait. Quand je l'ai utilisé directement après onMeasure(), il n'honorerait pas certains des décalages, mais l'utiliser après l'a onLayout()fait.

@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;
    }
}
mco
la source
Je cherchais un bon rappel après onMeasure! Thnx. Cela me dérangeait de savoir que lors de la première exécution, le décalage était désactivé ...
xdbas
0

Ma solution (sans support 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);
Artrmz
la source
0

J'utilise 'com.android.support:appcompat-v7:23.1.1'

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

auparavant j'utilisais à l' swipeRefreshLayout.setRefreshing(true);intérieurgetData() méthode , donc cela ne fonctionnait pas. Je ne sais pas pourquoi sa méthode ne fonctionne pas.

Bien que je n'ai utilisé swipeRefreshLayout.setRefreshing(true);qu'une seule fois dans mon Fragment.

Chinmay
la source
0

Essaye ça

mSwipeRefreshLayout.setNestedScrollingEnabled (true);

Ashwin H
la source
-1

Une autre solution de contournement consiste à créer un nouveau contrôle et à dériver de SwipeRefreshLayout. Remplacez la fonction OnMeasure et activez à nouveau l'actualisation, si l'actualisation est activée. J'utilise cette solution dans un projet xamarin et cela fonctionne bien. Voici quelques exemples de code 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;
            }
        }
    }
}
alchy
la source