Android 中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));
    }
  }
}

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

(0)

相关推荐

  • Android使用TabLayou+fragment+viewpager实现滑动切换页面效果

    TabLayou 主要实现的是标题头的 滑动 这个 控件 类似于 ScrollView XML中的布局 <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <android.support.design.widget.TabLayout a

  • Android之禁止ViewPager滑动实现实例

    Android之禁止ViewPager滑动实现实例 当我们想在同一个Activity或者Fragment中展示多个页面时往往会用到ViewPager,通过滑动,我们可以很方便地在不同的页面中切换.但是在某些情况下我们可能并不需要通过滑动来切换ViewPager中的页面(比如为了避免跟页面内的某些触摸事件冲突),而是希望只点击下面或者上面的按钮来切换页面.像知乎那样: 那么有什么方法可以实现不滑动ViewPager呢?其实很简单,只需要自定义一个不滑动的ViewPager就可以了.ViewPage

  • Android ViewPager撤消左右滑动切换功能实现代码

    最近做项目要求某种情况下ViewPager不能滑动,那么我们只需要重写这个方法就可以禁止ViewPager滑动.下面通过本文给大家ViewPager取消左右滑动切换功能的实例代码,具体代码如下所示: IndexViewPager.Java: <span style="background-color: rgb(255, 255, 255);">import android.content.Context; import android.support.v4.view.Vie

  • android 中viewpager+fragment仿微信底部TAG完美渐变

    viewpager+fragment仿微信底部TAG完美渐变,在图片渐变的同时字的颜色也在变,注意,是渐变哦! 效果图: activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:zhy="http://schemas.android.com/apk/res/com.Sing.weixin" xmlns:tools="

  • Android中DrawerLayout+ViewPager滑动冲突的解决方法

    DrawerLayout 是 Android 官方的侧滑菜单控件,而 ViewPager 相信大家都很熟悉了.今天这里就讲一下当在 DrawerLayout 中嵌套 ViewPager 时,要如何解决滑动冲突的问题,效果如下: 首先,让我们先来解决 DrawerLayout 和 ViewPager 的侧滑事件冲突.当 DrawerLayout 中嵌套 ViewPager 时,侧滑默认是执行 DrawerLayout 的侧滑事件,因为 Android 的事件分发是从 外层 ViewGroup 向里

  • Android解决viewpager嵌套滑动冲突并保留侧滑菜单功能

    重写子pagerview的dispatchTouchEvent方法,在返回前添加一句getParent().requestDisallowInterceptTouchEvent(true)中断掉事件的传递,类如下 public class SupperViewPager extends ViewPager { private int screenWidth;//屏幕宽度 public SupperViewPager(Context context) { super(context); } pub

  • Android 自定义布局竖向的ViewPager的实现

    Android 自定义布局竖向的ViewPager的实现 效果图: 这个自定义控件涉及到的知识点: 自定义ViewGroup中onMeasure和onLayout的写法 弹性滚动Scroller的用法 速度轨迹追踪器VelocityTracker的用法 如何处理滑动事件冲突 dispatchTouchEvent:(外部拦截)告诉此ScrollLayout的父布局,什么时候该拦截触摸事件,什么时候不该拦截触摸事件 onInterceptTouchEvent:(内部拦截)ScrollLayout告诉

  • Android中TabLayout+ViewPager实现tab和页面联动效果

    TabLayout+ViewPager实现tab和页面联动效果 xml中: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" an

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

    Android 中ViewPager重排序与更新实例详解 最近的项目中有栏目订阅功能,在更改栏目顺序以后需要更新ViewPager.类似于网易新闻的频道管理. 在重新排序之后调用了PagerAdapter的notifyDataSetChanged方法,发现ViewPager并没有更新,于是我开始跟踪源码,在调用PagerAdapter的notifyDataSetChanged方法后,会触发Viewpager的dataSetChanged方法. void dataSetChanged() { //

  • Android 中CheckBox的isChecked的使用实例详解

    Android 中CheckBox的isChecked的使用实例详解 范例说明 所有的网络服务在User使用之前,都需要签署同意条款,在手机应用程序.手机游戏的设计经验中,常看见CheckBox在同意条款情境的运用,其选取的状态有两种即isChecked=true与isChecked=false. 以下范例将设计一个TextView放入条款文字,在下方配置一个CheckBox Widget作为选取项,通过Button.onClickListener按钮事件处理,取得User同意条款的状态. 当C

  • Android中AlertDialog各种对话框的用法实例详解

    目标效果: 程序运行,显示图一的几个按钮,点击按钮分别显示图二到图六的对话框,点击对话框的某一项或者按钮,也会显示相应的吐司输出. 1.activity_main.xml页面存放五个按钮. activity_main.xml页面: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools&

  • Android 中SP与DP的区别实例详解

    从一开始写Android程序,就被告知这些常识 1.长度宽度的数值要使用dp作为单位放入dimens.xml文件中 2.字体大小的数值要使用sp作为单位,也放入dimens.xml文件中 然后,就没有然后了,仿佛潜台词就是说,你记住去用就行了. 偶然有一天,当我们阴差阳错地将字体写成了dp,也是可以工作,而且效果和sp一样. 这时候,就开始怀疑了,到底有啥区别呢,dp和sp有什么不同呢? 我们做个简单的Sample验证一下,如下,一个布局代码 <TextView android:layout_w

  • Android MotionEvent中getX()和getRawX()的区别实例详解

    Android MotionEvent中getX()和getRawX()的区别实例详解 实例代码: public class Res extends Activity implements View.OnTouchListener { Button btn = null; int x = 0; int y = 0; int rawx = 0; int rawy = 0; @Override public void onCreate(Bundle savedInstanceState) { sup

  • Android 中Crash时如何获取异常信息详解及实例

    Android 中Crash时如何获取异常信息详解 前言: 大家都知道,Android应用不可避免的会发生crash,无论你的程序写的多完美,总是无法完全避免crash的发生,可能是由于Android系统底层的bug,也可能是由于不充分的机型适配或者是糟糕的网络状况.当crash发生时,系统会kill掉你的程序,表现就是闪退或者程序已停止运行,这对用户来说是很不友好的,也是开发者所不愿意看到的,更糟糕的是,当用户发生了crash,开发者却无法得知程序为何crash,即便你想去解决这个crash,

  • Android源码中常用的接口传参实例详解

    Android源码中常用的接口传参实例详解 把MyCclass中的参数传到MyDclass /*接口传参例子2 * MyCclass.java发送MyDclass.java接收 * 原理和MyAclass.java发送MyDclass.java接收完全一样 * */ public class MyCclass { public void getEditext(GetMyFragmentData myFragmentData){ String edStr="人的生命是有限的,可是为人民服务是无限的

  • Android实现定时器的五种方法实例详解

    一.Timer Timer是Android直接启动定时器的类,TimerTask是一个子线程,方便处理一些比较复杂耗时的功能逻辑,经常与handler结合使用. 跟handler自身实现的定时器相比,Timer可以做一些复杂的处理,例如,需要对有大量对象的list进行排序,在TimerTask中执行不会阻塞子线程,常常与handler结合使用,在处理完复杂耗时的操作后,通过handler来更新UI界面. timer.schedule(task, delay,period); task: Time

  • Struts2中实现web应用的初始化实例详解

    Struts2中实现web应用的初始化实例详解 在JavsSE中,main方法为应用提供了入口,而在Android中,我们可以使用Application对于整个应用的生命周期进行管理,那么在基于Struts2的JavaEE应用中,如何实现类似的功能呢. 其中一种比较好的方式,是通过实现ServletContextListener接口进行坚挺,重写contextInitialized方法,实现自己需要进行的初始化操作,之后在web.xml中添加相应的listner,tomcat在启动服务时会调用相

  • Android 动态注册监听网络变化实例详解

    Android 动态注册监听网络变化实例详解 新建一个BroadcastTest项目,然后修改MainActivity中的代码,如下: public class MainActivity extends AppCompatActivity { private IntentFilter intentFilter; private NetworkChangeReceiver networkChangeReceiver; @Override protected void onCreate(Bundle

随机推荐