Dynamically replace Fragment in ViewPager

Directly upload the code (adapter), which is ready to use, and the comments are very clear:

import android.os.Parcelable;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.fragment.app.FragmentTransaction;
import java.util.List;

/**
 * <p>
 *     ViewPager的适配器
 *     可动态替换Fragment
 *     使用方法:
 *         mFragmentList.set(0, new SearchFragment());
 *         mViewPagerAdapter.setReplace(true);
 *         mViewPagerAdapter.notifyDataSetChanged();
 * </p>
 */
 
public class MainViewPagerAdapter extends FragmentStatePagerAdapter {
    
    

    /**
     * 被替换的Fragment需要实现这个空接口,相当于做个标记,代表是可以被替换的
     */
   public interface OnReplaceable {
    
    

    }

    /**
     * ViewPager容纳的Fragment集合
     */
    private List<Fragment> mList;

    /**
     * 是否替换,T表示替换,F表示不替换。默认不替换
     */
    private boolean isReplace = false;

    /**
     * Fragment管理器和事务管理器
     */
    private FragmentManager mFragmentManager;
    private FragmentTransaction mFragmentTransaction = null;

    /**
     * 当前主Fragment
     */
    private Fragment mCurrentPrimaryFragment = null;

    public MainViewPagerAdapter(@NonNull FragmentManager fm, int behavior, List<Fragment> list) {
    
    
        super(fm, behavior);
        this.mList = list;
        this.mFragmentManager = fm;
    }

    /**
     * 设置是否替换
     *
     * @param isReplace     T表示替换,F表示不替换
     */
    public void setReplace(boolean isReplace) {
    
    
        this.isReplace = isReplace;
    }

    /**
     * 判断Fragment是否可替换
     *
     * @param object
     * @return
     */
   private boolean isFragmentReplaceable(Object object){
    
    
       // 同时满足可替换标识符和实现了“可替换”接口,才能替换
        if(isReplace && (object instanceof OnReplaceable)){
    
    
            return true;
        }else {
    
    
            return false;
        }
   }


    /**
     * 获取指定位置上的Fragment
     *
     * @param position
     * @return
     */
    @NonNull
    @Override
    public Fragment getItem(int position) {
    
    
        return mList.get(position);
    }

    /**
     * 获取ViewPager一共搭载了多少个Fragment
     *
     * @return
     */
    @Override
    public int getCount() {
    
    
        return mList == null ? 0 : mList.size();
    }

    /**
     *  每次调用notifyDataChange()时会调用此方法
     *  POSITION_NONE : 表示该item会被destroyItem()方法执行销毁,然后重新加载
     *  POSITION_UNCHANGED : 表示不会重新加载
     *
     * @param object
     * @return
     */
    @Override
    public int getItemPosition(@NonNull Object object) {
    
    
        // 判断该子项Fragment是否可替换
        if(isFragmentReplaceable(object)){
    
    
            // 可替换的话返回POSITION_NONE让 destroyItem() 销毁当前item,并重新加载
            return POSITION_NONE;
        }else {
    
    
            return POSITION_UNCHANGED;
        }
    }

    /**
     * 设置主Fragment页面
     *
     * @param container
     * @param position
     * @param object    由instantiateItem()返回的object对象
     */
    @Override
    public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    
    
