当在刷新时使用SwipeRefreshLayout切换片段,片段会被冻结但实际上仍在工作。

75

更新:我以为它已经正确执行了,但在进行一些测试后发现问题仍然存在*哭泣*

于是我制作了一个更简单的版本来查看到底发生了什么,我知道应该被分离的刷新片段仍然留在那里。确切地说,旧片段的视图还留在那里,覆盖在较新的片段上面。由于我的原始应用程序的RecyclerView背景不透明,所以结果就像我之前说的那样。

更新结束


我有一个类似这样的布局MainActivity

<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_height="match_parent">

    <!-- As the main content view, the view below consumes the entire
         space available using match_parent in both dimensions. -->
    <FrameLayout android:id="@+id/container" android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- android:layout_gravity="start" tells DrawerLayout to treat
         this as a sliding drawer on the left side for left-to-right
         languages and on the right side for right-to-left languages.
         If you're not building against API 17 or higher, use
         android:layout_gravity="left" instead. -->
    <!-- The drawer is given a fixed width in dp and extends the full height of
         the container. -->
    <fragment android:id="@+id/navigation_drawer"
        android:layout_width="@dimen/navigation_drawer_width" android:layout_height="match_parent"
        android:layout_gravity="start" tools:layout="@layout/fragment_navigation_drawer" />

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

我使用的用于填充@id/container的片段ContentView配置如下:

<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contentView"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
    android:id="@+id/tweet_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/grey_300"/>

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

这里是ContentViewonCreateView()方法

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View inflatedView = inflater.inflate(R.layout.fragment_content, container, false);

    mRecyclerView = (RecyclerView) inflatedView.findViewById(R.id.tweet_list);
    mRecyclerView.setHasFixedSize(false);

    mLinearLayoutManager = new LinearLayoutManager(getActivity().getApplicationContext());
    mRecyclerView.setLayoutManager(mLinearLayoutManager);

    mTweetList = new ArrayList<Tweet>;
    mAdapter = new TweetAdapter(mTweetList);
    mRecyclerView.setAdapter(mAdapter);
    mRecyclerView.setItemAnimator(new DefaultItemAnimator());

    mSwipeRefreshLayout = (SwipeRefreshLayout) inflatedView.findViewById(R.id.contentView);
    mSwipeRefreshLayout.setOnRefreshListener(
        ...
    );
    return inflatedView;
}

然后在MainActivity中,我通过切换不同的ContentView来显示内容。一切看起来都很好,除了一个问题:当我在导航抽屉正在刷新时切换ContentView片段,内容会停止更新。但实际上所有的事情都像平常一样工作,只是你看不到它。


通常情况下,当您想要刷新时,如果您更改了整个列表,则在刷新结束时,它会尝试重新加载已更改的数据。在刷新时应锁定滑动操作。 - Sidd
好吧,看起来这是谷歌的问题... 升级到AppCompat v21.0.2后,所有的痛苦都消失了XD。无论如何还是谢谢<3。 - zlm2012
8个回答

106

好的... 经过一些挣扎,我最终通过一个巧妙的方法自己解决了这个问题...

我只需要在 onPause() 中添加以下内容:

@Override
public void onPause() {
    super.onPause();
    ...

    if (mSwipeRefreshLayout!=null) {
        mSwipeRefreshLayout.setRefreshing(false);
        mSwipeRefreshLayout.destroyDrawingCache();
        mSwipeRefreshLayout.clearAnimation();
    }
}

1
虽然我添加了这段代码,但它似乎没有起作用。我的支持库是22.0.0版本,实际上我发现在我的应用程序中冻结并没有消失。我尝试使用setTransition(),这也可以解决问题,但是这两个代码都不完全正确。如果你在滑动布局开始刷新时替换片段,你也会发现冻结内容,但一旦超过短时间,片段的替换就能正常工作了。我的最后一个解决方案是将滑动布局包装在一个FrameLayout中,这样你就不需要再添加其他代码了。 - cajsaiko
问题在使用support-v4:22.1.1时仍然存在,我在使用下拉刷新和滑动菜单时遇到了这个问题。我尝试在调用片段事务时将setRefreshing设置为false,但没有起作用。感谢您提供的解决方案。 - Ultimo_m
5
在appcompat v7:22+中,将SwipeRefreshLayout包裹在FrameLayout中可以解决问题。 - Rahul Rastogi
1
这里也可以工作:com.android.support:appcompat-v7:24.2.1 - Jeremy
2
谢谢。在FragmentTransaction期间,我只有setRefreshing(false)。添加了clearAnimation()后,我的应用程序可以正常运行而不会冻结。 - RobinBattle
显示剩余4条评论

