更新FragmentStatePagerAdapter中的视图/幻灯片(片段)-简单的重新排序



我尝试过使用扩展的FragmentStatePagerAdapter和扩展的FragmentPagerAdapter来实现我所尝试的,似乎都没有工作。我从我的服务器发送了一个JSON对象来填充android客户端的对象模型。在第一次加载时,一切都很完美,我可以从头到尾滑动页面。这是我的对象模型:

ContestEntriesModel.java

public class ContestEntriesModel {
    public int ContestId;
    public String ContestName;
    public ContestEntryModel[] entries;
}
如你所见,这里有一个ContestEntryModel数组。每个模型都是这样的:

ContestEntryModel.java

public class ContestEntryModel {
    public long ContestEntryId;
    public int UserId;
    public String FullName;
    public int Comments;
    public string ImageUrl;
}

如上所述,当第一个数据有效负载从服务器下来时,我用来自该模型的数据(包括来自互联网资源(ImageUrl)的图像)填充了ContestEntryModel[]数组中的每个项目的一个片段,并且工作完美。我可以刷到手指流血,世界上一切都是美好的。

我现在正试图从服务器发送另一个有效载荷,其中数组中的项目以不同的顺序(我最终也会添加新项目,但一次一步)。我在StackOverflow上搜索了高低来解决这个问题,并在昨天发布了一个问题,这让我接近了我需要的地方(感谢Reinier),但我仍然在努力让我所有的碎片重新排序,并让适配器完全刷新新订单。适配器似乎缓存了一些幻灯片(与我当前正在查看的幻灯片相邻的幻灯片),并且它们不会"刷新",直到我滑过一些图像然后返回。昨天的问题是,当新数据从我正在工作的服务器上下来时,当前查看的幻灯片不会"刷新"……排序的。假设我的数组中有7个项目,我在第一次加载时滑动到幻灯片#3(索引2)。然后,我从服务器推送新数据,这只是对数组进行反向排序。当前查看的幻灯片(幻灯片#3)应该移动到索引位置4,我应该只能向右滑动两次(索引位置5和6)。相反,它会记住该幻灯片的当前索引位置(索引2)并保持在那里。正如我上面所说,幻灯片2(索引1)和幻灯片4(索引3)也被保留。我期望的行为是这些幻灯片应该在幻灯片#3(索引2)周围反转位置。

这是我的PagerAdapter的实现(这是一个版本,是一个FragmentStatePagerAdapter不是FragmentPagerAdapter:

ScreenSlidePagerAdapter:

public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
    public ContestEntriesModel entries;
    public ScreenSlidePagerAdapter(FragmentManager fm, ContestEntriesModel Entries) {
        super(fm);
        this.entries = Entries;
    }
    public long getItemId(int position) {
        return entries.entries[position].ContestEntryId;
    }
    @Override
    public int getItemPosition(Object object) {
        android.support.v4.app.Fragment f = (android.support.v4.app.Fragment)object;
        for(int i = 0; i < getCount(); i++){
            android.support.v4.app.Fragment fragment = getItem(i);
            if(f.equals(fragment)){
                //Log.d("currentPosition",Integer.toString(i));
                return i;
            }
        }
        return POSITION_NONE;
    }

    @Override
    public android.support.v4.app.Fragment getItem(int position) {
        long entryId = getItemId(position);
        //Log.d("EntryIds",Integer.toString(entryId));
        if(mItems.get(entryId) != null) {
            return mItems.get(entryId);
        }
        Fragment f = ScreenSlidePageFragment.create(position);
        mItems.put(entryId, f);
        return f;
    }
    @Override
    public int getCount() {
        return NUM_PAGES;
    }
}

目前,我正在为适配器的活动设置一个静态属性(我在其他几个"外部"类中使用它),如:

MainActivity:

    public class MainActivity extends FragmentActivity {
        public static int NUM_PAGES = 0;
        public static ViewPager mPager;
        public static PagerAdapter mPagerAdapter;
        public static ContestEntriesModel Entries;
        public static HashMap<Long, android.support.v4.app.Fragment> mItems = new HashMap<Long, android.support.v4.app.Fragment>();
        public static Long CurrentEntryId;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.entries_layout);
            GetEntriesTask task = new GetEntriesTask(43);
            task.execute();
        }

        public void LoadEntries()
        {
            mPager = (ViewPager) findViewById(R.id.pager);
            mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager(), Entries);
            mPager.setAdapter(mPagerAdapter);
            mPager.setPageTransformer(true, new MyPageTransformer());
            mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
                @Override
                public void onPageSelected(int position) {
                    ContestEntries.CurrentEntryId = ContestEntries.Entries.entries[position].ContestEntryId;
                    Log.d("currentEntry",Long.toString(ContestEntries.CurrentEntryId));
                    invalidateOptionsMenu();
                }
            });
        }
