thunderousNinja
thunderousNinja

Reputation: 3520

SwipeRefreshLayout setRefreshing() not showing indicator initially

I have a very simple layout but when I call setRefreshing(true) in onActivityCreated() of my fragment, it does not show initially.

It only shows when I do a pull to refresh. Any ideas why it isn't showing up initially?

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>

Fragment code:

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

Upvotes: 146

Views: 59565

Answers (14)

Ashwin H
Ashwin H

Reputation: 723

Try this

mSwipeRefreshLayout.setNestedScrollingEnabled(true);

Upvotes: 0

Andrei Buneyeu
Andrei Buneyeu

Reputation: 6680

With Android support library 24.2.0 the issue should have been solved! https://developer.android.com/topic/libraries/support-library/revisions.html#24-2-0-bugfixes

Upvotes: 14

nikita.zhelonkin
nikita.zhelonkin

Reputation: 637

My solution is to override 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;
        }
    }
}

Upvotes: 49

Ahmed Hegazy
Ahmed Hegazy

Reputation: 12615

See Volodymyr Baydalka's answer instead.

These are the old workarounds.

That used to work on earlier version of the android.support.v4, but from version 21.0.0 ongoing it doesn't work and still exists with android.support.v4:21.0.3 released at 10-12 December, 2014 and this is the reason.

SwipeRefreshLayout indicator does not appear when the setRefreshing(true) is called before the SwipeRefreshLayout.onMeasure()

Workaround:

calling setProgressViewOffset() on the SwipeRefreshLayout that invalidtes the circle view of the layout causing SwipeRefreshLayout.onMeasure() to be called immediately.

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

UPDATE Better Workaround

Because the actionbar could got thinner when orientation changes or you have set actionbar size manually. We set the offset in pixels from the top of this view at which the progress spinner should come to reset after a successful swipe gesture to the current actionbar size.

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 Nov 20 2014

If it's not very crucial to your app to show the SwipeRefreshLayout once the view is started. You can just post it to a time in the future by using handlers or any thing you want.

as an example.

handler.postDelayed(new Runnable() {

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

or as Volodymyr Baydalka's answer mentioned.

Here is the issue in the android issue tracker. Please upvote it to show them that we need it to be fixed.

Upvotes: 102

mco
mco

Reputation: 1839

@niks.stack In response to his answer, I would show the progress wheel after onLayout() has been done. When I used it directly after onMeasure(), it wouldn't honor some of the offsets, but using it after onLayout() did.

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

Upvotes: 1

Leonardo Roese
Leonardo Roese

Reputation: 94

Also you can call this method before setRefreshing..

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

It workef for me.

Upvotes: 2

Chinmay
Chinmay

Reputation: 1

Im using 'com.android.support:appcompat-v7:23.1.1'

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

previously I was using swipeRefreshLayout.setRefreshing(true); inside getData() method,so it wasn't working. I don't know why its not working inside method.

Although I used swipeRefreshLayout.setRefreshing(true); only once in my Fragment.

Upvotes: 0

Junaid
Junaid

Reputation: 4832

In addition to Volodymyr Baydalka use the following code as well:

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

Explanation I implemented the solution given by Volodymyr Baydalka (using fragments) but after the swipeReferesh started it never went away even on calling swipeContainer.setRefreshing(false); so had to implement the code given above which solved my problem. any more ideas why this happens are most welcome.

Regards,

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

Upvotes: 1

Jes&#250;s Castro
Jes&#250;s Castro

Reputation: 2119

I used AppCompat Library com.android.support:appcompat-v7:21.0.3, using your same approach and it worked. So, you update the version of that library.

Advice: RelativeLayout doesn't support orientation, it's an attribute for 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);
}

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

Upvotes: 1

Gil Vegliach
Gil Vegliach

Reputation: 3572

Based on Volodymyr Baydalka's answer, this is just a small idea that will help you to keep code clean. It deserves a post I believe: you will be able to revert the behavior easily from posting to direct method call, once the bug is fixed.

Write a utility class like:

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

In your code substitute mSwipeContainer.setRefreshing(isRefreshing) by Utils.setRefreshing(mSwipeContainer, isRefreshing): now only one point in the code needs to be changed once the bug is fixed, the Utils class. The method can also be inlined then (and removed from Utils).

There will be usually no noticeable visual difference. Keep in mind though that the pending refresh could keep alive your old Activity instances, holding on the SwipeRefreshLayout in their view hierarchies. If that is a concern, tweak the method to use WeakReferences, but normally you don't block the UI thread anyway, and thus delay gc by a couple of milliseconds only.

Upvotes: 4

baoyz
baoyz

Reputation: 358

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

Upvotes: 20

alchy
alchy

Reputation: 9

Another workaround is to create a new control and deriver from SwipeRefreshLayout. Override the OnMeasure function and toggle the refresh again, if refresh is activated. I use this solution in a xamarin project and it works well. Here some sample C#-Code:

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

Upvotes: -1

artur-dev
artur-dev

Reputation: 350

My solution (without 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);

Upvotes: 0

Volodymyr Baydalka
Volodymyr Baydalka

Reputation: 3645

Faced with same issue. My solution -

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

Upvotes: 310

Related Questions