SwipeRefreshLayout setRefreshing () początkowo nie wyświetla wskaźnika

144

Mam bardzo prosty układ, ale gdy zgłoszę setRefreshing(true)w onActivityCreated()mojej fragmentu, nie wykazuje początkowo.

Pokazuje się tylko wtedy, gdy pociągam, by odświeżyć. Jakieś pomysły, dlaczego nie pojawia się na początku?

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>

Kod fragmentu:

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
    }
}
thunderousNinja
źródło
Jakiej wersji używasz?
Ahmed Hegazy
kompiluj „com.android.support:appcompat-v7:21.0.0”
thunderousNinja
Potwierdzam, że ten problem wystąpił ze mną od tej wersji. Wcześniejsze wersje nie mają z tym żadnych problemów. Prześlę rozwiązanie, jeśli je otrzymam.
Ahmed Hegazy
Spróbuję wcześniejszej wersji
thunderousNinja
Dla mnie też nie działa na v20. W której wersji to działa?
thunderousNinja

Odpowiedzi:

307

W obliczu tego samego problemu. Moje rozwiązanie -

mSwipeRefreshLayout.post(new Runnable() {
    @Override
    public void run() {
        mSwipeRefreshLayout.setRefreshing(true);
    }
});
Volodymyr Baydalka
źródło
1
Działa dobrze, ale nie wiem, dlaczego musimy to zrobić zamiast mSwipeRefreshLayout.setRefreshing (true);
Cocorico
1
To nie jest dobre rozwiązanie / obejście. Jeśli użytkownik jest w trybie samolotowym, refreshLayout zacząłby odświeżać się we własnym wątku po uruchomieniu żądania sieciowego i otrzymaniu odpowiedzi (w tym przypadku niepowodzenie). Obsługa go w celu zatrzymania refreshLayout, nie zadziała, ponieważ jeszcze się nie uruchomiła! Innymi słowy, refreshLayout zacznie się odświeżać po otrzymaniu odpowiedzi. Powodzenia w zatrzymaniu tego
Samer
1
Jak pamiętam „posty” są synchronizowane i uruchamiane w kolejności dodawania. Możesz więc dodać kolejny post, aby to zatrzymać.
Volodymyr Baydalka
2
Być może wygląda to najłatwiej w realizacji, ale nie jest przyjemne. @ rozwiązanie niks.stack poniżej jest lepiej jak nie wymaga żadnych zmian w kodzie go używać, więc kiedy w końcu ten problem został rozwiązany w wsparcia lib, wystarczy przełączyć się z powrotem do wsparcia lib SwipeRefreshLayout
Marcin Orłowski
100

Zamiast tego zobacz odpowiedź Volodymyra Baydalki.

To są stare obejścia.

To działało wcześniej we wcześniejszej wersji android.support.v4, ale od wersji 21.0.0 w toku nie działa i nadal istnieje z android.support.v4:21.0.3wydaniem 10-12 grudnia 2014 i to jest powód.

Wskaźnik SwipeRefreshLayout nie pojawia się, gdy setRefreshing(true)jest wywoływana przedSwipeRefreshLayout.onMeasure()

Obejście:

dzwoniąc setProgressViewOffset()naSwipeRefreshLayout które invalidtes pogląd grono układ powodując SwipeRefreshLayout.onMeasure()na miano natychmiast.

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

AKTUALIZUJ Lepsze obejście

Ponieważ pasek akcji może stać się cieńszy w przypadku zmiany orientacji lub ręcznego ustawienia rozmiaru paska akcji. Ustawiliśmy przesunięcie w pikselach od góry tego widoku, przy którym pokrętło postępu powinno zresetować się po udanym geście przesunięcia do bieżącego rozmiaru paska akcji.

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

UPDATE 20 listopada 2014

Jeśli nie jest to bardzo ważne dla Twojej aplikacji, aby wyświetlić SwipeRefreshLayout po uruchomieniu widoku. Możesz po prostu opublikować to w przyszłości, używając programów obsługi lub dowolnej innej rzeczy.

jako przykład.

