Android中最简单有效的Fragment全局管理器(全新实现方式)
这篇文章专门用于讲述LevineUtils中的
FragmentFactory
,需要集成LevineUtils
1.FragmentFactory简介
FragmentFactory
是利用apt技术,即通过注解的方式来管理整个应用中的自定义的Fragment,通过FragmentFactory
对象的showFragment(String tag)方法来控制fragment的显示和隐藏,从而实现了fragment的切换.
FragmentFactory
同时也对fragment的重影问题给出了解决方案,通过使用saveCurrentFragmentInfo(Bundle bundle)和restoreCurrentFragmentInfo(Bundle bundle)方法保存状态和恢复状态.
2.使用FragmentFactory
初始化FragmentFactory
在activity中的onCreate方法中初始化FragmentFactory
对象,但是需要注意的是需要继承自FragmentActicity 或者AppCompatActicity,因为在FragmentFactory
中使用的是getSupportFragmentManager,所以你的activity必须继承自FragmentActivity或
AppCompatActivity:
public class MainActivity extends AppCompatActivity { private FragmentFactory mFactory; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //使用单例模式创建`FragmentFactory`对象,R.id.mContentnF1是要显示的Fragment的布局容器的id mFactory = FragmentFactory.getInstance() .init(this, R.id.mContentFl); .... } @Override protected void onResume() { super.onResume(); //很关键,在activity切换时起作用,如果APP使用单activity,则不需要这句代码 mFactory.onResume(this,R.id.mContentFl); }}
自定义Fragment
例如我的主界面是这样的布局,里面含有两个tab,用于两个Fragment的切换:
<?xml version="1.0" encoding="utf-8"?><LinearLayout 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" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical"> <FrameLayout android:id="@+id/mContentFl" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="0.9" /> <com.google.android.material.tabs.TabLayout android:layout_gravity="bottom" android:id="@+id/mTabLayout" android:layout_weight="0.1" android:layout_width="match_parent" android:layout_height="0dp"> <com.google.android.material.tabs.TabItem android:id="@+id/tab1" android:text="page1" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent"/> <com.google.android.material.tabs.TabItem android:id="@+id/tab2" android:text="page2" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent"/> com.google.android.material.tabs.TabLayout>LinearLayout>
自定义两个Fragment用于tab点击时,切换使用(此时要在Fragment的外部使用@TargetFragmentTag(String tag)
注解,里面需要接收一个string类型的tag,用于和当前的fragment 一 一对应):
@TargetFragmentTag("fragment1")public class Fragment1 extends Fragment { .....}@TargetFragmentTag("fragment2")public class Fragment2 extends Fragment { .....}
注: 相当于
Fragment1
的tag就是“ fragment1”,Fragment2
的tag就是“ fragment2”,但是鉴于方便管理整个应用中所有Fragment的标签,所以建议创建一个类来存贮所有fragment的标签,比如,建立一个接口类FragmentTag:public interface FragmentTag { String FRAGMENT1="fragment1"; String FRAGMENT2="fragment2"; }
那么我们的代码就可以这样写:
@TargetFragmentTag(FragmentTag.FRAGMENT1)public class Fragment1 extends Fragment { .....}@TargetFragmentTag(FragmentTag.FRAGMENT2)public class Fragment2 extends Fragment { .....}
然后,这样就能统一调度fragment了。
使用FragmentFactory来显示指定的Fragment
在activity中调用showFragment(String tag)来显示fragment:
public class MainActivity extends AppCompatActivity { private FragmentFactory mFactory; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //使用单例模式创建`FragmentFactory`对象,R.id.mContentnF1是要显示的Fragment的布局容器的id mFactory = FragmentFactory.getInstance() .init(this, R.id.mContentFl); //默认显示fragment1 mFactory.showFragment(FragmentTag.FRAGMENT1) .... }}
另外在tab切换时,同时切换Fragment的显示:
@Override public void onTabSelected(TabLayout.Tab tab) { switch (tab.getPosition()) { case 0: mFactory.showFragment(FragmentTag.FRAGMENT1); break; case 1: mFactory.showFragment(FragmentTag.FRAGMENT2); break; } }
Fragment在切换时,会自动将当前的Fragment隐藏,但并非销毁掉,下次重新切换回来时,会再将这个Fragment显示出来.其实FragmetnFactory还能实现
回退栈
的功能:回退到上一个Fragment
,同样能使用showFragment
方法,只要传入上一个Fragment的tag即可.
跨Activity使用FragmentFactory
在APP中可能有多个Activity,每个Activity中有多个Fragment,所以经常会在全局中使用FragmentFactory
(跨Activity),那也是可以的,因为FragmentFactory
在初始化时,传入了一个Activity的对象,所以在跨Activity使用时,只需要重新初始化一下即可,各个Activity中使用FragmentFactory
是相互独立的.
例如:在另一个Activity中有一个有Fragment3和Fragment4,同样地:
@TargetFragmentTag(FragmentTag.FRAGMENT3)public class Fragment3 extends Fragment { .....}@TargetFragmentTag(FragmentTag.FRAGMENT4)public class Fragment4 extends Fragment { .....}public class AnotherActivity extends AppCompatActivity { private FragmentFactory mFactory; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_another); //使用单例模式创建`FragmentFactory`对象,R.id.mContentnF2是要显示的Fragment的布局容器的id mFactory = FragmentFactory.getInstance() .init(this, R.id.mContentF2); //默认显示fragment3 mFactory.showFragment(FragmentTag.FRAGMENT3) .... } @Override public void onTabSelected(TabLayout.Tab tab) { switch (tab.getPosition()) { case 0: mFactory.showFragment(FragmentTag.FRAGMENT3); break; case 1: mFactory.showFragment(FragmentTag.FRAGMENT4); break; } }}
3.延伸
Fragment确实能减少Activity的数量,使用灵活方便,但是也会引发一些问题,例如Fragment重影问题.重影问题主要是由于Activity会保存当前视图层级引起的,在某些事件发生后,Activity会调用onSaveInstance方法进行保存状态,在重新打开activity后,Activity会调用onRestoreInstance方法恢复之前保存的状态,再切换到另外的Fragment,就会出现重影.
关于onSaveInstance和onRestoreInstance方法的时机大致是:当activity可能会被回收时,调用onSaveInstance,当activity被回收后,又重新创建一个实例后,会执行onRestoreInstance方法
重影问题主要有几种方式引发:
-
横竖屏切换
-
当任务在后台被回收,重新打开页面时
-
在开发者模式下设置 不保存活动
第一个问题解决方案:
在AndroidManifest.xml中的Activity中添加android:configChanges="keyboardHidden|orientation|screenSize"
,则可以让Activity在横竖屏切换时,不调用这些生命周期的方法即可.
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.levine.ucall"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true"> <activity android:name=".ui.activity.MainActivity" android:configChanges="keyboardHidden|orientation|screenSize"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> intent-filter> activity> application>manifest>
另外,其他情况下的解决方案,在FragmentFactory
中也给出了解决方案:
-
1.首先重写Activity的OnSaveInstance和OnRestoreInstance方法
@SuppressLint("MissingSuperCall") @Override protected void onSaveInstanceState(@NonNull Bundle outState) {//去掉super.onSaveInstanceState(outState),这是重影问题的根源 mFactory.saveCurrentFragmentInfo(outState); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { if (savedInstanceState != null) { mFactory.restoreCurrentFragmentInfo(savedInstanceState); } }
-
2.在onCreate中也调用restoreCurrentFragmentInfo方法
private FragmentFactory mFactory; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_another); //使用单例模式创建`FragmentFactory`对象,R.id.mContentnF2是要显示的Fragment的布局容器的id mFactory = FragmentFactory.getInstance() .init(this, R.id.mContentF2); if (savedInstanceState != null) { mFactory.restoreCurrentFragmentInfo(savedInstanceState); } else { //默认显示fragment1 mFactory.showFragment(FragmentTag.FRAGMENT1) } .... }
这样就解决了重影问题。
补充:虽然重影问题解决了,但是如果想保存Fragment中某些控件状态该怎么办呢?
在Fragment重写onSaveInstanceState和onViewStateRestored方法即可,比如我的Fragment中有一个两个Tab,需要保存tab的位置:
@Override public void onSaveInstanceState(@NonNull Bundle outState) { // 保存fragment的状态 super.onSaveInstanceState(outState); outState.putInt("tabPosition",tabLayout.getSelectedTabPosition()); } @Override public void onViewStateRestored(@Nullable Bundle savedInstanceState) { super.onViewStateRestored(savedInstanceState); if(savedInstanceState!=null){ //恢复fragment的状态 int position=savedInstanceState.getInt("tabPosition"); tabLayout.getTabAt(position).select(); } }
更多相关文章
- android hal层 c 堆栈打印方法
- Android应用程序开发之requestWindowFeature()
- Android(安卓)设备 USB 通用调试驱动的安装方法
- Android(安卓)EventBus3.1.1从使用到源码解析
- Android面试题总结加强版(二)
- Android(安卓)图片压缩并保存的方法
- Android开发--WebView, WebChromeClient和WebViewClient
- Android学习笔记——常用的基本UI组件及其常用属性值和相关方法
- Android(安卓)Fragment 没有onRestoreInstanceState