SwipeRefreshLayout setRefreshing()在初始状态下不显示指示器

146

我的布局非常简单,但是当我在片段的 onActivityCreated() 中调用 setRefreshing(true) 时,它不会最初显示。

只有当我下拉刷新时才会显示。有什么想法为什么它最初不显示?

片段 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>

片段代码:

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

我确认这个问题从那个版本开始就发生了。早期的版本没有任何问题。 如果我得到解决方案,我会发布它。 - Ahmed Hegazy
让我试一下早期版本。 - thunderousNinja
对我来说,在v20上也无法工作。它在哪个版本上能够运行? - thunderousNinja
在刷新时,您只需显示SwipeRefreshLayout。只需在onRefresh()方法中添加mSwipeRefreshLayout.setRefreshing(true);即可。这是显示滑动视图的更好方式。 - Rahul Mandaliya
可能是setRefreshing(true) 不显示指示器的重复问题。 - Louis CAD
显示剩余6条评论
14个回答

310

面对同样的问题。我的解决方案 -

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

1
工作得很好,但我不知道为什么我们需要这样做,而不是 mSwipeRefreshLayout.setRefreshing(true); - Cocorico
http://www.google.com/design/spec/patterns/swipe-to-refresh.html#swipe-to-refresh-swipe-to-refresh - Anoop M Maddasseri
1
这不是一个好的解决方案/权宜之计。如果用户在飞行模式下,您的refreshLayout将在已经开始网络请求并接收到其响应(在此情况下为失败)后自行启动刷新线程。尝试停止refreshLayout以处理它是无效的,因为它还没有开始!换句话说,refreshLayout会在您收到响应后开始刷新。祝你好运停止它。 - Samer
1
据我记得,“posts”是同步的,并按添加顺序运行。因此,您可以添加另一篇文章来停止它。 - Volodymyr Baydalka
2
它的实现看起来可能最简单,但并不好。@niks.stack 的解决方案如下更好,因为它不需要对使用它的代码进行任何更改,所以当这个 bug 在支持库中被修复时,你只需切换回支持库 SwipeRefreshLayout。 - Marcin Orlowski
显示剩余5条评论

102

请查看Volodymyr Baydalka的回答。

这些是旧的解决方法。

这些方法曾经适用于早期版本的android.support.v4,但从版本21.0.0开始就不再适用,并且仍然存在于android.support.v4:21.0.3发布于2014年12月10日至12日,这就是原因。

当在SwipeRefreshLayout.onMeasure()之前调用setRefreshing(true)时,SwipeRefreshLayout指示器不会出现。

解决方法:

SwipeRefreshLayout上调用setProgressViewOffset(),使布局的圆形视图无效,从而立即调用SwipeRefreshLayout.onMeasure()

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

更新更好的解决方法

由于当屏幕方向改变或手动设置操作栏大小时,操作栏可能会变窄。我们将偏移量设置为从此视图顶部算起的像素,以便在成功滑动手势后将进度旋转器重置到当前操作栏大小。

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

2014年11月20日更新

如果您的应用程序在视图启动后不需要显示SwipeRefreshLayout,则可以使用处理程序或任何其他方式将其发布到未来的某个时间。

例如:

