1. 程式人生 > >Viewpager+Fragment動態處理(新增、刪除)Fragment

Viewpager+Fragment動態處理(新增、刪除)Fragment

問題

在進行Fragment的新增和刪除時,介面卡重新整理之後發現並沒有什麼變化,這是為什麼呢?

理解

FragmentPagerAdapter

適合少量的頁面顯示。該類每一個生成的Fragment物件都會儲存在記憶體中,所以適合相對靜態、頁面少的情況,如果是頁面多,且Fragment的處理相對動態(新增、刪除等)時,使用FragmentStatePagerAdapter較為適合。

FragmentStatePagerAdapter

適合大量的頁面顯示,當頁面處於不可見時,可能會被銷燬,只保留該片段的儲存狀態。與FragmentPagerAdapter切換頁面產生的大量開銷對比,這允許了介面卡保持與每個被訪問頁面相關聯的更少的儲存器。

分析

在切換頁面時,FragmentPagerAdapter與FragmentStatePagerAdapter對於上上頁(預載入預設1,所以取上上頁)的處理是不相同的,FragmentPagerAdapter只是銷燬對應Fragment的檢視,而FragmentStatePagerAdapter則是把Fragment的例項和檢視都銷燬了。

當我們對頁面進行動態處理時,新增(或刪除)是對介面卡所持有的list物件進行長度的變化,操作完之後就進行介面卡的重新整理,也就是notifyDataSetChanged方法,先看看該方法:

//PagerAdapter.class
public
void notifyDataSetChanged() { synchronized (this) { if (mViewPagerObserver != null) { //根據原始碼可知mViewPagerObserver的物件是ViewPager裡面PagerObserver類的例項 mViewPagerObserver.onChanged(); } } mObservable.notifyChanged(); }
//ViewPager.class
private class PagerObserver extends DataSetObserver { PagerObserver() { } @Override public void onChanged() { //呼叫的是該方法 dataSetChanged(); } @Override public void onInvalidated() { dataSetChanged(); } }

對FragmentPagerAdapter(或FragmentStatePagerAdapter)執行的方法大概進行註釋一下,方便理解,


//ViewPager.class
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;
        //遍歷所有item
        for (int i = 0; i < mItems.size(); i++) {
            final ItemInfo ii = mItems.get(i);
            //先呼叫adapter的getItemPosition方法,獲得newPos值
            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;
                }
                //newPos值為PagerAdapter.POSITION_NONE的時候才會執行destroyItem方法
                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) {
            //finishUpdate方法主要是對事務的操作進行commit
            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();
        }
    }

Adapter.getItemPosition方法預設返回的是PagerAdapter.POSITION_UNCHANGED值,如果我們不重寫getItemPosition方法,使其返回PagerAdapter.POSITION_NONE的話,那麼預設是不操作destroyItem方法的,而在destroyItem方法中,FragmentPagerAdapter和FragmentStatePagerAdapter 對Fragment物件的操作也不一樣,上面有說過,FragmentPagerAdapter是隻銷燬檢視,FragmentStatePagerAdapter 是把例項和檢視都銷燬,就是在destroyItem方法實現的,貼程式碼:

//FragmentPagerAdapter
@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());
        //這裡是對fragment進行detach操作,fragmentManager中還儲存該例項
        mCurTransaction.detach((Fragment)object);
    }
//FragmentStatePagerAdapter 
@Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        while (mSavedState.size() <= position) {
            mSavedState.add(null);
        }
        mSavedState.set(position, fragment.isAdded()
                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
        mFragments.set(position, null);
        //而這裡是對fragment進行remove,直接在fragmentManager中移除掉
        mCurTransaction.remove(fragment);
    }

解決

根據上面的分析,在進行新增刪除的時候,我採用了FragmentStatePagerAdapter的子類,進行方法的重寫,主要是對該類的兩個方法(instantiateItem和destroyItem)進行重寫,替換父類的實現,程式碼如下:

package com.voctex.adapter;

import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.view.ViewGroup;

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

/**
 * @author Voctex
 * on 2018/08/28 18:07
 */
public class DynamicFragmentAdapter extends FragmentStatePagerAdapter {
    private FragmentManager mFragmentManager;
    private List<Fragment> mFragments = new ArrayList<>();

    public DynamicFragmentAdapter(FragmentManager fm, List<Fragment> list) {
        super(fm);
        this.mFragmentManager = fm;
        if (list == null) return;
        this.mFragments.addAll(list);
    }

    public void updateData(List<Fragment> mlist) {
        if (mlist == null) return;
        this.mFragments.clear();
        this.mFragments.addAll(mlist);
        notifyDataSetChanged();
    }

    @Override
    public Fragment getItem(int arg0) {
        return mFragments.get(arg0);//
    }

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

    @Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public int getItemPosition(Object object) {
        if (!((Fragment) object).isAdded() || !mFragments.contains(object)) {
            return PagerAdapter.POSITION_NONE;
        }
        return mFragments.indexOf(object);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {

        Fragment instantiateItem = ((Fragment) super.instantiateItem(container, position));
        Fragment item = mFragments.get(position);
        if (instantiateItem == item) {
            return instantiateItem;
        } else {
            //如果集合中對應下標的fragment和fragmentManager中的對應下標的fragment物件不一致,那麼就是新新增的,所以自己add進入;這裡為什麼不直接呼叫super方法呢,因為fragment的mIndex搞的鬼,以後有機會再補一補。
            mFragmentManager.beginTransaction().add(container.getId(), item).commitNowAllowingStateLoss();
            return item;
        }
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment) object;
        //如果getItemPosition中的值為PagerAdapter.POSITION_NONE,就執行該方法。
        if (mFragments.contains(fragment)) {
            super.destroyItem(container, position, fragment);
            return;
        }
        //自己執行移除。因為mFragments在刪除的時候就把某個fragment物件移除了,所以一般都得自己移除在fragmentManager中的該物件。
        mFragmentManager.beginTransaction().remove(fragment).commitNowAllowingStateLoss();

    }
}

結束語

在不斷的看原始碼,查資料,除錯程式中,終於是把該問題解決了,網上的資料都說得模稜兩可,很多時候都得自己操刀,理解了才是自己的,特別是Fragment在FragmentManager中的mIndex值,有點坑,這裡沒拿出來說,以後有機會再補補。