实现ViewPager中的Fragment无感知动态替换

        最近在做公司某项目,其中有个功能是通过viewPager 和Fragment左右切换,但是因为要根据数据类型实现某一个Framgent可以在滑动动作停止后播放视频。之前我们是通过根据数据类型实现不同种类的fragment实例化即可。但是大家都知道ViewPager不显示区域可能也会有fragment,如果我们数据连续项都需要播放视频的话,测试中发现遇到会同时播放多个视频的情况,虽然最终通过某中手段屏蔽了这个问题。而且更重要的问题是,我们播放视频使用的VideoView较一般的View是比较耗费系统资源的,所以我们提出能否整个过程中仅实例化一个VideoView,根据当前position去动态替换制定位置的Fragment.

        为了达到以上目标,虽然花费了2天时间研究如何能够平滑无感知替换,但是感觉还是值得的。

        下面记录下供大家解决类似问题,希望对读者有帮助,也希望大家多多吐槽。

第一步:重写了ViewPager类。

         1、添加了如下方法:实现替换制定位置Fragment

              

    public boolean updateNextPrimaryView(Fragment fragment,int pos) {
        ItemInfo info = super.infoForPosition(pos);
        if (null != info && info.object instanceof Fragment) {
            info.object = fragment;
            if(super.infoForPosition(pos).object == info.object){
                Log.d("LYT","=================================");
            }
            resetFragmentState(pos-2);
            resetFragmentState(pos-1);
            resetFragmentState(pos+1);
            resetFragmentState(pos+2);
//            super.populate(pos);
            return true;
        }
        return false;
    }

    public void resetFragmentState(int position){
        ItemInfo info = super.infoForPosition(position);
        if (null != info && info.object instanceof Fragment) {
            try {
                Field field = ReflectionUtils.getDeclaredField(info.object, "mState");
                field.setAccessible(true);
                field.set(info.object, 0);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    public static interface IScrollPageListener {
        /**
         * @param oldPage old page index
         * @param newPage new page index
         */
        void onScrollToRightPage(int oldPage, int newPage);

        /**
         * @param oldPage old page index
         * @param newPage new page index
         */
        void onScrollToLeftPage(int oldPage, int newPage);
    }

    IScrollPageListener splistener;

    public void setOnScrollPageListener(IScrollPageListener listener) {
        splistener = listener;
    }

      其中 方法
void resetFragmentState(int position)
用于改变ViewPager中指定位置的fragment的生命周期,也是比较重要的关键一步,我们带会让再细说。

方法

 void setOnScrollPageListener(IScrollPageListener listener) 
用于设置监听

第二步,我们应该选择在什么实际去进行替换,通过详细测试和观察发现ViewPager中有个重要的监听回调,就是OnPageSelected,大家如果认真看ViewPager源码会发现,这个方法被调用时,其实viewPager还没有真正scroll,

ViewPager相关源码如下:

1、先设置内部当前位置item,即接下来要滚动到的位置,源码如下:

首先调用ViewPager的方法

public boolean onTouchEvent(MotionEvent ev) {
其中,在MotionEvent.ACTION_UP时会调用下面的方法

void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
    if (mAdapter == null || mAdapter.getCount() <= 0) {
        setScrollingCacheEnabled(false);
        return;
    }
    if (!always && mCurItem == item && mItems.size() != 0) {
        setScrollingCacheEnabled(false);
        return;
    }

    if (item < 0) {
        item = 0;
    } else if (item >= mAdapter.getCount()) {
        item = mAdapter.getCount() - 1;
    }
    final int pageLimit = mOffscreenPageLimit;
    if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
        // We are doing a jump by more than one page.  To avoid
        // glitches, we want to keep all current pages in the view
        // until the scroll ends.
        for (int i=0; i<mItems.size(); i++) {
            mItems.get(i).scrolling = true;
        }
    }
    final boolean dispatchSelected = mCurItem != item;

    if (mFirstLayout) {
        // We don't have any idea how big we are yet and shouldn't have any pages either.
        // Just set things up and let the pending layout handle things.
        mCurItem = item;
        if (dispatchSelected) {
            dispatchOnPageSelected(item);
        }
        requestLayout();
    } else {
        populate(item);
        scrollToItem(item, smoothScroll, velocity, dispatchSelected);
    }
}
又会调用如下方法:

private void dispatchOnPageSelected(int position) {
    if (mOnPageChangeListener != null) {
        mOnPageChangeListener.onPageSelected(position);
    }
    if (mOnPageChangeListeners != null) {
        for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {
            OnPageChangeListener listener = mOnPageChangeListeners.get(i);
            if (listener != null) {
                listener.onPageSelected(position);
            }
        }
    }
    if (mInternalPageChangeListener != null) {
        mInternalPageChangeListener.onPageSelected(position);
    }
}
经过分析,这时候只是设置了下一页要显示的位置item而且这时候它是不可见的,所以我们选择在此时进行替换fragment.

第三步,如何替换制定位置的Fragment呢,我们仍然需要从源码中寻找答案。

我们再看ViewPager源码的时候会发现如下的私有方法定义

ItemInfo infoForPosition(int position) {
    for (int i = 0; i < mItems.size(); i++) {
        ItemInfo ii = mItems.get(i);
        if (ii.position == position) {
            return ii;
        }
    }
    return null;
}
那么我们会发现原来ViewPager中显示的Fragment都存放在mItems中,而他的元素类型却是ItemItem,其定义如下:

static class ItemInfo {
    Object object;
    int position;
    boolean scrolling;
    float widthFactor;
    float offset;
}

我们能得出的结论就是 object对象就是暂存fragment的地方,position就是当前位置索引。

于是就有了第一步中我们通过

ItemInfo info = super.infoForPosition(pos);
来获取指定pos位置的info,只有我们进行替换即可。

世界上什么事情都不是一蹴而就的,接下来新问题来了,我们运行后发现,目标位置的Fragment正常按照预想成功完成替换,但是左右滑动时却惊奇发现主屏幕左右两侧Fragment位置竟然消失了(我司项目表现为黑屏),这是什么原因呢,瞬间头又大了。怎么办呢?用我们强大的log工具呀,Fragment消失那么他的生命周期肯定会发生变化,这里就是我们的着手点。


D/LYT: destroyItem,time=1173
D/LYT: setPrimaryItem,time=1174

D/LYT: finishUpdate,executePendingTransactions,time=1174
D/LYT: LockWallpaperStatePreviewFragment,onCreate,id = I01026408
D/LYT: LockWallpaperStatePreviewFragment,onPause,id = I01026409
D/LYT: LockWallpaperStatePreviewFragment,onStop,id = I01026409
D/LYT: LockWallpaperStatePreviewFragment,onDestroy,id = I01026409
D/LYT: LockWallpaperStatePreviewFragment,onDetach,id = I01026409
D/LYT: LockWallpaperStatePreviewFragment,onPause,id = I01026973
D/LYT: LockWallpaperStatePreviewFragment,onStop,id = I01026973
D/LYT: LockWallpaperStatePreviewFragment,onDestroy,id = I01026973
D/LYT: LockWallpaperStatePreviewFragment,onDetach,id = I01026973
D/LYT: LockWallpaperStatePreviewFragment,onPause,id = I01026974
D/LYT: LockWallpaperStatePreviewFragment,onStop,id = I01026974
D/LYT: LockWallpaperStatePreviewFragment,onDestroy,id = I01026974
D/LYT: LockWallpaperStatePreviewFragment,onDetach,id = I01026974
D/LYT: LockWallpaperStatePreviewFragment,onPause,id = I01026972
D/LYT: LockWallpaperStatePreviewFragment,onStop,id = I01026972
D/LYT: LockWallpaperStatePreviewFragment,onDestroy,id = I01026972
D/LYT: LockWallpaperStatePreviewFragment,onDetach,id = I01026972
D/LYT: LockWallpaperStatePreviewFragment,onPause,id = I00003793
D/LYT: LockWallpaperStatePreviewFragment,onStop,id = I00003793
D/LYT: LockWallpaperStatePreviewFragment,onDestroy,id = I00003793
D/LYT: LockWallpaperStatePreviewFragment,onDetach,id = I00003793
D/LYT: initView,id=I01026973,time=1307
D/LYT: ,time=1307
D/LYT: onHiddenChanged,hidden=false,id=I01026973
D/LYT: startVideo,id=I01026973 D/LYT: onResume,id=I01026973


通过查看日志发现,当我们通过Replace的时候,竟然都onDetch()掉了(我们替换,id = I00003793,但其他也都被移除),什么原因,只能再从源码找答案了。

找了半天原来是我们自己主动触发的,即下方代码执行方法FragmentManager.executePendingTransactions()引起的

public void replaceNextItem(Fragment fragment, int newPos) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if(null != mFragments && null != mCurrentPrimaryItem){
        repFragment = mFragments.get(newPos);
        if (repFragment != null) {
            mCurTransaction.replace(vg.getId(), fragment);
            if (fragment != null) {
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            }
            try {
                mFragmentManager.executePendingTransactions();
            } catch (Exception e) {
            }
        }
    }
}

其中executePendingTransactions方法定义如下:

/**
 * Only call from main thread!
 */
public boolean execPendingActions() {
    if (mExecutingActions) {
        throw new IllegalStateException("Recursive entry to executePendingTransactions");
    }
    
    if (Looper.myLooper() != mHost.getHandler().getLooper()) {
        throw new IllegalStateException("Must be called from main thread of process");
    }

    boolean didSomething = false;

    while (true) {
        int numActions;
        
        synchronized (this) {
            if (mPendingActions == null || mPendingActions.size() == 0) {
                break;
            }
            
            numActions = mPendingActions.size();
            if (mTmpActions == null || mTmpActions.length < numActions) {
                mTmpActions = new Runnable[numActions];
            }
            mPendingActions.toArray(mTmpActions);
            mPendingActions.clear();
            mHost.getHandler().removeCallbacks(mExecCommit);
        }
        
        mExecutingActions = true;
        for (int i=0; i<numActions; i++) {
            mTmpActions[i].run();
            mTmpActions[i] = null;
        }
        mExecutingActions = false;
        didSomething = true;
    }

    doPendingDeferredStart();

    return didSomething;
}
其中执行了 mTmpActions[i].run(),那么这个对象是谁呢,再仔细查找你会发现是BackStackRecord类对象:

因为

@Override
public FragmentTransaction beginTransaction() {
    return new BackStackRecord(this);
}
继续看,我们会发现在BackStackRecord的run()中,

case OP_REPLACE: {
    Fragment f = op.fragment;
    int containerId = f.mContainerId;
    if (mManager.mAdded != null) {
        for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {
            Fragment old = mManager.mAdded.get(i);
            if (FragmentManagerImpl.DEBUG) {
                Log.v(TAG,
                        "OP_REPLACE: adding=" + f + " old=" + old);
            }
            if (old.mContainerId == containerId) {
                if (old == f) {
                    op.fragment = f = null;
                } else {
                    if (op.removed == null) {
                        op.removed = new ArrayList<Fragment>();
                    }
                    op.removed.add(old);
                    old.mNextAnim = op.exitAnim;
                    if (mAddToBackStack) {
                        old.mBackStackNesting += 1;
                        if (FragmentManagerImpl.DEBUG) {
                            Log.v(TAG, "Bump nesting of "
                                    + old + " to " + old.mBackStackNesting);
                        }
                    }
                    mManager.removeFragment(old, mTransition, mTransitionStyle);
                }
            }
        }
    }
    if (f != null) {
        f.mNextAnim = op.enterAnim;
        mManager.addFragment(f, false);
    }
}
由于我们使用的是Replace去替换Fragment,所以又会调用FragmentManager.removeFragment方法,其定义

public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
    if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
    final boolean inactive = !fragment.isInBackStack();
    if (!fragment.mDetached || inactive) {
        if (false) {
            // Would be nice to catch a bad remove here, but we need
            // time to test this to make sure we aren't crashes cases
            // where it is not a problem.
            if (!mAdded.contains(fragment)) {
                throw new IllegalStateException("Fragment not added: " + fragment);
            }
        }
        if (mAdded != null) {
            mAdded.remove(fragment);
        }
        if (fragment.mHasMenu && fragment.mMenuVisible) {
            mNeedMenuInvalidate = true;
        }
        fragment.mAdded = false;
        fragment.mRemoving = true;
        moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
                transition, transitionStyle, false);
    }
}
接着继续调用moveToState,这是一个比较重要的方法,折腾了好长时间才明白,会根据Fragment的mState去以及新状态newState去控制Fragment的生命周期,定义:

void moveToState(Fragment f, int newState, int transit, int transitionStyle,
        boolean keepActive) {
    if (DEBUG && false) Log.v(TAG, "moveToState: " + f
        + " oldState=" + f.mState + " newState=" + newState
        + " mRemoving=" + f.mRemoving + " Callers=" + Debug.getCallers(5));

    // Fragments that are not currently added will sit in the onCreate() state.
    if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {
        newState = Fragment.CREATED;
    }
    if (f.mRemoving && newState > f.mState) {
        // While removing a fragment, we can't change it to a higher state.
        newState = f.mState;
    }
    // Defer start if requested; don't allow it to move to STARTED or higher
    // if it's not already started.
    if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) {
        newState = Fragment.STOPPED;
    }
    if (f.mState < newState) {
        // For fragments that are created from a layout, when restoring from
        // state we don't want to allow them to be created until they are
        // being reloaded from the layout.
        if (f.mFromLayout && !f.mInLayout) {
            return;
        }
        if (f.mAnimatingAway != null) {
            // The fragment is currently being animated...  but!  Now we
            // want to move our state back up.  Give up on waiting for the
            // animation, move to whatever the final state should be once
            // the animation is done, and then we can proceed from there.
            f.mAnimatingAway = null;
            moveToState(f, f.mStateAfterAnimating, 0, 0, true);
        }
        switch (f.mState) {
            case Fragment.INITIALIZING:
                if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
                if (f.mSavedFragmentState != null) {
                    f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray(
                            FragmentManagerImpl.VIEW_STATE_TAG);
                    f.mTarget = getFragment(f.mSavedFragmentState,
                            FragmentManagerImpl.TARGET_STATE_TAG);
                    if (f.mTarget != null) {
                        f.mTargetRequestCode = f.mSavedFragmentState.getInt(
                                FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
                    }
                    f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(
                            FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);
                    if (!f.mUserVisibleHint) {
                        f.mDeferStart = true;
                        if (newState > Fragment.STOPPED) {
                            newState = Fragment.STOPPED;
                        }
                    }
                }
                f.mHost = mHost;
                f.mParentFragment = mParent;
                f.mFragmentManager = mParent != null
                        ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
                f.mCalled = false;
                f.onAttach(mHost.getContext());
                if (!f.mCalled) {
                    throw new SuperNotCalledException("Fragment " + f
                            + " did not call through to super.onAttach()");
                }
                if (f.mParentFragment == null) {
                    mHost.onAttachFragment(f);
                } else {
                    f.mParentFragment.onAttachFragment(f);
                }

                if (!f.mRetaining) {
                    f.performCreate(f.mSavedFragmentState);
                } else {
                    f.restoreChildFragmentState(f.mSavedFragmentState, true);
                    f.mState = Fragment.CREATED;
                }
                f.mRetaining = false;
                if (f.mFromLayout) {
                    // For fragments that are part of the content view
                    // layout, we need to instantiate the view immediately
                    // and the inflater will take care of adding it.
                    f.mView = f.performCreateView(f.getLayoutInflater(
                            f.mSavedFragmentState), null, f.mSavedFragmentState);
                    if (f.mView != null) {
                        f.mView.setSaveFromParentEnabled(false);
                        if (f.mHidden) f.mView.setVisibility(View.GONE);
                        f.onViewCreated(f.mView, f.mSavedFragmentState);
                    }
                }
            case Fragment.CREATED:
                if (newState > Fragment.CREATED) {
                    if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
                    if (!f.mFromLayout) {
                        ViewGroup container = null;
                        if (f.mContainerId != 0) {
                            if (f.mContainerId == View.NO_ID) {
                                throwException(new IllegalArgumentException(
                                        "Cannot create fragment "
                                                + f
                                                + " for a container view with no id"));
                            }
                            container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
                            if (container == null && !f.mRestored) {
                                String resName;
                                try {
                                    resName = f.getResources().getResourceName(f.mContainerId);
                                } catch (NotFoundException e) {
                                    resName = "unknown";
                                }
                                throwException(new IllegalArgumentException(
                                        "No view found for id 0x"
                                        + Integer.toHexString(f.mContainerId) + " ("
                                        + resName
                                        + ") for fragment " + f));
                            }
                        }
                        f.mContainer = container;
                        f.mView = f.performCreateView(f.getLayoutInflater(
                                f.mSavedFragmentState), container, f.mSavedFragmentState);
                        if (f.mView != null) {
                            f.mView.setSaveFromParentEnabled(false);
                            if (container != null) {
                                Animator anim = loadAnimator(f, transit, true,
                                        transitionStyle);
                                if (anim != null) {
                                    anim.setTarget(f.mView);
                                    setHWLayerAnimListenerIfAlpha(f.mView, anim);
                                    anim.start();
                                }
                                container.addView(f.mView);
                            }
                            if (f.mHidden) f.mView.setVisibility(View.GONE);
                            f.onViewCreated(f.mView, f.mSavedFragmentState);
                        }
                    }

                    f.performActivityCreated(f.mSavedFragmentState);
                    if (f.mView != null) {
                        f.restoreViewState(f.mSavedFragmentState);
                    }
                    f.mSavedFragmentState = null;
                }
            case Fragment.ACTIVITY_CREATED:
                if (newState > Fragment.ACTIVITY_CREATED) {
                    f.mState = Fragment.STOPPED;
                }
            case Fragment.STOPPED:
                if (newState > Fragment.STOPPED) {
                    if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
                    f.performStart();
                }
            case Fragment.STARTED:
                if (newState > Fragment.STARTED) {
                    if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
                    f.performResume();
                    // Get rid of this in case we saved it and never needed it.
                    f.mSavedFragmentState = null;
                    f.mSavedViewState = null;
                }
        }
    } else if (f.mState > newState) {
        switch (f.mState) {
            case Fragment.RESUMED:
                if (newState < Fragment.RESUMED) {
                    if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f);
                    f.performPause();
                }
            case Fragment.STARTED:
                if (newState < Fragment.STARTED) {
                    if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f);
                    f.performStop();
                }
            case Fragment.STOPPED:
            case Fragment.ACTIVITY_CREATED:
                if (newState < Fragment.ACTIVITY_CREATED) {
                    if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f);
                    if (f.mView != null) {
                        // Need to save the current view state if not
                        // done already.
                        if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) {
                            saveFragmentViewState(f);
                        }
                    }
                    f.performDestroyView();
                    if (f.mView != null && f.mContainer != null) {
                        Animator anim = null;
                        if (mCurState > Fragment.INITIALIZING && !mDestroyed) {
                            anim = loadAnimator(f, transit, false,
                                    transitionStyle);
                        }
                        if (anim != null) {
                            final ViewGroup container = f.mContainer;
                            final View view = f.mView;
                            final Fragment fragment = f;
                            container.startViewTransition(view);
                            f.mAnimatingAway = anim;
                            f.mStateAfterAnimating = newState;
                            anim.addListener(new AnimatorListenerAdapter() {
                                @Override
                                public void onAnimationEnd(Animator anim) {
                                    container.endViewTransition(view);
                                    if (fragment.mAnimatingAway != null) {
                                        fragment.mAnimatingAway = null;
                                        moveToState(fragment, fragment.mStateAfterAnimating,
                                                0, 0, false);
                                    }
                                }
                            });
                            anim.setTarget(f.mView);
                            setHWLayerAnimListenerIfAlpha(f.mView, anim);
                            anim.start();

                        }
                        f.mContainer.removeView(f.mView);
                    }
                    f.mContainer = null;
                    f.mView = null;
                }
            case Fragment.CREATED:
                if (newState < Fragment.CREATED) {
                    if (mDestroyed) {
                        if (f.mAnimatingAway != null) {
                            // The fragment's containing activity is
                            // being destroyed, but this fragment is
                            // currently animating away.  Stop the
                            // animation right now -- it is not needed,
                            // and we can't wait any more on destroying
                            // the fragment.
                            Animator anim = f.mAnimatingAway;
                            f.mAnimatingAway = null;
                            anim.cancel();
                        }
                    }
                    if (f.mAnimatingAway != null) {
                        // We are waiting for the fragment's view to finish
                        // animating away.  Just make a note of the state
                        // the fragment now should move to once the animation
                        // is done.
                        f.mStateAfterAnimating = newState;
                        newState = Fragment.CREATED;
                    } else {
                        if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
                        if (!f.mRetaining) {
                            f.performDestroy();
                        } else {
                            f.mState = Fragment.INITIALIZING;
                        }

                        f.performDetach();
                        if (!keepActive) {
                            if (!f.mRetaining) {
                                makeInactive(f);
                            } else {
                                f.mHost = null;
                                f.mParentFragment = null;
                                f.mFragmentManager = null;
                            }
                        }
                    }
                }
        }
    }
    
    if (f.mState != newState) {
        Log.w(TAG, "moveToState: Fragment state for " + f + " not updated inline; "
                + "expected state " + newState + " found " + f.mState);
        f.mState = newState;
    }
}
这个代码比较多,大家可以直接看源码,经过分析,原来是因为替换工作进行后,触发 executePendingTransactions() 由于mState状态等于5