handler.postDelayed(new Runnable() {

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

lub jak wspomniano w odpowiedzi Wołodymyra Baydalki.

Oto problem w narzędziu do śledzenia problemów w systemie Android. Zagłosuj na to, aby pokazać im, że musimy to naprawić.

Ahmed Hegazy
źródło
Czy wykonałeś wiele testów z delay time? Używam 500na moim Galaxy S4. Nie jestem pewien, czy byłby to problem na innym urządzeniu.
theblang
Nie przeprowadzałem zbyt wielu testów z czasem opóźnienia, ale myślę, że 500byłoby dobrze. Przetestowałem to na. Po emulatorprostu chciałem być safez 1000milisekundami
Ahmed Hegazy,
3
Nie ma potrzeby wysyłania z opóźnieniem. możesz po prostu opublikować. wysyłanie postów bez opóźnienia oznacza po prostu „zrób to, gdy skończysz z tym, co robisz teraz”. a to, co teraz robi, mierzy i układa Twój interfejs użytkownika.
Oren
47

Moim rozwiązaniem jest zastąpienie 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
źródło
3
Zaletą Twojego rozwiązania jest zachowanie niezmienionego przesunięcia widoku! W ten sposób kontynuujemy zgodnie ze wzorcem projektowym określonym tutaj: google.com/design/spec/patterns/… . Dzięki!
Igor de Lorenzi
Czy możesz wyjaśnić, jak to działa? To działa, ale nie do końca rozumiem wewnętrzne działanie. Czy po prostu brakuje mi czegoś trywialnego?
Sree
setRefreshing prace dopiero po onMeasure rozmowy, więc przechowywać lokalną flagę orzeźwiający i na pierwsze wezwanie onMeasure zastosować go
nikita.zhelonkin
5
Uwielbiam to rozwiązanie, ponieważ oznacza to, że kod wywołujący SwipeRefreshLayout może być dokładnie taki, jak tego chcemy, bez żadnych komplikacji. Zasadniczo naprawił błąd w SwipeRefreshLayout. SwipeRefreshLayout naprawdę powinno być zaimplementowane w ten sposób.
DataGraham
Ta odpowiedź może nie wyglądać tak prosto, ale ładnie rozwiązuje problem dzięki wielu wersjom bibliotek wsparcia (w moim przypadku jest to 23.1.1). Zgodnie z biletem problem ten nie został rozwiązany @ 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
źródło
3
Ta odpowiedź jest jedyną, która nie jest hackiem, więc należy ją zaakceptować
Heinrich
1
Tylko mała rzecz: .removeGlobalOnLayoutListener powinno być .removeOnGlobalLayoutListener
mkuech
1
@mkeuch zależy od tego, który interfejs API jest przeznaczony dla Ciebie. Jeśli celujesz w API16, musisz sprawdzić wersję API i użyć obu.
Marko,
Ta bardziej mi się podoba niż najbardziej pozytywna odpowiedź, ponieważ jest bardziej jasne, dlaczego jest używana.
Marcel Bro
Muszę się poprawić, to rozwiązanie NIE zawsze działa u mnie. W niektórych przypadkach setRefreshing(false)zawinięte wywołanie onGlobalLayoutListener()nie wyłącza wskaźnika obciążenia.
Marcel Bro
4

Na podstawie odpowiedzi Wołodymyra Baydalki , jest to tylko mały pomysł, który pomoże Ci zachować czystość kodu. Wierzę, że zasługuje na post: będziesz w stanie łatwo zmienić zachowanie z wysyłania do bezpośredniego wywołania metody, gdy błąd zostanie naprawiony.

Napisz klasę użytkową taką jak:

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

W swoim kodzie zamień mSwipeContainer.setRefreshing(isRefreshing)na Utils.setRefreshing(mSwipeContainer, isRefreshing): teraz tylko jeden punkt w kodzie musi zostać zmieniony po naprawieniu błędu, Utilsklasa. Metodę można również wstawić (i usunąć z Utils).

Zwykle nie będzie zauważalnej różnicy wizualnej. Należy jednak pamiętać, że oczekujące odświeżenie może utrzymać stare Activityinstancje przy życiu , utrzymując SwipeRefreshLayoutw ich hierarchiach widoku. Jeśli jest to problem, dostosuj metodę, aby użyć WeakReferences, ale normalnie i tak nie blokujesz wątku interfejsu użytkownika, a tym samym opóźniasz gc tylko o kilka milisekund.

Gil Vegliach
źródło
2

Możesz również wywołać tę metodę przed setRefreshing.

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

To działa dla mnie.

Leonardo Roese
źródło
1

Użyłem biblioteki AppCompat com.android.support:appcompat-v7:21.0.3, używając tego samego podejścia i zadziałało. Tak więc aktualizujesz wersję tej biblioteki.

Rada: RelativeLayoutnie obsługuje orientacji, jest atrybutem dla 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);
}

Układ 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
źródło
1

Oprócz Volodymyra Baydalki użyj również następującego kodu:

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

Wyjaśnienie Zaimplementowałem rozwiązanie podane przez Volodymyra Baydalkę (używając fragmentów), ale po uruchomieniu swipeReferesha nigdy nie zniknęło, nawet po wywołaniu, swipeContainer.setRefreshing(false); więc musiałem zaimplementować powyższy kod, który rozwiązał mój problem. wszelkie pomysły, dlaczego tak się dzieje, są mile widziane.

Pozdrowienia,

„com.android.support:appcompat-v7:22.2.1”

Junaid
źródło
1

@ niks.stack W odpowiedzi na jego odpowiedź, pokażę koło postępu po zakończeniu onLayout(). Kiedy użyłem go bezpośrednio po onMeasure(), nie uszanowałoby to niektórych przesunięć, ale użycie go później onLayout().

@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
źródło
Szukałem dobrego oddzwonienia po onMeasure! Thnx.
Denerwowało
0

Moje rozwiązanie (bez wsparcia 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
źródło
0

Używam „com.android.support:appcompat-v7:23.1.1”

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

poprzednio swipeRefreshLayout.setRefreshing(true);używałem getData()metody inside , więc nie działała. Nie wiem, dlaczego nie działa w metodzie.

Chociaż użyłem swipeRefreshLayout.setRefreshing(true);tylko raz w moim fragmencie.

Chinmay
źródło
0

Spróbuj tego

mSwipeRefreshLayout.setNestedScrollingEnabled (true);

Ashwin H.
źródło
-1

Innym obejściem jest utworzenie nowej kontrolki i pochodnej z SwipeRefreshLayout. Zastąp funkcję OnMeasure i ponownie przełącz odświeżanie, jeśli odświeżanie jest aktywowane. Używam tego rozwiązania w projekcie Xamarin i działa dobrze. Oto przykładowy kod 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
źródło