在ViewPager中使用SwipeRefreshLayout呈现的列表的冻结快照 [英] Frozen snapshot of list rendered with SwipeRefreshLayout in ViewPager
问题描述
考虑一个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)
inonPause
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
不包含任何行)才有用.不需要的快照可能仍在第一页上呈现,但它是完全透明的. - 您可以在刷新动画时禁止分页.但是,这需要花费一些精力,因为您必须同时继承
ViewPager
和PagerTabStrip
的子类,并防止子类中出现适当的UI事件. - 当然,您可以完全避免在
ViewPager
中使用SwipeRefreshLayout
,但这似乎不是必需的.
- Clearing the data (so that the
ListView
contains no rows) inSwipeRefreshLayout.OnRefreshListener#onRefresh()
helps only if theListView
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
andPagerTabStrip
and prevent appropriate UI events in your subclasses. - Of course, you can avoid using
SwipeRefreshLayout
inViewPager
completely but this does not seem to be necessary.
这篇关于在ViewPager中使用SwipeRefreshLayout呈现的列表的冻结快照的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!