当前位置: 首页 > 知识库问答 >
问题:

在旋转时恢复viewpager中的片段及其状态

龚寂弦
2023-03-14

在需要动态添加和从viewpager中删除列表片段的应用程序中工作,当点击列表中的一个项目时,新的片段会添加到viewpager中,当需要删除swipe back片段时,这工作效果很好,但当我旋转屏幕时,页面中的片段会有两个实例,再旋转一次,实例会增加四倍。

同时,我需要持久化片段的状态(列表位置、加载的项数等),这意味着重写片段中的onsaveinstancestate并将相关数据保存在捆绑包中,以便在重新创建时恢复。我通过清除适配器并调用notifydatasetchanged来解决了双重实例的问题,但由于显而易见的原因没有调用onsaveinstance,因此我会丢失碎片中的保存状态,如果不清除适配器,则只会使实例加倍。我曾在Dropbox app中看到过进出文件夹时同样的行为。

这是我正在使用的自定义寻呼机适配器实现

 /**
 * Implementation of {@link PagerAdapter} that
 * uses a {@link Fragment} to manage each page. This class also handles
 * saving and restoring of fragment's state.
 *
 * <p>This version of the pager is more useful when there are a large number
 * of pages, working more like a list view.  When pages are not visible to
 * the user, their entire fragment may be destroyed, only keeping the saved
 * state of that fragment.  This allows the pager to hold on to much less
 * memory associated with each visited page as compared to
 * {@link FragmentPagerAdapter} at the cost of potentially more overhead when
 * switching between pages.
 *
 * <p>When using FragmentPagerAdapter the host ViewPager must have a
 * valid ID set.</p>
 *
 * <p>Subclasses only need to implement {@link #getItem(int)}
 * and {@link #getCount()} to have a working adapter. They also should
 * override {@link #getItemId(int)} if the position of the items can change.
 */
public abstract class UpdatableFragmentPagerAdapter extends PagerAdapter {

  private final FragmentManager fragmentManager;
  private final LongSparseArray<Fragment> fragmentList = new LongSparseArray<>();
  private final LongSparseArray<Fragment.SavedState> savedStatesList = new LongSparseArray<>();
  @Nullable private FragmentTransaction currentTransaction = null;
  @Nullable private Fragment currentPrimaryItem = null;

  public UpdatableFragmentPagerAdapter(@NonNull FragmentManager fm) {
    this.fragmentManager = fm;
  }

  /**
   * Return the Fragment associated with a specified position.
   */
  public abstract Fragment getItem(int position);

  @Override public void startUpdate(@NonNull ViewGroup container) {
    if (container.getId() == View.NO_ID) {
      throw new IllegalStateException("ViewPager with adapter " + this + " requires a view id");
    }
  }

  @Override @NonNull public Object instantiateItem(ViewGroup container, int position) {
    long tag = getItemId(position);
    Fragment fragment = fragmentList.get(tag);
    // If we already have this item instantiated, there is nothing
    // to do.  This can happen when we are restoring the entire pager
    // from its saved state, where the fragment manager has already
    // taken care of restoring the fragments we previously had instantiated.
    if (fragment != null) {
      return fragment;
    }

    if (currentTransaction == null) {
      currentTransaction = fragmentManager.beginTransaction();
    }

    fragment = getItem(position);
    // restore state
    final Fragment.SavedState savedState = savedStatesList.get(tag);
    if (savedState != null) {
      fragment.setInitialSavedState(savedState);
    }
    fragment.setMenuVisibility(false);
    fragment.setUserVisibleHint(false);
    fragmentList.put(tag, fragment);
    currentTransaction.add(container.getId(), fragment, "f" + tag);

    return fragment;
  }

  @Override public void destroyItem(ViewGroup container, int position, @NonNull Object object) {
    Fragment fragment = (Fragment) object;
    int currentPosition = getItemPosition(fragment);

    int index = fragmentList.indexOfValue(fragment);
    long fragmentKey = -1;
    if (index != -1) {
      fragmentKey = fragmentList.keyAt(index);
      fragmentList.removeAt(index);
    }

    //item hasn't been removed
    if (fragment.isAdded() && currentPosition != POSITION_NONE) {
      savedStatesList.put(fragmentKey, fragmentManager.saveFragmentInstanceState(fragment));
    } else {
      savedStatesList.remove(fragmentKey);
    }

    if (currentTransaction == null) {
      currentTransaction = fragmentManager.beginTransaction();
    }

    currentTransaction.remove(fragment);
  }

