欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android 中ViewPager重排序与更新实例详解

程序员文章站 2023-08-25 12:00:03
android 中viewpager重排序与更新实例详解 最近的项目中有栏目订阅功能,在更改栏目顺序以后需要更新viewpager。类似于网易新闻的频道管理。 在...

android 中viewpager重排序与更新实例详解

最近的项目中有栏目订阅功能,在更改栏目顺序以后需要更新viewpager。类似于网易新闻的频道管理。

在重新排序之后调用了pageradapter的notifydatasetchanged方法,发现viewpager并没有更新,于是我开始跟踪源码,在调用pageradapter的notifydatasetchanged方法后,会触发viewpager的datasetchanged方法。

 void datasetchanged() {
    // this method only gets called if our observer is attached, so madapter is non-null.

    final int adaptercount = madapter.getcount();
    mexpectedadaptercount = adaptercount;
    boolean needpopulate = mitems.size() < moffscreenpagelimit * 2 + 1 &&
        mitems.size() < adaptercount;
    int newcurritem = mcuritem;

    boolean isupdating = false;
    for (int i = 0; i < mitems.size(); i++) {
      final iteminfo ii = mitems.get(i);
      final int newpos = madapter.getitemposition(ii.object);

      if (newpos == pageradapter.position_unchanged) {
        continue;
      }

      if (newpos == pageradapter.position_none) {
        mitems.remove(i);
        i--;

        if (!isupdating) {
          madapter.startupdate(this);
          isupdating = true;
        }

        madapter.destroyitem(this, ii.position, ii.object);
        needpopulate = true;

        if (mcuritem == ii.position) {
          // keep the current item in the valid range
          newcurritem = math.max(0, math.min(mcuritem, adaptercount - 1));
          needpopulate = true;
        }
        continue;
      }

      if (ii.position != newpos) {
        if (ii.position == mcuritem) {
          // our current item changed position. follow it.
          newcurritem = newpos;
        }

        ii.position = newpos;
        needpopulate = true;
      }
    }

    if (isupdating) {
      madapter.finishupdate(this);
    }

    collections.sort(mitems, comparator);

    if (needpopulate) {
      // reset our known page widths; populate will recompute them.
      final int childcount = getchildcount();
      for (int i = 0; i < childcount; i++) {
        final view child = getchildat(i);
        final layoutparams lp = (layoutparams) child.getlayoutparams();
        if (!lp.isdecor) {
          lp.widthfactor = 0.f;
        }
      }

      setcurrentiteminternal(newcurritem, false, true);
      requestlayout();
    }
  }

通过源码发现,在发生数据更新是,viewpager会调用adapter.getitemposition判断当前页是否发生变化,如果当前页没有变化则返回position_unchanged,如果当前页的顺序发生变化则返回新的索引,如果当前页不存在则返回position_none将会移除当前页并更新当前页。

接着查看viewpageradapter的getitemposition方法

 public int getitemposition(object object) {
    return position_unchanged;
  }

发现默认返回position_unchanged,这也是为什么我们的viewpager没有更新的原因,网上有多种解决方案,其中一种是直接重写getitemposition直接返回position_none。我也试着使用了,发现并没有什么用,数据还是没有更新,后来发现我的adapter继承的是fragmentpageradapter。而fragmentpageradapter自带了缓存策略,查看其instantiateitem方法。

 @override
  public object instantiateitem(viewgroup container, int position) {
    if (mcurtransaction == null) {
      mcurtransaction = mfragmentmanager.begintransaction();
    }

    final long itemid = getitemid(position);

    // do we already have this fragment?
    string name = makefragmentname(container.getid(), itemid);
    fragment fragment = mfragmentmanager.findfragmentbytag(name);
    if (fragment != null) {
      if (debug) log.v(tag, "attaching item #" + itemid + ": f=" + fragment);
      mcurtransaction.attach(fragment);
    } else {
      fragment = getitem(position);
      if (debug) log.v(tag, "adding item #" + itemid + ": f=" + fragment);
      mcurtransaction.add(container.getid(), fragment,
          makefragmentname(container.getid(), itemid));
    }
    if (fragment != mcurrentprimaryitem) {
      fragment.setmenuvisibility(false);
      fragment.setuservisiblehint(false);
    }

    return fragment;
  }

我们可以发现fragmentpageradapter通过其内部的fragmentmanager管理fragment缓存,而每一个fragment都是通过name来分别的,而name则由makefragmentname生成,我们查看makefragmentname方法

 private static string makefragmentname(int viewid, long id) {
    return "android:switcher:" + viewid + ":" + id;
  }