handler.postDelayed(new Runnable() {

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

或者如Volodymyr Baydalka的回答所提到的那样。

这里是安卓问题跟踪器中的issue。请投票支持,以向他们表明我们需要解决此问题。


你对“延迟时间”进行了多少测试?我在我的Galaxy S4上使用了500。不确定在另一台设备上是否会有问题。 - theblang
我没有对延迟时间进行太多测试,但我认为 500 应该可以胜任。我已经在模拟器上进行了测试。我只是想用 1000 毫秒来保险起见。 - Ahmed Hegazy
3
没必要延迟发布,你可以直接发布。没有延迟发布意味着“在你完成目前的工作后立即执行此操作”。目前正在进行的是测量和布局用户界面。请直接发布即可。 - Oren

49

我的解决方案是重写 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
您的解决方案的优点是保持视图偏移不变!这样我们就可以与此处指定的设计模式保持一致:http://www.google.com/design/spec/patterns/swipe-to-refresh.html#swipe-to-refresh-swipe-to-refresh。谢谢! - Igor de Lorenzi
你能解释一下这是如何工作的吗?它确实在运行,但我不太明白内部的运作方式。我是不是错过了什么微不足道的东西? - Sree
setRefreshing 只有在 onMeasure 调用后才起作用,因此我们存储本地刷新标志,并在第一次 onMeasure 调用时应用它。 - nikita.zhelonkin
5
我喜欢这个解决方案,因为它意味着调用SwipeRefreshLayout的代码可以完全按照我们想要的方式进行,没有任何复杂性。它基本上修复了SwipeRefreshLayout中的错误。 SwipeRefreshLayout确实应该以这种方式实现。 - DataGraham
这个答案可能看起来不是那么直接,但是它通过许多支持库版本(在我的情况下是23.1.1)很好地解决了问题。根据工单,这个问题在23.2版本中没有被修复。https://code.google.com/p/android/issues/detail?id=77712 - Robert
很棒的回答。子类化万岁!不用说,我们已经必须子类化来捕获另一个烦人的 bug - https://dev59.com/Keo6XIcBkEYKwwoYGwcw#31452997/ - Sufian

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

3
这个答案是唯一一个不是“hack”的答案,所以应该被接受。 - Jan Heinrich Reimer
1
只是一个小问题:.removeGlobalOnLayoutListener 应该是 .removeOnGlobalLayoutListener。 - mkuech
1
@mkeuch 这取决于你要使用哪个API。如果你的目标是API16以下,你需要进行API版本检查,并同时使用两个API。 - Marko
我比最受赞同的答案更喜欢这个,因为它更清楚地解释了为什么要使用它。 - Marcel Bro
我必须更正自己,这个解决方案并不总是适用于我。在某些情况下,在onGlobalLayoutListener()中包装调用setRefreshing(false)无法消除加载指示器。 - Marcel Bro

14

1
使用AndroidX库,问题仍然存在。 - Farid

4

根据Volodymyr Baydalka的回答,这只是一个小想法,可以帮助您保持代码清洁。我认为它值得一篇文章:一旦修复了错误,您将能够从发布转换为直接方法调用来恢复行为。

编写一个实用程序类,如下:

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

在您的代码中,将 mSwipeContainer.setRefreshing(isRefreshing)替换为 Utils.setRefreshing(mSwipeContainer, isRefreshing) :现在只有代码中的一个点需要在修复错误后更改,即 Utils 类。然后可以将该方法内联(并从 Utils 中删除)。
通常不会有明显的视觉差异。但请记住,挂起的刷新可能会保持旧的 Activity 实例,保留其视图层次结构中的 SwipeRefreshLayout 。如果这是一个问题,请调整方法以使用 WeakReference ,但通常您不会阻塞UI线程,因此仅延迟几毫秒的gc。

2
您可以在设置刷新之前调用此方法。
    swipeRefreshLayout.measure(View.MEASURED_SIZE_MASK,View.MEASURED_HEIGHT_STATE_SHIFT);
swipeRefreshLayout.setRefreshing(true);

它对我起作用了。


1
作为回应他的答案,我会在onLayout()完成后显示进度轮。当我直接在onMeasure()之后使用它时,它不会遵守一些偏移量,但是在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;
    }
}

我在寻找一个好的onMeasure回调函数!谢谢。初始运行时偏移量不准确真的很烦人... - xdbas

1
我使用了AppCompat库com.android.support:appcompat-v7:21.0.3,采用了您相同的方法,它可以正常工作。因此,您需要更新该库的版本。
建议: RelativeLayout不支持方向,这是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:

<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
除了使用Volodymyr Baydalka的代码之外,也可以使用以下代码:
swipeContainer.post(new Runnable() {
        @Override
        public void run() {
            swipeContainer.setRefreshing(false);
        }
    });

解释 我使用 Volodymyr Baydalka 提供的解决方案(使用碎片),但在启动 swipeReferesh 后,即使调用 swipeContainer.setRefreshing(false);,它也从未消失,所以不得不实现上面给出的代码来解决我的问题。 如果还有更多想法,请随时提出。

问候,

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接