  @Override public void setPrimaryItem(ViewGroup container, int position, @Nullable Object object) {
    Fragment fragment = (Fragment) object;
    if (fragment != currentPrimaryItem) {
      if (currentPrimaryItem != null) {
        currentPrimaryItem.setMenuVisibility(false);
        currentPrimaryItem.setUserVisibleHint(false);
      }
      if (fragment != null) {
        fragment.setMenuVisibility(true);
        fragment.setUserVisibleHint(true);
      }
      currentPrimaryItem = fragment;
    }
  }

  @Override public void finishUpdate(ViewGroup container) {
    if (currentTransaction != null) {
      currentTransaction.commitNowAllowingStateLoss();
      currentTransaction = null;
    }
  }

  @Override public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
    return ((Fragment) object).getView() == view;
  }

  @Override public Parcelable saveState() {
    Bundle state = null;
    if (savedStatesList.size() > 0) {
      // save Fragment states
      state = new Bundle();
      long[] stateIds = new long[savedStatesList.size()];
      for (int i = 0; i < savedStatesList.size(); i++) {
        Fragment.SavedState entry = savedStatesList.valueAt(i);
        stateIds[i] = savedStatesList.keyAt(i);
        state.putParcelable(Long.toString(stateIds[i]), entry);
      }
      state.putLongArray("states", stateIds);
    }
    for (int i = 0; i < fragmentList.size(); i++) {
      Fragment f = fragmentList.valueAt(i);
      if (f != null && f.isAdded()) {
        if (state == null) {
          state = new Bundle();
        }
        String key = "f" + fragmentList.keyAt(i);
        fragmentManager.putFragment(state, key, f);
      }
    }
    return state;
  }

  @Override public void restoreState(@Nullable Parcelable state, ClassLoader loader) {
    if (state != null) {
      Bundle bundle = (Bundle) state;
      bundle.setClassLoader(loader);
      long[] fss = bundle.getLongArray("states");
      savedStatesList.clear();
      fragmentList.clear();
      if (fss != null) {
        for (long fs : fss) {
          savedStatesList.put(fs, bundle.getParcelable(Long.toString(fs)));
        }
      }
      Iterable<String> keys = bundle.keySet();
      for (String key : keys) {
        if (key.startsWith("f")) {
          Fragment f = fragmentManager.getFragment(bundle, key);
          if (f != null) {
            f.setMenuVisibility(false);
            fragmentList.put(Long.parseLong(key.substring(1)), f);
          } else {
            Timber.w("Bad fragment at key %s", key);
          }
        }
      }
    }
  }

  /**
   * Return a unique identifier for the item at the given position.
   * <p>
   * <p>The default implementation returns the given position.
   * Subclasses should override this method if the positions of items can change.</p>
   *
   * @param position Position within this adapter
   * @return Unique identifier for the item at position
   */
  public long getItemId(int position) {
    return position;
  }
}

这是适配器的实现

    class FolderPagerAdapter extends UpdatableFragmentPagerAdapter {

  private final FragmentManager fragmentManager;
  // Sparse array to keep track of registered fragments in memory
  private List<Fragment> addedFragments;

  FolderPagerAdapter(FragmentManager fm) {
    super(fm);
    this.fragmentManager = fm;
  }

  void init() {
    if (addedFragments == null) {
      addedFragments = new ArrayList<>();
    }
    addedFragments.clear();
    addedFragments.add(CollectionsListFragment.newInstance());
    notifyDataSetChanged();
  }

  @Override public Fragment getItem(int position) {
    return addedFragments.get(position);
  }

  @Override public long getItemId(int position) {
    return addedFragments.get(position).hashCode();
  }

  @Override public int getCount() {
    return addedFragments.size();
  }

  //this is called when notifyDataSetChanged() is called
  @Override public int getItemPosition(Object object) {
    //// refresh all fragments when data set changed
    Fragment fragment = (Fragment) object;
    if (fragment instanceof CollectionFragment) {
      return POSITION_UNCHANGED;
    } else {
      int hashCode = fragment.hashCode();
      for (int i = 0; i < addedFragments.size(); i++) {
        if (addedFragments.get(i).hashCode() == hashCode) {
          return i;
        }
      }
    }
    return PagerAdapter.POSITION_NONE;
  }

  void removeLastPage() {
    addedFragments.remove(addedFragments.size() - 1);
    notifyDataSetChanged();
  }

  void addCollectionFragment(CollectionFragment collectionFragment) {
    addedFragments.add(collectionFragment);
    notifyDataSetChanged();
  }

  void addFolderFragment(FolderFragment folderFragment) {
    addedFragments.add(folderFragment);
    notifyDataSetChanged();
  }

  void restoreFragments(List<PagerFolderCollectionModel> pagesList) {
    if (!pagesList.isEmpty()) {
      for (int i = 0; i < pagesList.size(); i++) {
        if (i == 0) {
          addedFragments.add(CollectionFragment.newInstance(pagesList.get(0).getItemId()));
        } else {
          addedFragments.add(FolderFragment.newInstance(pagesList.get(i).getItemName()));
        }
      }
      notifyDataSetChanged();
    }
  }

  void removeAll() {
    addedFragments.clear();
    notifyDataSetChanged();
  }
}

