ViewPager动态添加删除及刷新页面

  1. 利用ViewPage的PagerTransformer定制页面切换效果
  2. ViewPager动态添加删除及刷新页面
  3. ViewPager打造真正意义的无限轮播
  4. ViewPage 联动效果自带角标
  5. ViewPager禁止滑动和修改滑动速度

文章目录

  • 1. 前言
  • 2. PagerAdapter 的刷新
    • 2.1 源码解析
    • 2.2 例子
  • 3. FragmentPagerAdapter的刷新
    • 3.1 源码解析
    • 3.2 参考代码

1. 前言

      在此之前,我总是不得其解,同样都提供了**notifyDataSetChanged()**方法,为什么 ListView 的adapter使用刷新的方法非常好用,而 ViewPager 的adapter使用刷新方法总是有这样那样的问题?百度了一下,查阅了很多篇文章,难以找到满意的解决方案。尤其涉及到ViewPager动态添加删除及刷新页面时,刷新成了难以克服的痛点。有的说ViewPager不能刷新,有的说需要重写这个方法,有的说需要重写那个方法。

      为了搞清楚ViewPager的刷新问题,我参考多篇博客,又研读了源码,费了不少脑力。最终,这个问题还是被我搞定了。


2. PagerAdapter 的刷新

2.1 源码解析

      要想真正的理解PagerAdapter的刷新,就一定要从源码找突破口。下面是PagerAdapter的类注释。

/**
 * ......
 *
 * 

PagerAdapter supports data set changes. Data set changes must occur on the * main thread and must end with a call to {@link #notifyDataSetChanged()} similar * to AdapterView adapters derived from {@link android.widget.BaseAdapter}. A data * set change may involve pages being added, removed, or changing position. The * ViewPager will keep the current page active provided the adapter implements * the method {@link #getItemPosition(Object)}.

*/ public abstract class PagerAdapter { //...... public static final int POSITION_UNCHANGED = -1; public static final int POSITION_NONE = -2; //...... }

看PagerAdapter的最后一段,大概翻译一下,意思是:
      PagerAdapter支持数据集改变。数据集的改变必须发生在主线程,并且以调用notifyDataSetChanged()方法结束,类似于AdapterView的适配器(继承自android.widget.BaseAdapter的)。数据集改变包括页面被添加、删除或位置改变。如果适配器实现了方法getItemPosition(Object),ViewPager将保持当前页面处于活动状态。

      这段话有一个非常明确的信息:PagerAdapter可以通过调用notifyDataSetChanged()方法实现数据集的刷新。这就打破了有些人认为的ViewPager无法通过notifyDataSetChanged()刷新的认知。而且,这段话还告诉我们,我们可能需要实现方法getItemPosition(Object)。下面我面找到该方法的源码看一下:

 /**
    * Called when the host view is attempting to determine if an item's position
    * has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given
    * item has not changed or {@link #POSITION_NONE} if the item is no longer present
    * in the adapter.
    *
    * 

The default implementation assumes that items will never * change position and always returns {@link #POSITION_UNCHANGED}. * * @param object Object representing an item, previously returned by a call to * {@link #instantiateItem(View, int)}. * @return object's new position index from [0, {@link #getCount()}), * {@link #POSITION_UNCHANGED} if the object's position has not changed, * or {@link #POSITION_NONE} if the item is no longer present. */ public int getItemPosition(Object object) { return POSITION_UNCHANGED; }

需要看一下方法注释,大概意思是:
      当主控件(个人认为是ViewPager)尝试确定item的位置是否已经改变时调用。如果给定的item位置没有改变,返回POSITION_UNCHANGED;如果item已经在适配器中不存在了,返回POSITION_NONE

      默认实现假设所有item永远不会改变位置并且总是返回POSITION_UNCHANGED(未改变)。

      参数:object     代表一个item的对象(个人认为:View或Fragment),最初由instantiateItem(View, int) 方法返回。
      返回      如果位置发生改变,返回对象的新位置,是0到getCount()的索引(不包括getCount());如果位置没有改变,返回 POSITION_UNCHANGED ;如果item不存在了,返回 POSITION_NONE

2.2 例子

@Override
public int getItemPosition(@NonNull Object object) {
	if (mDataList.contains(object)) {
		return mDataList.indexOf(object);
    } else {
		return POSITION_NONE;
	}
}

注意:getItemPosition 的参数 object 与 adapter 的初始化方法:public Object instantiateItem(@NonNull ViewGroup container, int position) 的返回值有关,两者是对应关系。

其实这几个方法 isViewFromObject(view, object)getItemPosition(object)getItemPosition(object) 的 object 都与
instantiateItem(container, position) 的返回值一致。

可参考 ViewPager打造真正意义的无限轮播


3. FragmentPagerAdapter的刷新

3.1 源码解析

由于FragmentPagerAdapter继承了PagerAdapter,那么是不是FragmentPagerAdapter只要重写了方法getItemPosition()就可以实现自己的刷新了?事实并非如此,即使我们重写了方法getItemPosition(),用FragmentPagerAdapter刷新界面时依然会有问题。查找博客,翻看源码,发现我们还需要重写一个FragmentPagerAdapter的方法getItemId()。下面是FragmentPagerAdaptergetItemId()方法的源码:

    /**
     * Return a unique identifier for the item at the given position.
     *
     * 

The default implementation returns the given position. * Subclasses should override this method if the positions of items can change.

* * @param position Position within this adapter * @return Unique identifier for the item at position */
public long getItemId(int position) { return position; }

从方法描述我们可以看出:这个方法为给定position的item,返回一个唯一的id(identifier)。默认情况下,返回这个item的position。如果item的position可能发生改变,子类应该重写这个方法。

说的很清楚,如果ViewPager的item的position可能发生改变,子类应该重写这个方法。item的position发生改变的情况,恐怕只有添加,修改(互换位置),删除了。总之只要position可能发生改变,我们就需要重写这个方法。

所以,使用FragmentPagerAdapter除了需要重写其父类PagerAdaptergetItemPosition()方法,还需要重写getItemId()方法。

为什么FragmentPagerAdapter需要重写getItemId()方法呢?看一下FragmentPagerAdapter初始化item的方法:

@SuppressWarnings("ReferenceEquality")
@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;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
            + " v=" + ((Fragment)object).getView());
    mCurTransaction.detach((Fragment)object);
}

