在ViewPager中使用SwipeRefreshLayout呈现的列表的冻结快照 [英] Frozen snapshot of list rendered with SwipeRefreshLayout in ViewPager

查看:101
本文介绍了在ViewPager中使用SwipeRefreshLayout呈现的列表的冻结快照的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑一个Android活动,该活动是android.support.v4.app.FragmentActivity的子类.它包含由FragmentStatePagerAdapter管理的几个apges.每个页面包含由android.support.v4.widget.SwipeRefreshLayout包装的ListView.以下屏幕截图显示了此活动的初始状态.

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.

现在,我向下滑动以刷新页面0上的列表.出现刷新动画.现在,让我们在第2页上向右滑动,然后回到第0页.

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.

刷新动画仍然可见.这对我来说很奇怪,因为第0页被破坏并再次创建.此外,如果我向上滑动(尝试滚动到页面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.

这是一个最小应用程序的源代码,该应用程序由3个布局文件,一个活动类,一个片段类,一个清单文件和一个构建脚本组成.

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

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

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

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

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

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

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

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

推荐答案

最后,我发现了一种很容易实现并解决该问题的解决方法.

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

只需包装SwipeRefreshLayout,例如通过FrameLayout.似乎ViewPager不喜欢SwipeRefreshLayout作为页面片段的视图层次结构的根.

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.

无论如何,此问题看起来像是Android支持库中的错误.看 https://code.google.com/p/android/issues/detail ?id = 194957

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:

  • 用`FragmentPagerAdapter替换FragmentStatePagerAdapter将无济于事.
  • onPause中呼叫mySwipeRefreshLayout.setRefreshing(false)并没有帮助.
  • 修改FragmentStatePagerAdapter以便不改变被破坏页面的状态将无济于事.
  • 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.

以下变通办法可以以某种方式提供帮助,但无论如何,我还是建议使用SwipeRefreshLayout包装:

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

  • 仅当ListView具有透明背景时,清除SwipeRefreshLayout.OnRefreshListener#onRefresh()中的数据(以便ListView不包含任何行)才有用.不需要的快照可能仍在第一页上呈现,但它是完全透明的.
  • 您可以在刷新动画时禁止分页.但是,这需要花费一些精力,因为您必须同时继承ViewPagerPagerTabStrip的子类,并防止子类中出现适当的UI事件.
  • 当然,您可以完全避免在ViewPager中使用SwipeRefreshLayout,但这似乎不是必需的.
  • 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.

这篇关于在ViewPager中使用SwipeRefreshLayout呈现的列表的冻结快照的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