和一个持有者pojo,我正在使用它保存在activity的onsaveinstancestate中,并在循环时恢复

    public class PagerFolderCollectionModel implements Parcelable {

  public static final Parcelable.Creator<PagerFolderCollectionModel> CREATOR =
      new Parcelable.Creator<PagerFolderCollectionModel>() {
        @Override public PagerFolderCollectionModel createFromParcel(Parcel source) {
          return new PagerFolderCollectionModel(source);
        }

        @Override public PagerFolderCollectionModel[] newArray(int size) {
          return new PagerFolderCollectionModel[size];
        }
      };
  private String itemId;
  private String itemName;

  public PagerFolderCollectionModel(String itemId, String itemName) {
    this.itemId = itemId;
    this.itemName = itemName;
  }

  protected PagerFolderCollectionModel(Parcel in) {
    this.itemId = in.readString();
    this.itemName = in.readString();
  }

  public String getItemId() {
    return itemId;
  }

  public String getItemName() {
    return itemName;
  }

  @Override public int describeContents() {
    return 0;
  }

  @Override public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(this.itemId);
    dest.writeString(this.itemName);
  }
}

activity中的onsaveinstance方法

@Override protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(STATE_SELECTED_OPTION, selectedDrawerOption);
        outState.putBoolean(STATE_SHOW_GRID_OPTION, isShowGridOption);
        outState.putParcelableArrayList(STATE_SHOWN_FRAGMENTS,
            (ArrayList<PagerFolderCollectionModel>) adapteritemslist);
        Timber.e("save");
      }

要求是适配器中的第一项始终是集合片段,文件夹片段按需添加和删除(点击或向后滑动)

对此有解决方案吗(以不同的方式实现寻呼机适配器,在适配器中使用自定义视图...)?有没有人知道Dropbox应用程序是怎么做到这一点的?

共有1个答案

古凌
2023-03-14

请尝试在片段的onCreateView方法中使用setRetainInstance(布尔保留)。将其设置为true。它控制片断实例是否在activity重新创建过程中保留(例如从配置更改)。

 类似资料:
  • 我有个问题。我有一个片段,里面包含了viewpager。对于viewpager,我使用FragmentPagerAdapter扩展。每个寻呼机片段内部都有回收器视图。问题是,在

  • 我使用和来显示片段。最初增加了2个片段,Fragment1和Fragment2,在接收到服务器的响应后,在第二个位置增加了第三个片段Fragment3。因此,在添加所有页面之后,这应该是ViewPager中的片段序列-->Fragment1,Fragment3,fragment2。 下面是解决方案代码 代码:

  • 我试图探索Android框架是如何管理碎片的,通过我的研究,我了解了很多关于碎片的新知识,但我有一次陷入了困境,无法理解这是如何发生的。 请先试着理解我的场景。它是这样的:我有一个活动,一个接一个地添加两个片段。首次加载活动时,使用以下代码将片段A附加到该活动: 片段A的这些回调方法在加载时被调用 FirstDummyFragment:onCreate:savedInstanceState---

  • 让我们称它们为片段A和B。片段B只是A的一个详细视图,它在点击片段A中的按钮时替换了片段A。 更换代码: 当我现在旋转片段B中的屏幕一次并按下后,旧片段A会毫无问题地恢复(它在由savedInstanceState包创建的OnActivityCreate中恢复其状态)。 现在到了有趣的部分... 当我在片段B中多次旋转屏幕并按下Back键时,我得到一个NullPointerException,因为

  • 您好,我创建了一个带有默认“导航抽屉活动”的项目。所以我有一个MainActivity,其中有一个片段,用于替换菜单上的每个项目。 菜单之一是“客户”,显示客户列表。 从客户片段中我可以看到这个客户的利益,其中是一个调用利益的片段。 还有更多的层次,但要简短,这就足够了。 这是MainActivity上的代码,我使用它从片段中调用片段并在之间传递数据 我用的是: 问题是,当我旋转手机时,无论我的活

  • 我搜索了很多信息,但没有一个明确的。有人能给出一个明确的解决方案和一个例子吗?