此方法中使用到了getItemId()方法,用于方法makeFragmentName()生成Fragment的tag。FragmentMaganger会通过这个tag查找和添加Fragment

// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));

我们都知道,ViewPager有预加载前一页和后一页的功能,预加载的页面(Fragment)会添加到FragmentPagerAdapterFragmentManager中,其中的Fragment应当与它的tag一一对应,ViewPager切换到position页,就会把对应的Fragment attach到mCurTransaction中。在destroyItem()方法中会移除超出界限的Fragment。

当所有item的position不发生改变时,通过getItemId()可以获得对应的Fragment
当我们的position发生改变时,同一个position,上面代码中查找的Fragment将不再是原来的Fragment (tag改变了),刷新界面就会出现错乱或报错。

所以,只要我们需要保证,也就是一个position对应一个id,通过position能获得原来的Fragment。

3.2 参考代码


import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.view.ViewGroup;

import com.zhxg.androidtv.fragment.yqfbindex.CommonPageFragment;

import java.util.ArrayList;
import java.util.List;

/**
 * 用于动态改变页面(新增页面和删除页面)的FragmentPagerAdapter
 * Created by wangzhengyang on 2017-12-27 0027.
 */

public class CommonPageAdapter extends FragmentPagerAdapter {

    private List<Fragment> mFragmentList = new ArrayList<>();
    private List<Integer> mItemIdList = new ArrayList<>();
    private int id = 0;
    private FragmentManager mFm;

    public CommonPageAdapter(FragmentManager fm, @NonNull List<Fragment> fragmentList) {
        super(fm);
        this.mFm = fm;
        for (Fragment fragment : fragmentList) {
            this.mFragmentList.add(fragment);
            mItemIdList.add(id++);
        }

    }

