Frozen snapshot of list rendered with SwipeRefreshLayout in ViewPager

百般思念 提交于 2019-12-08 09:23:31

问题


Consider an Android activity that is subclass ofandroid.support.v4.app.FragmentActivity. It contains several apges managed by FragmentStatePagerAdapter. Each page contains a ListView wrapped by android.support.v4.widget.SwipeRefreshLayout. The following screenshot shows the initial state of this activity.

Now, I swipe down to refresh the list on page 0. The refresh animation appears. Now, let's swipe to the right on page 2 and then back to page 0.

The refresh animation is still visible. This seems strange to me as page 0 was destroyed and created again. Moreover, if I swipe up (trying to scroll to the end of the list on page 0) some strange layer is rendered over the page which seems to contain the old state of the page from the time it was destroyed.

I have no idea why this happens and how to fix it. Thanks for any help.

Here is the source code of a minimal application that consists of 3 layout files, an activity class, a fragment class, a manifest file and a build script.

swipeandroid/src/main/res/layout/service_schedule_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.view.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/servicePager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.PagerTabStrip
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="top"/>

</android.support.v4.view.ViewPager>

swipeandroid/src/main/res/layout/service_schedule_tab_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/serviceRefreshLayout"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/timeSlots"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

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

swipeandroid/src/main/res/layout/service_schedule_row.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/time"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"/>

</RelativeLayout>

swipeandroid/src/main/java/com/swipetest/ServiceScheduleActivity.java

package com.swipetest;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;

public class ServiceScheduleActivity extends FragmentActivity {
    @Override
    protected void onCreate(final Bundle inState) {
        // call super class
        super.onCreate(inState);

        // set content
        setContentView(R.layout.service_schedule_activity);

        // set up service pager
        ((ViewPager) findViewById(R.id.servicePager)).setAdapter(new ServiceFragmentPagerAdapter());
    }

    private class ServiceFragmentPagerAdapter extends FragmentStatePagerAdapter {
        public ServiceFragmentPagerAdapter() {
            super(getSupportFragmentManager());
        }

        @Override
        public int getCount() {
            return 5;
        }

        @Override
        public Fragment getItem(final int position) {
            return ServiceScheduleTabFragment.newInstance(position);
        }

        @Override
        public CharSequence getPageTitle(final int position) {
            return String.format("page %d", position);
        }
    }
}

swipeandroid/src/main/java/com/swipetest/ServiceScheduleTabFragment.java

package com.swipetest;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class ServiceScheduleTabFragment extends Fragment {
    private static final String TAB_POSITION_ARGUMENT = "tabPosition";

    public static ServiceScheduleTabFragment newInstance(final int tabPosition) {
        // prepare arguments
        final Bundle arguments = new Bundle();
        arguments.putInt(TAB_POSITION_ARGUMENT, tabPosition);

        // create fragment
        final ServiceScheduleTabFragment fragment = new ServiceScheduleTabFragment();
        fragment.setArguments(arguments);
        return fragment;
    }

    @Override
    public View onCreateView(final LayoutInflater layoutInflater, final ViewGroup parent, final Bundle inState) {
        // create fragment root view
        final View rootView = layoutInflater.inflate(R.layout.service_schedule_tab_fragment, parent, false);

        // initialize time slot list view
        final ListView timeSlotListView = (ListView) rootView.findViewById(R.id.timeSlots);
        timeSlotListView.setAdapter(new TimeSlotListAdapter());

        // return fragment root view
        return rootView;
    }

    private int getTabPosition() {
        return getArguments().getInt(TAB_POSITION_ARGUMENT);
    }

    private static class TimeSlotRowViewHolder {
        private final TextView timeView;

        public TimeSlotRowViewHolder(final View rowView) {
            timeView = (TextView) rowView.findViewById(R.id.time);
        }

        public TextView getTimeView() {
            return timeView;
        }
    }

    private class TimeSlotListAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return 80;
        }

        @Override
        public Object getItem(final int position) {
            return position;
        }

        @Override
        public long getItemId(final int position) {
            return position;
        }

        @Override
        public View getView(final int position, final View convertView, final ViewGroup parent) {
            // reuse or create row view
            final View rowView;
            if (convertView != null) {
                rowView = convertView;
            } else {
                rowView = LayoutInflater.from(getContext()).inflate(R.layout.service_schedule_row, parent, false);
                rowView.setTag(new TimeSlotRowViewHolder(rowView));
            }

            // fill data
            final TimeSlotRowViewHolder rowViewHolder = (TimeSlotRowViewHolder) rowView.getTag();
            rowViewHolder.getTimeView().setText(String.format("tab %d, row %d", getTabPosition(), position));
            return rowView;
        }
    }
}

swipeandroid/src/main/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.swipetest"
          android:versionCode="1"
          android:versionName="1.0">

    <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="23"/>

    <application
        android:name="android.app.Application"
        android:label="Swipe Test"
        android:allowBackup="false">

        <activity
            android:name="com.swipetest.ServiceScheduleActivity">

            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>

        </activity>

    </application>

</manifest>

swipeandroid/build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'
    }
}

apply plugin: 'com.android.application'

repositories {
    mavenCentral()
}

dependencies {
    compile "com.android.support:support-v4:23.1.1"
}

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"
    buildTypes {
        debug {
            debuggable true
            minifyEnabled false
        }
    }
}

回答1:


Try to clear the list and then load the new data on swipe refresh.

As mentioned in the comment.




回答2:


Finally, I have discovered a workaround that is very simple to implement and fixes the issue.

Just wrap the SwipeRefreshLayout e.g. by FrameLayout. It seems that the ViewPager does not like SwipeRefreshLayout being the root of the view hierarchy of a page fragment.

Anyway, this issue looks like a bug in Android Support Library. See https://code.google.com/p/android/issues/detail?id=194957

I have also tried other workarounds that do not work:

  • Replacing FragmentStatePagerAdapter by `FragmentPagerAdapter will not help.
  • Calling mySwipeRefreshLayout.setRefreshing(false) in onPause will not help.
  • Modifying the FragmentStatePagerAdapter so that it does not remeber states of destroyed pages will not help.

The following workarounds can help somehow but I prefer the suggested wrapping of SwipeRefreshLayout anyway:

  • Clearing the data (so that the ListView contains no rows) in SwipeRefreshLayout.OnRefreshListener#onRefresh() helps only if the ListView has transparent background. The unwanted snapshot is probably still rendered over the first page but it is fully transparent.
  • You can disallow paging for the time of refresh animation. However, this takes some effort as you must subclass both ViewPager and PagerTabStrip and prevent appropriate UI events in your subclasses.
  • Of course, you can avoid using SwipeRefreshLayout in ViewPager completely but this does not seem to be necessary.


来源:https://stackoverflow.com/questions/36087295/frozen-snapshot-of-list-rendered-with-swiperefreshlayout-in-viewpager

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!