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

hackernew
发布于 2021-3-2 09:56
浏览
0收藏

文章目录


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的类注释。

 

/**
 * ......
 *
 * <p>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)}.</p>
 */
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.
    *
    * <p>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) 的返回值一致。

 

可参考之后的文章

 

3. FragmentPagerAdapter的刷新


3.1 源码解析


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

 

    /**
     * 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;
    }

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

 

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

 

所以,使用FragmentPagerAdapter除了需要重写其父类PagerAdapter的getItemPosition()方法,还需要重写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)会添加到FragmentPagerAdapter的FragmentManager中,其中的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();
    }
}

 

 

 

分类
标签
已于2021-3-2 09:56:42修改
收藏
回复
举报
1条回复
按时间正序
/
按时间倒序
wx6367e9cc19b96
wx6367e9cc19b96

那么请问要怎么在fragment中实现增删改呢

回复
2022-11-7 01:08:24
回复
    相关推荐