持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第25天,点击查看活动详情
Fragment添加的方式与单Activity+多Fragment的实现方式
谷歌一直再喊,一个App只需要一个Activity就够了,Activity少了之后,清单文件解析更快,Fragment更加轻量了,看起来很不错。但是也是有很多坑,恢复重建问题,生命周期问题,返回问题等。
而随着组件化开发的流行,我们没有那么极端的只有一个Activity,我们可以把每一个组件创建一个Activity,UI也实现组件化了,除了一些公用的Activity和多进程的Activity,我们一个组件只需要一个Activity就够了。
其实这个开发方式早在2014年左右就已经有类似的方式了。我们看看此框架的演进
一、addToBackStack的方式
首先来看看Fragment的几种添加方式
一般的添加
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.fl_fragment, mHistoryFragment).commitAllowingStateLoss();
几个Fragment切换的添加方式
FragmentTransaction beginTransaction = getSupportFragmentManager().beginTransaction();
beginTransaction.add(R.id.fl_fragment,fragmentModuleOneShow);
beginTransaction.add(R.id.fl_fragment,fragmentMmoduleTwoShow);
beginTransaction.show(fragmentModuleOneShow).hide(fragmentMmoduleTwoShow).commit();
切换的时候:就是show一个hide一个 然后提交
public void onClick(View view){
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if(view.getId() == R.id.tv_moduleone_fragment){
fragmentTransaction.show(fragmentModuleOneShow).hide(fragmentMmoduleTwoShow).commit();
}else if(view.getId() == R.id.tv_moduletwo_fragment){
fragmentTransaction.show(fragmentMmoduleTwoShow).hide(fragmentModuleOneShow).commit();
}
}
进出栈的添加方式: 调用addToBackState()方法,将这些变化加入到activity管理的back stack中去,这样用户调用返回键就可以回退这些变化了
SignUpOptionsFragment signUpOptionsFragment = new SignUpOptionsFragment();
getSupportFragmentManager()
.beginTransaction().
add(R.id.signup_fragment_container, signUpOptionsFragment)
.addToBackStack(SignUpOptionsFragment.class.getSimpleName())
.commit();
第二次替换
SignUpInputFragment signUpInputFragment = new SignUpInputFragment();
signUpInputFragment.setGoogleSignInResult(result);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.signup_fragment_container, signUpInputFragment)
.addToBackStack(SignUpInputFragment.class.getSimpleName())
.commit();
带动画效果 xml资源 动画设置一定要在前面
getFragmentManager().beginTransaction()
//设置自定义动画
.setCustomAnimations(R.animator.animator_enter,R.animator. animator_exit,R.animator.animator_enter,R.animator.animator_exit)
.addToBackStack("OtherFragment") //设置fragment加入回退栈
.replace(R.id.fragment, new OtherFragment())
.commit();
比较蛮荒的用法了,缺点太多了,无法控制生命周期,无法恢复,无法设置启动模式,无法精准回退,还会出现无法返回的Bug。当时Fragment的Bug不少。后面出现了框架对Bug进行修复。
二、Fragmentation框架
它是一个对 fragment 进行管理的三方库,可以方便地对 fragment 进行 进栈、出栈、显示隐藏等操作,只需要调用简单的 api;对 fragment 的生命周期进行了拓展;方便的实现单 Activity + 多 Fragment 的 app 架构。帮你简化使用过程,轻松解决各种复杂嵌套等问题,修复了官方Fragment库存在的一些BUG。源码在此。(已停止维护)
它大致原理是把我们的 Activity 和 Fragment 包装一层。当我们在 Activity 或 Fragment 中直接调用它的 API 的时候,它通过对应的代理类 SupportActivityDelegate 和 SupportFragmentDelegate,把代码逻辑转换到代理类中;而这两个代理类中都持有 TransactionDelegate
的实例,对 fragment 的操作逻辑又都转移到 TransactionDelegate
中去完成
TransactionDelegate
为核心类 ,加载根Fragment、加载多个根Fragment、替换根Fragment、子Fragment之间的替换、隐藏显示Fragment、启动目标Fragment、得到位于栈顶Fragment、获取栈内的Fragment、Fragment出栈、出栈到目标fragment、获取栈顶的子Fragment、获取当前Fragment的前一个Fragment、子栈内Fragment出栈 等等操作
使用起来也是很简单,封装的很好
Fragmentation.builder()
.stackViewMode(Fragmentation.BUBBLE)
.debug(BuildConfig.DEBUG)
.handleException(new ExceptionHandler() {
@Override
public void onException(@NonNull Exception e) {
e.printStackTrace();
}
})
.install();
//使用的时候我们需要指定 SupportActivity 和 SupportFragment 即可
public class YYJobsAuthActivity extends SupportActivity {
if (findFragment(EmployeeLoginFragment.class) == null) {
loadRootFragment(R.id.fl_content, EmployeeLoginFragment.newInstance());
}
}
public class EmployeeLoginFragment extends SupportFragment{
//常用方法
// 启动目标Fragment,并关闭当前Fragment
startWithPop(SupportFragment fragment)
// 以某种启动模式,启动新的Fragment
start(SupportFragment fragment, int launchMode)
// 出栈targetFragment之上的所有Fragments
popTo(Class targetFragment, boolean includeTargetFragment);
// 隐藏软键盘 一般用在hide时
hideSoftInput();
// 显示软键盘,调用该方法后,会在onPause时自动隐藏软键盘
showSoftInput(View view);
// show一个Fragment,hide一个Fragment; 主要用于类似微信主页那种 切换tab的情况
showHideFragment(SupportFragment showFragment, SupportFragment hideFragment);
}
缺点:
- 已经没有维护了,不支持AndroidX 在 androidx.activity.1.1.0 版本是可以用的,但是升级到 androidx.activity.1.2.0 以上就会报错。
- 需要包装 Fragment 和 Activity 而普通的 Fragment 和 SupportActivity不能通用,不兼容。
三、AndroidX Navigation框架
Jectpack工具开发出了 Navigation,现在已经逐步成为主流的单 Activity 应用了,毕竟谷歌自家的 App 都是用这个实现单Activity+多Fragment。
//androidx
implementation "androidx.fragment:fragment-ktx:1.2.0"
implementation "androidx.navigation:navigation-fragment-ktx:2.3.0"
implementation "androidx.navigation:navigation-ui-ktx:2.3.0"
使用的时候需要 Activity 定义 xml
<?xml version="1.0" encoding="utf-8"?>
<android.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=".simple.SimpleActivity">
<fragment
android:id="@+id/frag_nav_simple"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/nav_simple"
app:defaultNavHost="true" />
</android.support.constraint.ConstraintLayout>
navGraph
核心是这个导航图,需要我们自己定义XML
大致如下所示:
<?xml version="1.0" encoding="utf-8"?>
<navigation 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"
app:startDestination="@id/page1Fragment"> //这里是默认显示的fragmentid
<fragment
android:id="@+id/page1Fragment" //用于跳转的fragmentid
android:name="com.guadou.testnavigation.Fragment1" //全路径
android:label="fragment_page1" //提示信息,没什么大用,可以在toolbar上展示
tools:layout="@layout/fragment_page1"> //fragment的布局文件
<action //是fragment的事件
android:id="@+id/action_page2" //fragment的事件id,用于View的点击事件跳转
app:destination="@id/page2Fragment" /> //destination是目的地的意思,跳转到哪个fragmentid
</fragment>
<fragment
android:id="@+id/page2Fragment"
android:name="com.guadou.testnavigation.Fragment2"
android:label="fragment_page2"
tools:layout="@layout/fragment_page2">
<action
android:id="@+id/action_page1"
app:popUpTo="@id/page1Fragment" /> //popuTo 返回到哪一个fragmentid
<action
android:id="@+id/action_page3"
app:destination="@id/nav_graph_page3" />
</fragment>
<navigation
android:id="@+id/nav_graph_page3"
app:startDestination="@id/page3Fragment">
<fragment
android:id="@+id/page3Fragment"
android:name="com.guadou.testnavigation.Fragment3"
android:label="fragment_page3"
tools:layout="@layout/fragment_page3" />
</navigation>
</navigation>
在我们跳转的时候就需要拿到我们定义好的这些action来进行操作:
public class Fragment2 extends Fragment {
private Button mBtn_jump_to3;
private Button mBtn_jump_to1;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View inflate = inflater.inflate(R.layout.fragment_page2, container, false);
mBtn_jump_to1 = inflate.findViewById(R.id.btn_jump_to1);
mBtn_jump_to3 = inflate.findViewById(R.id.btn_jump_to3);
return inflate;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mBtn_jump_to1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//返回,返回键也是这样的功能
Navigation.findNavController(view).navigateUp();
}
});
mBtn_jump_to3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//跳转,可以写道Bulde数据跳转
Navigation.findNavController(view).navigate(R.id.action_page3);
}
});
}
}
当然我们还能给这个action指定动画。
<?xml version="1.0" encoding="utf-8"?>
<navigation 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"
app:startDestination="@id/page1Fragment">
<fragment
android:id="@+id/page1Fragment"
android:name="com.guadou.testnavigation.Fragment1"
android:label="fragment_page1"
tools:layout="@layout/fragment_page1">
<action
android:id="@+id/action_page2"
app:destination="@id/page2Fragment"
app:enterAnim="@anim/open_enter"
app:exitAnim="@anim/pre_close_exit"
app:popEnterAnim="@anim/pre_open_enter"
app:popExitAnim="@anim/close_exit"/>
</fragment>
<fragment
android:id="@+id/page2Fragment"
android:name="com.guadou.testnavigation.Fragment2"
android:label="fragment_page2"
tools:layout="@layout/fragment_page2">
<action
android:id="@+id/action_page3"
app:destination="@id/page3Fragment"
app:enterAnim="@anim/open_enter"
app:exitAnim="@anim/pre_close_exit"
app:popEnterAnim="@anim/pre_open_enter"
app:popExitAnim="@anim/close_exit"/>
</fragment>
<fragment
android:id="@+id/page3Fragment"
android:name="com.guadou.testnavigation.Fragment3"
android:label="fragment_page3"
tools:layout="@layout/fragment_page3" >
</fragment>
</navigation>
我自己都不想再写了xml,太复杂了,如果一个项目中有100个页面,相信我你会崩溃的。
Demo的示例如下,能实现跳转,但是Fragment实例没有被保存,输入框里面的东西都没了。
好了,总结下Navigation的优缺点
优点
- Activity和Fragment完全兼容,无需包装类。
- 谷歌出品支持最新AndroidX。
- 基于导航图可以实现随意的跳转。
缺点
- 默认replace实现,无法恢复上一个页面状态。
- xml定义的导航图太过复杂,大型项目简直痛苦。
- 生命周期回调有问题,因为replace的原因,无法真正回调 onStart onResume 等生命周期。
- 返回事件无法拦截需要自定义
篇幅原因,后面一期会出Navigation的封装版本,解决 Navigation 上的一些缺点。让 Navigation 在实战中真正好用起来。
更新:Navigation的封装!