        Fragment fragment = (Fragment)object;
        if(fragment != mCurrentPrimaryFragment){
    
    
            if(mCurrentPrimaryFragment != null){
    
    
                // setUserVisibleHint:判断这个Fragment是否对用户可见
                mCurrentPrimaryFragment.setUserVisibleHint(false);
                mCurrentPrimaryFragment.setMenuVisibility(false);
            }
            if(fragment != null){
    
    
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            }
            mCurrentPrimaryFragment = fragment;
        }
    }

    /**
     * 移除指定位置上的Object对象(Fragment),此时调用事务的remove(FragmentTransaction.remove())
     *
     * @param container
     * @param position
     * @param object
     */
    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    
    
        if(mFragmentTransaction == null || isFragmentReplaceable(object)){
    
    
            mFragmentTransaction = mFragmentManager.beginTransaction();
            // 设置Fragment切换动画...
            setFragmentChangeAnimation(mFragmentTransaction);
        }

        if(isFragmentReplaceable(object)){
    
    
            // 如果是可替换的Fragment,则销毁当前位置上的Fragment实例
            mFragmentTransaction.remove((Fragment) object);
        }else {
    
    
            // 如果不是可替换的Fragment,则销毁当前位置上的Fragment布局(实例没有被销毁,仍由事务管理)
            mFragmentTransaction.detach((Fragment) object);
        }
    }

    /**
     * 实例化Item.
     * 每次ViewPager需要显示内容时,该方法都会被ViewPager内部的addNewItem()方法调用;
     * 该方法会通过position调用getItem(position)方法拿到Object对象,这个对象会被添加到事务中去(FragmentTransaction.add());
     * FragmentStatePagerAdapter就是通过这种方式创建新的Fragment,从而达到不需要显示的情况下释放资源,节省内存的目的。
     *
     * @param container
     * @param position
     * @return
     */
    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
    
    

        if(mFragmentTransaction == null){
    
    
            mFragmentTransaction = mFragmentManager.beginTransaction();
            // 设置切换动画
            setFragmentChangeAnimation(mFragmentTransaction);
        }

        final long itemId = getCurrentItemId(position);

        String fragmentName = makeFragmentName(container.getId(), itemId);

        Fragment fragment = mFragmentManager.findFragmentByTag(fragmentName);

        if(fragment != null && !(isFragmentReplaceable(fragment))){
    
    
            mFragmentTransaction.attach(fragment);
        }else {
    
    
            // fragment不为null且不刷新viewpager
            fragment = getItem(position);
            // 将Fragment添加到事务中去
            mFragmentTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId));
        }

        if(fragment != mCurrentPrimaryFragment){
    
    
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }

        if(isFragmentReplaceable(fragment)){
    
    
            isReplace = false;
        }

        return fragment;
    }

    /**
     * 当显示的页面中的更改完成时调用,必须确保所有页面实际上都已适当地从容器中添加或删除。
     *
     * @param container     包含该适配器的页面视图的包含容器View
     */
    @Override
    public void finishUpdate(@NonNull ViewGroup container) {
    
    
        // 当替换完成时调用,释放事务
        if(mFragmentTransaction != null){
    
    
            /*
                事务最终的提交方法有4个:
                1.commit()  需要在宿主 Activity 保存状态之前调用,否则会报错。这是因为如果 Activity 出现异常需要恢复状态,在保存状态之后的 commit() 将会丢失,这和调用的初衷不符,所以会报错。
                2.commitAllowingStateLoss() 允许在 Activity 保存状态之后调用,也就是说它遇到状态丢失不会报错。因此我们一般在界面状态出错是可以接受的情况下使用它。
                3.commitNow()   是同步执行的,立即提交任务
                4.commitNowAllowingStateLoss() 类比
             */
            // 释放前将事务提交
            mFragmentTransaction.commitAllowingStateLoss();
            // 释放事务
            mFragmentTransaction = null;
        }
    }

    /**
     * 判断当前显示的view是否与object相关联
     *
     * @param view      当前显示的view
     * @param object    instantiateItem()方法返回的Object对象
     * @return
     */
    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
    
    
        return ((Fragment)object).getView() == view;
    }

    /**
     * 保存与该适配器及其页面相关联的所有实例状态,如果需要重建当前的UI状态,则应恢复该实例及其页面。
     *
     * @return
     */
    @Nullable
    @Override
    public Parcelable saveState() {
    
    
        return null;
    }

    /**
     * 设置Fragment名字,仿FragmentStatesPagerAdapter中的makeFragmentName,原因是父类的方法是私有的
     *
     * @param viewId
     * @param id
     * @return
     */
    private String makeFragmentName(int viewId, long id){
    
    
        return "android:switcher:" + viewId + ":" + id;
    }

    public long getCurrentItemId(int position) {
    
    
        return position;
    }

    /**
     * 设置Fragment切换动画
     * @param ft
     */
    private void setFragmentChangeAnimation(FragmentTransaction ft){
    
    
        ft.setCustomAnimations( R.anim.fragment_enter,
                R.anim.fragment_exit,
                R.anim.fragment_pop_enter,
                R.anim.fragment_pop_exit);
    }
}

Guess you like

Origin blog.csdn.net/C_biubiubiu/article/details/111321886