2

处理需要从viewpager动态添加和删除列表片段的应用程序,当点击列表中的一个项目时,新片段添加到viewpager,并且当需要删除向后滑动片段时,这很好用,但是当我旋转屏幕时,我得到双倍寻呼机中的片段实例,再次旋转它,它会使实例翻两番。

同时我需要保持片段的状态(列表位置、加载的项目数等),这意味着覆盖片段中的 onsaveinstancestate 并将相关数据保存在包中以在重新创建时恢复。我设法通过清除适配器并调用 notifydatasetchanged 解决了双实例的问题,但随后我丢失了片段中的保存状态,因为由于明显的原因没有调用 onsaveinstance,如果我不清除适配器,它只会使实例加倍。进入和离开文件夹时,我在 Dropbox 应用中看到了相同的行为。

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

 /**
 * 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,我用它来保存活动中的 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);
  }
}

活动中的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 应用中完成这项工作吗?

4

3 回答 3

1

您能否尝试在片段的 onCreateView 方法中使用 setRetainInstance(boolean retain) 。将其设置为true。它控制是否在 Activity 重新创建(例如从配置更改)中保留片段实例。

于 2017-01-17T17:05:40.070 回答
0

我已设法通过编辑此代码来解决此问题

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

问题是生成的 hashCode 在每次轮换时都不同,因为要求不改变页面的位置,所以我刚刚删除了这个方法。它现在按预期工作,您可以添加和删除片段并在方向更改时恢复状态。

于 2017-01-18T08:58:23.253 回答
0

看看这个资源:

viewPager 如何在方向更改时保留片段状态?

片段上的 ViewPager 中的片段不会在方向更改时重新加载

https://stackoverflow.com/a/27316052/2930101

在 ViewPager 中返回片段时,请先尝试在片段管理器中搜索片段。

如果您设法解决您的问题,我会对您的解决方案感兴趣!

于 2017-01-17T14:23:49.137 回答