    public CommonPageAdapter(FragmentManager fm) {
        super(fm);
    }

    public List<Fragment> getFragmentList() {
        return mFragmentList;
    }

    public void addPage(int index, Fragment fragment){
        mFragmentList.add(index, fragment);
        mItemIdList.add(index, id++);
        notifyDataSetChanged();
    }
    
    public void addPage(Fragment fragment){
        mFragmentList.add(fragment);
        mItemIdList.add(id++);
        notifyDataSetChanged();
    }

    public void delPage(int index) {
        mFragmentList.remove(index);
        mItemIdList.remove(index);
        notifyDataSetChanged();
    }
    
    public void delPage() {
        mFragmentList.remove();
        mItemIdList.remove();
        notifyDataSetChanged();
    }

    public void updatePage(List<Fragment> fragmentList) {
        mFragmentList.clear();
        mItemIdList.clear();
        
        for(int i = 0; i < fragmentList.size(); i++){
            mFragmentList.add(fragmentList.get(i));
            mItemIdList.add(id++);//注意这里是id++,不是i++。
		}
        notifyDataSetChanged();
    }

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

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

	/**
	 * 返回值有三种,
	 * POSITION_UNCHANGED  默认值,位置没有改变
	 * POSITION_NONE       item已经不存在
	 * position            item新的位置
	 * 当position发生改变时这个方法应该返回改变后的位置,以便页面刷新。
	 */
    @Override
    public int getItemPosition(Object object) {
        if (object instanceof Fragment) {

            if (mFragmentList.contains(object)) {
                return mFragmentList.indexOf(object);
            } else {
                return POSITION_NONE;
            }

        }
        return super.getItemPosition(object);
    }

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

还可以利用Fragment对象的hashCode,不用维护一个idList,前提是没有重写Fragment的hashCode。代码如下:

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;

import java.util.ArrayList;
import java.util.List;

/**
 * 用于动态改变页面(新增页面和删除页面)的FragmentPagerAdapter
 * Created by wangzhengyang.
 */

public class CommonPageAdapter extends FragmentPagerAdapter {

    private List<Fragment> mFragmentList = new ArrayList<>();
    private FragmentManager mFm;

    public CommonPageAdapter(FragmentManager fm, int behavior, @NonNull List<Fragment> fragmentList) {
        super(fm, behavior);
        this.mFm = fm;
        for (Fragment fragment : fragmentList) {
            this.mFragmentList.add(fragment);
        }

    }

    public CommonPageAdapter(FragmentManager fm) {
        super(fm);
    }

    public List<Fragment> getFragmentList() {
        return mFragmentList;
    }

    public void addPage(int index, Fragment fragment){
        mFragmentList.add(index, fragment);
        notifyDataSetChanged();
    }
    
    public void addPage(Fragment fragment){
        mFragmentList.add(fragment);
        notifyDataSetChanged();
    }

    public void delPage(int index) {
        mFragmentList.remove(index);
        notifyDataSetChanged();
    }
    
    public void delPage(Fragment fragment) {
        mFragmentList.remove(fragment);
        notifyDataSetChanged();
    }

    public void updatePage(List<Fragment> fragmentList) {
        mFragmentList.clear();
        
        for(int i = 0; i < fragmentList.size(); i++){
            mFragmentList.add(fragmentList.get(i));
		}
        notifyDataSetChanged();
    }

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

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

	/**
	 * 返回值有三种,
	 * POSITION_UNCHANGED  默认值,位置没有改变
	 * POSITION_NONE       item已经不存在
	 * position            item新的位置
	 * 当position发生改变时这个方法应该返回改变后的位置,以便页面刷新。
	 */
    @Override
    public int getItemPosition(Object object) {
        if (object instanceof Fragment) {
			int index = mFragmentList.indexOf(object);
            if (index != -1) {
                return index;
            } else {
                return POSITION_NONE;
            }

        }
        return super.getItemPosition(object);
    }

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

你可能感兴趣的:(android,Android,源码分析)