77

在appcompat 22.1.1中似乎仍存在这个问题。将SwipeRefreshLayout包装在FrameLayout中对我很有帮助。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v4.widget.SwipeRefreshLayout

    android:id="@+id/contentView"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/tweet_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/grey_300" />

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

1
嗨@Pin,我认为这并不比其他解决方案更重。FrameLayout是最轻的ViewGroup元素,它不应该增加太多复杂性。 - andrea.rinaldi
1
这在 Fragment 中对我真的起作用了。哇。 - EpicPandaForce
2
谢谢!它解决了我的问题!我浪费了很多时间寻找原因和解决方案。这必须被接受为正确答案! - Trancer
3
在版本25.2.0中仍存在错误,但这个解决方案解决了问题。在找到这个解决方案之前,我想要翻转桌子。谢谢。 - Lukas Novak
这是适用于 appcompat-v7:24+ 的可行解决方案。我认为这应该是被接受的答案... 感谢 @user1354603。 - Kushal
显示剩余2条评论

3

除了 @zlm2012 的回答之外,我注意到这个问题在Support Library 21.0.0下得以复制,但现在在21.0.3版本中似乎已经解决了。


1
在22.2.1版本中仍然存在错误。 - wrozwad
3
我遇到了这个问题:使用compile 'com.android.support:appcompat-v7:25.1.0'时出现了一个bug。 - EpicPandaForce

1

这个解决方案有效,但我认为最好将这些代码行放入自己的函数中,例如:

public void terminateRefreshing() {
    mSwipeRefreshLayout.setRefreshing(false);
    mSwipeRefreshLayout.destroyDrawingCache();
    mSwipeRefreshLayout.clearAnimation();
}

在切换片段时调用。
Fragment prevFrag = fragmentManager.findFragmentById(drawerContainerId);

if(prevFrag instanceof SwipeRefreshFragment) {
    ((SwipeRefreshFragment)prevFrag).terminateRefreshing();
}

fragmentManager.beginTransaction().replace(drawerContainerId, fragment).commit();

1

它能够工作!只需从您的片段替换中删除转换即可,就像我的情况一样,我从代码中删除了以下内容:

.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)

1
在顶层父布局中设置 clickable="true" 可能会解决问题。

1
这个解决方案适用于点击事件,但对于导航抽屉滑动事件,这个解决方案不起作用。 - samit

0

目前,您可以通过以下方式避免此问题:

public class FixedRefreshLayout extends SwipeRefreshLayout {

    private static final String TAG = "RefreshTag";
    private boolean selfCancelled = false;

    public FixedRefreshLayout(Context context) {
        super(context);
    }

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

    @Override
    protected Parcelable onSaveInstanceState()
    {
        if(isRefreshing()) {
            clearAnimation();
            setRefreshing(false);
            selfCancelled = true;
            Log.d(TAG, "For hide refreshing");
        }
        return super.onSaveInstanceState();
    }

    @Override
    public void setRefreshing(boolean refreshing) {
        super.setRefreshing(refreshing);
        selfCancelled = false;
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        if(hasWindowFocus && selfCancelled) {
            setRefreshing(true);
            Log.d(TAG, "Force show refreshing");
        }
    }
}

这样做的额外好处是,如果您决定不切换片段(来自某个菜单),它可以恢复动画。它还与内部动画相结合,以确保在网络刷新已经完成的情况下,刷新动画不会返回。


0

当导航菜单项被点击时,终止SwipeRefresh。

它起作用了。

private void selectItem(int position) {

       mSwipeRefreshLayout.setRefreshing(false);

       /*
        Other part of the function
       */
}

我做了这个,但我还必须按照被接受的答案所说的去做,否则屏幕上会留下一堆从开始刷新的视图。 - EpicPandaForce

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