static final int INVALID_STATE = -1;   // Invalid state used as a null value.
static final int INITIALIZING = 0;     // Not yet created.
static final int CREATED = 1;          // Created.
static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
static final int STOPPED = 3;          // Fully created, not started.
static final int STARTED = 4;          // Created and started, not resumed.
static final int RESUMED = 5;          // Created started and resumed.
也就是RESUMED状态,而Replace的时候只会是INITIZLIZING和CREATED状态,这就导致我们这时候只能走到分支 if (f.mState > newState),也就是只能会导致Fragment的OnPause、OnStop、OnDestoryView、OnDestach的调用,分析到这里我们就找到原因了 ,只要我们替换后改变Fragment成员变量状态mState即可,于是有了我们第一步中提到的,通过反射改变Fragment状态

public void resetFragmentState(int position){
        ItemInfo info = super.infoForPosition(position);
        if (null != info && info.object instanceof Fragment) {
            try {
                Field field = ReflectionUtils.getDeclaredField(info.object, "mState");
                field.setAccessible(true);
                field.set(info.object, 0);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

另外还要修改其余未显示的Fragment

resetFragmentState(pos-2);
resetFragmentState(pos-1);
resetFragmentState(pos+1);
resetFragmentState(pos+2);

到目前位置我们的目标就实现了,实现了无感知提换Fragment。

当然这里还有问题就是使用时替换了,那么如果划出屏幕区域如何替换呢,这个我们明天继续。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我会为您讲解关于AndroidViewPager和Fragment的使用。 ViewPager和FragmentAndroid非常常用的组件,他们可以一起使用来实现滑动页面效果。ViewPager是一个可以左右滑动切换页面的布局容器,而Fragment作为ViewPager的子页面,可以在ViewPager进行动态添加和移除。 下面我们将分别介绍ViewPager和Fragment的使用。 ## ViewPager的使用 ### 1.布局文件 在布局文件,我们需要使用ViewPager作为容器,将需要滑动切换的页面放入其。如下所示: ``` <androidx.viewpager.widget.ViewPager android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="match_parent"/> ``` ### 2.创建Adapter 我们需要创建一个Adapter继承自PagerAdapter,并重写以下方法: ``` public class MyPagerAdapter extends PagerAdapter { private List<Fragment> mFragments; public MyPagerAdapter(List<Fragment> fragments) { mFragments = fragments; } @Override public int getCount() { return mFragments.size(); } @Override public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { return view == object; } @NonNull @Override public Object instantiateItem(@NonNull ViewGroup container, int position) { Fragment fragment = mFragments.get(position); container.addView(fragment.getView()); return fragment.getView(); } @Override public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { container.removeView((View) object); } } ``` ### 3.设置Adapter 在Activity或Fragment,我们需要创建ViewPager的实例,并设置Adapter。如下所示: ``` ViewPager viewPager = findViewById(R.id.viewPager); List<Fragment> fragments = new ArrayList<>(); fragments.add(new Fragment1()); fragments.add(new Fragment2()); fragments.add(new Fragment3()); MyPagerAdapter adapter = new MyPagerAdapter(fragments); viewPager.setAdapter(adapter); ``` 这样,我们就完成了ViewPager的使用。 ## Fragment的使用 ### 1.创建Fragment 我们需要创建一个继承自Fragment的类,并重写以下方法: ``` public class Fragment1 extends Fragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment1, container, false); return view; } } ``` ### 2.布局文件 我们需要在Fragment添加布局文件,如下所示: ``` <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:text="Fragment1" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> ``` 这样,我们就完成了Fragment的使用。 ## ViewPager和Fragment的结合使用 通过以上介绍,我们已经知道了如何使用ViewPager和Fragment了。现在我们需要将它们结合起来使用。 ### 1.创建Fragment 我们需要创建多个Fragment作为ViewPager的子页面。 ### 2.创建Adapter 我们需要创建一个PagerAdapter,将Fragment添加到ViewPager。如上所示,我们已经创建了一个MyPagerAdapter。 ### 3.设置Adapter 在Activity或Fragment,我们需要创建ViewPager的实例,并设置Adapter。如上所示,我们已经使用ViewPager的setAdapter方法设置了MyPagerAdapter。 这样,我们就完成了ViewPager和Fragment的结合使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值