【问题标题】:Frozen snapshot of list rendered with SwipeRefreshLayout in ViewPager在 ViewPager 中使用 SwipeRefreshLayout 呈现的列表的冻结快照
【发布时间】:2016-03-18 14:44:14
【问题描述】:

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

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

刷新动画仍然可见。这对我来说似乎很奇怪,因为第 0 页被破坏并再次创建。此外,如果我向上滑动(尝试滚动到第 0 页上的列表末尾),页面上会呈现一些奇怪的图层,该图层似乎包含页面被销毁时的旧状态。

我不知道为什么会发生这种情况以及如何解决它。感谢您的帮助。

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

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

【问题讨论】:

  • 你在哪里初始化 SwipeToRefresh 布局
  • @Veeresh Charantimath 在这个最小的应用程序中,没有监听器被添加到 SwipeRefreshLayout。在我真正的更大的应用程序中,当然有这样的监听器,但无论如何都会发生错误。
  • 当需要停止滑动刷新时可以设置'swiperefreshLayout.setRefreshing(false)'。
  • @Ragu Swaminathan 调用swipeRefreshLayout.setRefreshing(false) 很好,如果真正的刷新操作(比如对服务器的 HTTP 请求)在用户滑动到下一页(比如从第 0 页到第 1 页)之前完成.但是,如果操作仍在进行中,我不应该致电 swipeRefreshLayout.setRefreshing(false)。如果用户在操作完成之前滑动到第 1 页,那么随后对 swipeRefreshLayout.setRefreshing(false) 的调用将不会阻止该错误。
  • 尝试清除列表,然后在滑动刷新时加载新数据

标签: android listview android-fragments android-viewpager swiperefreshlayout


【解决方案1】:

尝试清除列表,然后在滑动刷新时加载新数据。

如评论中所述。

【讨论】:

  • 顺便说一句。这似乎是 Android 支持库中的一个错误:code.google.com/p/android/issues/detail?id=194957
  • 好吧,这个解决方法只有在ListView 是透明的时候才有效。 SwipeRefreshLayout 在销毁之前拍摄一些快照,并且在重新创建该快照时将其呈现在页面上。仅当快照不包含任何单个不透明像素时,它才不会造成任何伤害。
  • 很抱歉将接受的答案更改为我自己的答案。但是,我提出了另一种对我来说似乎更好的解决方法。
【解决方案2】:

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

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

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

我还尝试了其他不起作用的解决方法:

  • 用 `FragmentPagerAdapter 替换 FragmentStatePagerAdapter 不会有帮助。
  • onPause 中调用mySwipeRefreshLayout.setRefreshing(false) 将无济于事。
  • 修改 FragmentStatePagerAdapter 使其不记住已损坏页面的状态将无济于事。

以下解决方法可能会有所帮助,但无论如何我更喜欢建议的 SwipeRefreshLayout 包装:

  • 仅当ListView 具有透明背景时,清除SwipeRefreshLayout.OnRefreshListener#onRefresh() 中的数据(使ListView 不包含行)才有帮助。不需要的快照可能仍会在第一页上呈现,但它是完全透明的。
  • 您可以在刷新动画时禁止分页。但是,这需要一些努力,因为您必须将 ViewPagerPagerTabStrip 子类化,并防止子类中出现适当的 UI 事件。
  • 当然,您可以完全避免在ViewPager 中使用SwipeRefreshLayout,但这似乎没有必要。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-06
  • 1970-01-01
  • 2015-09-22
相关资源
最近更新 更多