//the rest is omitted as it's not pertinent to my issue.

当一个新的数据负载从服务器下来时,我只是设置我的静态属性(Entries)为新版本。我还尝试访问PagerAdapter(条目)中的公共属性来直接修改,但由于某种原因无法访问(即使类和属性都有公共访问器)。我认为这就是问题所在,因为这是我需要修改的实例,但老实说,我的大脑在这一点上是油炸的,我只是不确定。以下是我用于更新的代码:

UpdateCode:

@Override
protected void onPostExecute(ContestEntriesModel entries) {
    Fragment currentFragment = MainActivity.mItems.get(MainActivity.CurrentEntryId);
    Log.d("fromUpdate",Long.toString(MainActivity.CurrentEntryId));
    MainActivity.Entries = entries;
    MainActivity.NUM_PAGES = MainActivity.Entries.entries.length;
    //MainActivity.mItems.clear();
    MainActivity.mPagerAdapter.notifyDataSetChanged();
    MainActivity.mPager.setCurrentItem(MainActivity.mPagerAdapter.getItemPosition(currentFragment));
}

因此使用它,它确实使我保持在当前幻灯片上(这是我需要的),但该幻灯片没有如上所述正确地重新定位,其他相邻的幻灯片也没有"刷新"。假设我在幻灯片3上,如果我向左滑动,旧的幻灯片2仍然在那里。如果我向左滑动两次,幻灯片#1就会更新为"新"幻灯片(第一次加载的幻灯片#7)。如果我再向右滑动一次,幻灯片2仍然在那里(应该是幻灯片6)。如果我再次向右滑动,我需要维护的"原始"幻灯片现在被幻灯片#5所取代(同样,这个数字来自原始加载)。如果我一直向右滑动到"新"幻灯片#5,我又能看到原来的幻灯片了。这就是它在新数据加载中需要的位置。

我真诚地为这一墙的文本道歉,但我想如果我能提供尽可能多的代码,这将更有意义。我读过几种不同的方法来解决这个问题,但我想不出哪一种适合我的需求。

任何建议/代码帮助将非常感激。

你的思路是对的。你的getItemPosition永远不会工作,因为它总是实例化一个新的Fragment对象。您需要维护自己的排序列表,并在对象视图仍处于正确位置时接收返回的POSITION_UNCHANGED。我将很快用一个代码示例更新我的答案。

如果所有的Fragments都属于同一个类,你可以直接用新数据更新Fragments,而不是重新排序它们。

MyFragment:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    DataProvider.getInstance().addOnDataChangedListener(this);
    if (mRoot == null) {
        mRoot = inflater.inflate(R.layout.entitlement, container, false);
    }
    updateDetails();
    return mRoot;
}
@Override
public void onDestroyView() {
    DataProvider.getInstance().removeOnDataChangeListener(this);
    super.onDestroyView();
}
public void dataChanged() {
    updateDetails();
}
@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    mContext = activity;
}
@Override
public void onDetach() {
    super.onDetach();
    mContext = null;
}

DataProvider只是在注册的监听器上调用dataChanged()。重要:如果您使用getActivity() getResource()或类似的updateDetails()这可能会崩溃,因为活动可能仍然是空的,尽管OnAttach()已被调用。为了避免这种情况,我使用我自己的上下文对象来做这些事情,它在OnAttach()中设置并在OnDetach()中删除。

我的适配器只负责页面标题,因为片段会自己刷新:

protected LinkedList<CharSequence> mPageTitles = new LinkedList<CharSequence>();
@Override
public CharSequence getPageTitle(int position) {
    try {
        return mPageTitles.get(position);
    } catch (Exception ex) {
        return null;
    }
}
protected final void setData(List data) {
    mPageTitles.clear();
    if (list != null) {
        for (int idx = 0; idx < list.size(); ++idx) {
            mPageTitles.add(DataProvider.getInstance().getPageTitle(list.get(idx)));
        }
    }
}
@Override
public void notifyDataSetChanged() {
    setData(DataProvider.getInstance().getData());
    super.notifyDataSetChanged();
}

当你更新你的DataProvider时,你也必须调用myAdapter.notifyDataSetChanged()

这可能不是最好的方法,但你可以试一试。这个解决方案实际上产生了一个小问题,因为它的目标是立即显示新数据。

如果你的currentSlide不应该更新,你应该在updateDetails() ':

中标记它并处理它
private boolean sticky = false;
public void setSticky() {
    sticky = true;
}
public void forceUpdate() {
    sticky = false;
    updateDetails();
}
protected void updateDetails() {
    if (sticky) {
        return;
    }
    // Do your update
}

最新更新