直接上代码(适配器),即拿即用,注释也很明确:
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);
}
}