很简单拼接的字符串,一个是viewpager的id,一个是由getitemid方法生成,而getitemid方法更简单直接返回position,这也就是为什么我们不能更新数据的原因。

  /**
   * return a unique identifier for the item at the given position.
   *
   * <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;
  }

知道原因以后接着就开始改造adapter,首先为每一个频道生成唯一的id我的做法是使用一个map来保存,频道名称与id的对应关系,使用一个list来保存之前的position顺序,记得在notifydatasetchanged中初始化,由于list保存的是之前的position所以需要在完成更新后,再添加。

int id=1;
  map<string,integer> idsmap=new hashmap<>();
  list<string> preids=new arraylist<>();
 @override
  public void notifydatasetchanged() {
    for(menuinfo info:data){
      if(!idsmap.containskey(info.gettitle())){
        idsmap.put(info.gettitle(),id++);
      }
    }
    super.notifydatasetchanged();
    preids.clear();
    int size=getcount();
    for(int i=0;i<size;i++){
      preids.add((string) getpagetitle(i));
    }
  }

接着重写getitemposition

 @override
  public int getitemposition(object object) {
    itemfragment fragment= (itemfragment) object;
    string title=fragment.gettitle();
    int preid = preids.indexof(fragment.gettitle());
    int newid=-1;
    int i=0;
    int size=getcount();
    for(;i<size;i++){
      if(getpagetitle(i).equals(fragment.gettitle())){
        newid=i;
        break;
      }
    }
    if(newid!=-1&&newid==preid){
      log.i("zgh","title="+title+" position_unchanged");
      return position_unchanged;
    }
    if(newid!=-1){
      log.i("zgh","title="+title+" newid="+newid);
      return newid;
    }
    log.i("zgh","title="+title+" position_none");
    return position_none;
  }

还有getitemid

 @override
  public long getitemid(int position) {
    return idsmap.get(getpagetitle(position));
  }

完整的代码

package com.trs.xizang.gov.adapter;

import android.os.bundle;
import android.support.v4.app.fragment;
import android.support.v4.app.fragmentmanager;
import android.support.v4.app.fragmentpageradapter;
import android.util.log;
import android.view.viewgroup;

import com.trs.lib.base.trsurlfragment;
import com.trs.lib.bean.trsmenu;
import com.trs.lib.fragment.base.simpletitlefragment;
import com.trs.xizang.gov.bean.menuinfo;
import com.trs.xizang.gov.fragment.itemfragment;

import java.util.arraylist;
import java.util.hashmap;
import java.util.list;
import java.util.map;

/**
 * created by zhuguohui on 2016/5/12.
 */
public class menuinfopageadapter extends fragmentpageradapter {
  list<menuinfo> data;
  int id=1;
  map<string,integer> idsmap=new hashmap<>();
  list<string> preids=new arraylist<>();
  public menuinfopageadapter(fragmentmanager manager, list<menuinfo> data){
    super(manager);
    this.data= data==null? new arraylist<menuinfo>() :data;

  }

  @override
  public int getcount() {
    return data.size();
  }


  @override
  public fragment getitem(int position) {
    itemfragment fragment=new itemfragment();
    bundle bundle=new bundle();
    bundle.putstring(trsurlfragment.key_url,data.get(position).geturl());
    bundle.putstring(trsurlfragment.key_title, data.get(position).gettitle());
    fragment.setarguments(bundle);
    return fragment;
  }

  @override
  public charsequence getpagetitle(int position) {
    return data.get(position).gettitle();
  }

  @override
  public object instantiateitem(viewgroup container, int position) {
    return super.instantiateitem(container, position);
  }

  @override
  public long getitemid(int position) {
    return idsmap.get(getpagetitle(position));
  }

  @override
  public int getitemposition(object object) {
    itemfragment fragment= (itemfragment) object;
    string title=fragment.gettitle();
    int preid = preids.indexof(fragment.gettitle());
    int newid=-1;
    int i=0;
    int size=getcount();
    for(;i<size;i++){
      if(getpagetitle(i).equals(fragment.gettitle())){
        newid=i;
        break;
      }
    }
    if(newid!=-1&&newid==preid){
      log.i("zgh","title="+title+" position_unchanged");
      return position_unchanged;
    }
    if(newid!=-1){
      log.i("zgh","title="+title+" newid="+newid);
      return newid;
    }
    log.i("zgh","title="+title+" position_none");
    return position_none;
  }

  @override
  public void notifydatasetchanged() {
    for(menuinfo info:data){
      if(!idsmap.containskey(info.gettitle())){
        idsmap.put(info.gettitle(),id++);
      }
    }
    super.notifydatasetchanged();
    preids.clear();
    int size=getcount();
    for(int i=0;i<size;i++){
      preids.add((string) getpagetitle(i));
    }
  }
}

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!