前段时间遇到了这么一个需求,当某个终端分配多个订单时可以切换订单分别处理,当时想着这不是分分钟的事吗,上面一个Tablayout下面一个Viewpager,Tablaout里面的item展示各订单号,Viewpager里面添加相应的Fragment就搞定了。
后来仔细一想每个订单主要有两种状态,一种是待激活,一种是已激活。假设未激活订单需展示fragment1,已激活订单展示fragment2,需要满足在fragment1中激活当前订单,这时候应该将fragment1替换成fragment2。
首先我们新建一个项目,简单的创建一个MainActivity,对应的XML布局如下:
"1.0" encoding="utf-8"?>
.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
.support.design.widget.TabLayout
android:id="@+id/mTabLayout"
android:layout_width="match_parent"
android:layout_height="50dp"/>
.support.v4.view.ViewPager
android:id="@+id/mViewPager"
app:layout_constraintTop_toBottomOf="@+id/mTabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
.support.v4.view.ViewPager>
.support.constraint.ConstraintLayout>
然后为了方便查看Fragment的生命周期情况,我们新建一个Fragment的基类BaseFragment,代码如下:
public class BaseFragment extends Fragment {
protected final String TAG = "======"+this.getClass().getSimpleName();
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.d(TAG,"isVisibleToUser:"+isVisibleToUser);
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(TAG,"onSaveInstanceState");
}
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
Log.d(TAG,"onViewStateRestored");
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.d(TAG,"onAttach");
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG,"onCreate");
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.d(TAG,"onCreateView");
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(TAG,"onActivityCreated");
}
@Override
public void onStart() {
super.onStart();
Log.d(TAG,"onStart");
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG,"onResume");
}
@Override
public void onPause() {
super.onPause();
Log.d(TAG,"onPause");
}
@Override
public void onStop() {
super.onStop();
Log.d(TAG,"onStop");
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG,"onDestroyView");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG,"onDestroy");
}
@Override
public void onDetach() {
super.onDetach();
Log.d(TAG,"onDetach");
}
只是简单的用log输出了一下各方法的执行情况,然后我们新建几个Fragment继承该基类,定义了一个接口用于在MainActivity执行回调方法,代码如下:
Fragment1
public class Fragment1 extends BaseFragment {
@BindView(R.id.bt_replace)
Button btReplace;
Unbinder unbinder;
private IReplaceCurrentFg iReplaceCurrentFg;
public Fragment1() {
// Required empty public constructor
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
iReplaceCurrentFg = (IReplaceCurrentFg) context;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
super.onCreateView(null, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_fragment1, container, false);
unbinder = ButterKnife.bind(this, view);
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
@OnClick(R.id.bt_replace)
public void onViewClicked() {
if (iReplaceCurrentFg != null){
iReplaceCurrentFg.replace();
}
}
public interface IReplaceCurrentFg{
void replace();
}
}
对应的xml很简单,只有一个Button用于触发回调方法,代码如下:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Fragment1">
<Button
android:id="@+id/bt_replace"
android:layout_gravity="center"
android:text="替换当前fragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<android.support.design.widget.TextInputEditText
tools:text="1634153"
android:layout_width="match_parent"
android:layout_height="50dp" />
FrameLayout>
然后我们新建一个ReplaceFragment用来展示替换后的新内容,布局和代码如下:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ReplaceFragment">
<Button
android:id="@+id/bt_add"
android:text="添加Fragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/bt_addChild"
android:text="添加Edit"
android:layout_marginLeft="200dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:id="@+id/container"
android:layout_marginTop="100dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
LinearLayout>
<TextView
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是替换后的fragment"/>
FrameLayout>
ReplaceFragment
public class ReplaceFragment extends BaseFragment {
@BindView(R.id.bt_add)
Button btAdd;
Unbinder unbinder;
@BindView(R.id.container)
LinearLayout container;
@BindView(R.id.bt_addChild)
Button btAddChild;
private IAddNewFragment iAddNewFragment;
public ReplaceFragment() {
// Required empty public constructor
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
}
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
iAddNewFragment = (IAddNewFragment) context;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_replace, container, false);
unbinder = ButterKnife.bind(this, view);
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
btAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (iAddNewFragment != null) {
iAddNewFragment.add();
}
}
});
btAddChild.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EditText editText = new EditText(getContext());
container.addView(editText);
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
public interface IAddNewFragment {
void add();
}
}
然后我们在MainActivity中将Viewpager与TabLayout,添加几条静态数据
mFragmentList = new ArrayList<>();
mTitleList = new ArrayList<>();
Fragment1 fragment1 = new Fragment1();
Fragment2 fragment2 = new Fragment2();
Fragment3 fragment3 = new Fragment3();
Fragment4 fragment4 = new Fragment4();
mFragmentList.add(fragment1);
mFragmentList.add(fragment2);
mFragmentList.add(fragment3);
mFragmentList.add(fragment4);
mTitleList.add("标题1");
mTitleList.add("标题2");
mTitleList.add("标题3");
mTitleList.add("标题4");
mFgPagerAdapter = new MyFgPagerAdapter(getSupportFragmentManager(), mFragmentList, mTitleList);
mViewPager.setAdapter(mFgPagerAdapter);
mTabLayout.setupWithViewPager(mViewPager);
MyFgPagerAdapter是继承于FragmentPagerAdapter的,代码如下:
public class MyFgPagerAdapter extends FragmentPagerAdapter {
private FragmentManager fm;
private List fragmentList;
private List titleList;
public MyFgPagerAdapter(FragmentManager fm, List fragmentList, List titleList) {
super(fm);
this.fm = fm;
this.fragmentList = fragmentList;
this.titleList = titleList;
}
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public int getCount() {
return fragmentList.size();
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return titleList.get(position);
}
}
项目运行效果如下
我们可以看见标题1对应的Fragment中有个按钮,我们点击这个按钮来将Fragment1替换成ReplaceFragment,button对应的点击事件如下:
public void replace() {
//默认replace第一个
ReplaceFragment replaceFragment = new ReplaceFragment();
mFragmentList.remove(0);
mFragmentList.add(0,replaceFragment);
mFgPagerAdapter.notifyDataSetChanged();
Toast.makeText(this, "替换fragment成功", Toast.LENGTH_SHORT).show();
}
我们将首位的Fragment1移除掉,然后再将ReplaceFragment 添加到首位,最后调用notifyDataSetChanged()方法来尝试更新视图,实际效果如下:
我们通过log日志可以看到,在页面初始化完毕之后,我们点击按钮进行Fragment的数据刷新,发现并没有任何效果。
这时在网上查找相关问题的解决方法,说我们可以重写FragmentPagerAdapter的getItemPosition方法,来使Fragment重新创建,好的,我们在MyFgPagerAdapter中重写该方法
@Override
public int getItemPosition(@NonNull Object object) {
return POSITION_NONE;
}
加上上面的代码之后,我们再运行项目看一下效果
通过log日志我们发现现在点击按钮时我们的Fragment确实销毁重建了,可是并没有达到我们将Fragment1替换成ReplaceFragment的目的。
接着查找资料,发现都提到了FragmentPagerAdapter中的instantiateItem这个方法,我们点进FragmentPagerAdapter源码简单的看一下该方法
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
//获取fragment的名字
String name = makeFragmentName(container.getId(), itemId);
//从内存中根据fragment的名字取出fragment
Fragment fragment = mFragmentManager.findFragmentByTag(name);
//如果存在,直接attach
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;
}
我们在使用FragmentPagerAdapter时,每次实例化一个Fragment时都会调用instantiateItem这个方法,而这个方法里面原来是先通过FragmentManager从内存里面获取fragment,如果已经存在了,直接就attach了,不会再重新创建。而我们只是从List中简单的移除了Fragment1,并没有从内存中移除它,所以当我们调用notifyDataSetChanged方法时 ,ReplaceFragment并不会被创建。解决方法就是我们在调用notifyDataSetChanged方法之前,先将内存当中的fragment全部移除掉,我们在MyFgPagerAdapter中增加一个方法达到我们的目标, 代码如下:
public void setFragments(List fragments) {
if(this.fragmentList != null){
FragmentTransaction ft = fm.beginTransaction();
for(Fragment f:this.fragmentList){
ft.remove(f);
}
ft.commit();
ft=null;
fm.executePendingTransactions();
}
this.fragmentList = fragments;
notifyDataSetChanged();
}
这里需要注意一点,因为我们在MyFgPagerAdapter的构造函数当中直接将fragmentList 指向了我们MainActivity中的mFragmentList,而我们又是在执行setFragments这个方法之前将mFragmentList中的数据改变了,这就会导致我们遍历fragmentList 从内存中移除fragment时遗漏掉Fragment1,所以对MyFgPagerAdapter的构造函数稍作修改,代码如下:
public MyFgPagerAdapter(FragmentManager fm, List fragmentList, List titleList) {
super(fm);
this.fm = fm;
//this.fragmentList = fragmentList;
this.fragmentList = new ArrayList<>();
this.fragmentList.addAll(fragmentList);
this.titleList = titleList;
}
然后对Fragment1中的按钮方法稍作修改:
@Override
public void replace() {
//默认replace第一个
ReplaceFragment replaceFragment = new ReplaceFragment();
mFragmentList.remove(0);
mFragmentList.add(0,replaceFragment);
mFgPagerAdapter.setFragments(mFragmentList);
Toast.makeText(this, "替换fragment成功", Toast.LENGTH_SHORT).show();
}
接着再次运行项目,效果如下:
哈哈,总算达到我们想要的结果啦,咦?不过怎么感觉有点不对劲呢。Fragment1确确实实是成功的被替换了,可是标题2对应的Fragment2怎么是空白呢,查看log日志发现Fragment2在销毁之后重新创建时只执行到了onCreate方法,所以还没有创建视图,导致界面为空,而我也没有设置懒加载,为什么onCreateView没有执行呢,欢迎知道原因的小伙伴文章下方留言解答,谢谢!
不知道有没有细心的小伙伴发现,我们在使用FragmentPagerAdapter的notifyDataSetChanged方法时,Fragment的onSaveInstanceState方法并没有执行
在查找相关资料说可以将适配器更换成FragmentStatePagerAdapter也可以满足我们最初的需求。我们更换成FragmentStatePagerAdapter试一下,MainActivity中代码稍作修改 :
将适配器稍作更改
mFgStatePagerAdapter = new MyFgStatePagerAdapter(getSupportFragmentManager(),mFragmentList,mTitleList);
mViewPager.setAdapter(mFgStatePagerAdapter);
对应的按钮事件也稍作更改
@Override
public void replace() {
//默认replace第一个
ReplaceFragment replaceFragment = new ReplaceFragment();
mFragmentList.remove(0);
mFragmentList.add(0,replaceFragment);
mFgStatePagerAdapter.notifyDataSetChanged();
Toast.makeText(this, "替换fragment成功", Toast.LENGTH_SHORT).show();
}
其中MyFgStatePagerAdapter代码如下:
public class MyFgStatePagerAdapter extends FragmentStatePagerAdapter {
private FragmentManager fm;
private List fragmentList;
private List titleList;
public MyFgStatePagerAdapter(FragmentManager fm, List fragmentList, List titleList) {
super(fm);
this.fragmentList = fragmentList;
this.titleList = titleList;
}
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public int getItemPosition(@NonNull Object object) {
return POSITION_NONE;
}
@Override
public int getCount() {
return fragmentList.size();
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return titleList.get(position);
}
我们在MyFgStatePagerAdapter中依旧重写了getItemPosition方法,返回POSITION_NONE。值得注意的是我们没有再提供一个方法来清除内存中的Fragment,这是因为FragmentStatePagerAdapter的instantiateItem方法和FragmentPagerAdapter稍有不同,我们查看一下FragmentStatePagerAdapter的instantiateItem方法:
@Override
public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
//直接调用getItem方法获取Fragment
Fragment fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
可以看到在FragmentStatePagerAdapter的instantiateItem方法中,不会从内存中获取Fragment,而是直接调用我们重写的getItem方法,返回我们list中的Fragment,所以我们也就不需要重写instantiateItem方法了。
我们运行项目,看一下更换成FragmentStatePagerAdapter后的效果:
nice!可以看到我们使用FragmentStatePagerAdapter之后,不但满足了我们最初的需求,而且还解决了我们使用FragmentPagerAdapter导致相邻的Fragment2视图不加载的问题!
@Override
public int getItemPosition(@NonNull Object object) {
return POSITION_NONE;
}
最后附上Demo源码:下载
为什么调用 FragmentPagerAdapter.notifyDataSetChanged() 并不能更新其 Fragment