牛逼!像使用Activity一樣使用Fragment
BAT作者:fundroid_方卓
https://blog.csdn.net/vitaviva
前言
– Jake Wharton @Droidcon NYC 2017
https://github.com/vitaviva/fragivity
https://github.com/YoKeyword/Fragmentation
Use Fragment Like Activity
生命周期與Activity行為一致 支持多種LaunchMode 支持OnBackPressed事件處理、支持SwipeBack 支持Transition、SharedElement等轉(zhuǎn)場(chǎng)動(dòng)畫 支持以Dialog樣式顯示 支持Deep Links
基本使用
gradle依賴
implementation 'com.github.fragivity:core:$latest_version'
聲明NavHostFragment
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:fitsSystemWindows="true">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true" />
</FrameLayout>
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host) as NavHostFragment
navHostFragment.loadRoot(HomeFragment::class)
}
}
頁面跳轉(zhuǎn)
//跳轉(zhuǎn)到目標(biāo)Fragment
navigator.push(DestinationFragment::class)
//攜帶參數(shù)跳轉(zhuǎn)
val bundle = bundleOf(KEY_ARGUMENT to "some args")
navigator.push(DestinationFragment::class, bundle)
頁面返回
//返回上一頁面
navigator.pop()
//返回到指定頁面
navigator.popTo(HomeFramgent::class)
轉(zhuǎn)場(chǎng)動(dòng)畫
navigator.push(UserProfile::class, bundle) { //this:NavOptions
//配置動(dòng)畫
enterAnim = R.anim.enter_anim
exitAnim = R.anim.exit_anim
popEnterAnim = R.anim.enter_anim
popExitAnim = R.anim.exit_anim
}
//跳轉(zhuǎn)時(shí),對(duì)imageView設(shè)置SharedElement
navigator.push(UserProfile::class, bundle,
FragmentNavigatorExtras(imageView to "imageView")) { //this:NavOptions
enterAnim = R.anim.enter_anim
exitAnim = R.anim.exit_anim
popEnterAnim = R.anim.enter_anim
popExitAnim = R.anim.exit_anim
}
class UserProfile : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//目標(biāo)Fragment中設(shè)置共享元素動(dòng)畫
sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
}
}
無需配置實(shí)現(xiàn)頁面跳轉(zhuǎn)
<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/first">
<fragment
android:id="@+id/fragment_first"
android:name=".FirstFagment"
android:label="@string/tag_first">
<action
android:id="@+id/action_to_second"
app:destination="@id/fragment_second"/>
</fragment>
<fragment
android:id="@+id/fragment_second"
android:name=".SecondFragment"
android:label="@string/tag_second"/>
</navigation>
動(dòng)態(tài)創(chuàng)建Graph
fun NavHostFragment.loadRoot(root: KClass<out Fragment>) {
navController.apply {
//添加Navigator
navigatorProvider.addNavigator(
FragivityNavigator(
context,
childFragmentManager,
id
)
)
//創(chuàng)建Graph
graph = createGraph(startDestination = startDestId) {
val startDestId = root.hashCode()
//添加startDestination
destination(
FragmentNavigatorDestinationBuilder(
provider[FragivityNavigator::class],
startDestId,
root
))
}
}
}
動(dòng)態(tài)添加Destination
fun NavHost.push(
clazz: KClass<out Fragment>,
args: Bundle? = null,
extras: Navigator.Extras? = null,
optionsBuilder: NavOptions.() -> Unit = {}
) = with(navController) {
// 動(dòng)態(tài)創(chuàng)建Destination
val node = putFragment(clazz)
// 調(diào)用NavController的navigate方法進(jìn)行跳轉(zhuǎn)
navigate(
node.id, args,
convertNavOptions(clazz, NavOptions().apply(optionsBuilder)),
extras
)
}
// 創(chuàng)建并添加Destination
private fun NavController.putFragment(clazz: KClass<out Fragment>): FragmentNavigator.Destination {
val destId = clazz.hashCode()
lateinit var destination: FragmentNavigator.Destination
if (graph.findNode(destId) == null) {
destination = (FragmentNavigatorDestinationBuilder(
navigatorProvider[FragivityNavigator::class],
destId,
clazz
)).build()
graph.plusAssign(destination)// 添加進(jìn)Graph
} else {
destination = graph.findNode(destId) as FragmentNavigator.Destination
}
return destination
}
BackStack及生命周期

ActivityA:onStart -> onResume | |
FragmentA : no change | |
FragmentA: onCreateView -> onStart -> onResume |
目標(biāo)1:回退時(shí),F(xiàn)ragmentB不重新onCreateView (add方式滿足) 目標(biāo)2:回退時(shí),F(xiàn)ragmentB會(huì)觸發(fā)onStart -> onResume (replace方式滿足) 目標(biāo)3:后臺(tái)的Fragment不跟隨父生命周期發(fā)生變化 (replace方式滿足)
重寫FragmentNavigator
@Navigator.Name("fragment")
public class FragmentNavigator extends Navigator<FragmentNavigator.Destination> {
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
String className = destination.getClassName();
//實(shí)例化Fragment
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.replace(mContainerId, frag); // replace方式添加Fragment
ft.setPrimaryNavigationFragment(frag);
//事務(wù)壓棧
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
ft.setReorderingAllowed(true);
ft.commit();
}
}
public class FragivityNavigator extends FragmentNavigator {
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
//ft.replace(mContainerId, frag); // replace改為add
ft.add(mContainerId, frag, generateBackStackName(mBackStack.size(), destination.getId()));
}
}
添加OnBackStackChangedListener
private final FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener =
new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
if (mIsPendingAddToBackStackOperation) {
mIsPendingAddToBackStackOperation = !isBackStackEqual();
if (mFragmentManager.getFragments().size() > 1) {
// 切到后臺(tái)時(shí)的生命周期
Fragment fragment = mFragmentManager.getFragments().get(mFragmentManager.getFragments().size() - 2);
if (fragment instanceof ReportFragment) {
fragment.performPause();
fragment.performStop();
((ReportFragment) fragment).setShow(false);
}
}
} else if (mIsPendingPopBackStackOperation) {
mIsPendingPopBackStackOperation = !isBackStackEqual();
// 回到前臺(tái)時(shí)的生命周期
Fragment fragment = mFragmentManager.getPrimaryNavigationFragment();
if (fragment instanceof ReportFragment) {
((ReportFragment) fragment).setShow(true);
fragment.performStart();
fragment.performResume();
}
}
}
};
ReportFragment代理
//ReportFragment
internal class ReportFragment : Fragment() {
internal lateinit var className: String
private val _real: Class<out Fragment> by lazy {
Class.forName(className) as Class<out Fragment>
}
private val _realFragment by lazy { _real.newInstance() }
override fun onAttach(context: Context) {
super.onAttach(context)
//將目標(biāo)Framgent作為child進(jìn)行管理
mChildFragmentManager.beginTransaction().apply {
_realFragment.arguments = arguments
add(R.id.container, _realFragment)
commitNow()
}
}
}
//ReportFragmentManager
internal class ReportFragmentManager : FragmentManager() {
//isShow:在后臺(tái)時(shí),不響應(yīng)生命周期分發(fā)
internal var isShow = true
public override fun dispatchResume() {
if (isShow) super.dispatchResume()
}
//...
}
支持Launch Mode
navigator.push(LaunchModeFragment::class, bundle) { //this: NavOptions
launchMode = LaunchMode.STANDARD // 默認(rèn)可省略
//launchMode = LaunchMode.SINGLE_TOP
//launchMode = LaunchMode.SINGLE_TASK
}
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
final Fragment preFrag = mFragmentManager.getPrimaryNavigationFragment();
//當(dāng)以singleTop啟動(dòng)時(shí)
if (isSingleTopReplacement) {
if (mBackStack.size() > 1) {
ft.remove(preFrag);// 刪除舊實(shí)例
//更新FragmentTransaction中的實(shí)例信息
frag.mTag = generateBackStackName(mBackStack.size() - 1, destination.getId());
if (mFragmentManager.mBackStack.size() > 0) {
List<FragmentTransaction.Op> ops =
mFragmentManager.mBackStack.get(mFragmentManager.mBackStack.size() - 1).mOps;
for (FragmentTransaction.Op op : ops) {
if (op.mCmd == OP_ADD && op.mFragment == preFrag) {
op.mFragment = frag;
}
}
}
}
}
}
Fragment通信
//SourceFragment
val cb = { it : Boolean ->
//...
}
navigator.push {
DestinationFragment(cb)
}
//Destination
class DestinationFragment(val cb:(Boolean) -> Unit) {...}
inline fun <reified T : Fragment> NavHost.push(
noinline optionsBuilder: NavOptions.() -> Unit = {},
noinline block: () -> T
) {
//...
push(T::class, optionsBuilder)
}
支持DeepLinks
在編譯期通過kapt解析注解,獲取URI信息,并與Fragment相關(guān)聯(lián) 在Activity的入口處攔截Intent,解析URI并跳轉(zhuǎn)到相關(guān)聯(lián)的Fragment
添加kapt依賴
kapt 'com.github.fragivity:processor:$latest_version'
配置URI
const val URI = "myapp://fragitiy.github.com/"
@DeepLink(uri = URI)
class DeepLinkFragment : AbsBaseFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_deep_link, container, false)
}
}
處理Intent
//MainActivity#onCreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host) as NavHostFragment
navHostFragment.handleDeepLink(intent)
}
//NavController
public void navigate(@NonNull Uri deepLink) {
navigate(new NavDeepLinkRequest(deepLink, null, null));
}
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("myapp://fragitiy.github.com/"))
startActivity(intent)
OnBackPressed事件攔截
https://developer.android.com/guide/navigation/navigation-custom-back
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requireActivity().onBackPressedDispatcher.addCallback( this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// 攔截back鍵事件
}
})
}
back鍵返回與pop()返回
//NavHostFragment#onCreate
public void onCreate(@Nullable Bundle savedInstanceState) {
//...
mNavController = new NavHostController(context);
mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
//...
}
//NavController#setOnBackPressedDispatcher
void setOnBackPressedDispatcher(@NonNull OnBackPressedDispatcher dispatcher) {
if (mLifecycleOwner == null) {
throw new IllegalStateException("You must call setLifecycleOwner() before calling "
+ "setOnBackPressedDispatcher()");
}
// Remove the callback from any previous dispatcher
mOnBackPressedCallback.remove();
// Then add it to the new dispatcher
dispatcher.addCallback(mLifecycleOwner, mOnBackPressedCallback);
}
//NavController#mOnBackPressedCallback
private final OnBackPressedCallback mOnBackPressedCallback =
new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
popBackStack(); // 最終回調(diào)Navigator#popBackStack
}
};
SwipeBack
class SwipeBackFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_swipe_back, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
swipeBackLayout.setEnableGesture(true) //一句話開啟SwipeBack
}
}
val Fragment.swipeBackLayout
get() = (parentFragment as ReportFragment).swipeBackLayout
internal class ReportFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
swipeBackLayout =
SwipeBackLayout(requireContext()).apply {
attachToFragment(
this@ReportFragment,
inflater.inflate(R.layout.report_layout, container, false)
.apply { appendBackground() } // add a default background color to make it opaque
)
setEnableGesture(false) //default false
}
return swipeBackLayout
}
private fun View.appendBackground() {
val a: TypedArray =
requireActivity().theme.obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))
val background = a.getResourceId(0, 0)
a.recycle()
setBackgroundResource(background)
}
ShowDialog
定義DialogFragment
class DialogFragment : DialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_dialog, container, false)
}
}
顯示Dialog
navigator.showDialog(DialogFragment::class)
//創(chuàng)建Destination
val destination = DialogFragmentNavigatorDestinationBuilder(
navigatorProvider[DialogFragmentNavigator::class],
destId, clazz ).apply {
label = clazz.qualifiedName
}.build()
//添加到Graph
graph.plusAssign(destination)
最后

https://github.com/vitaviva/fragivity
推薦閱讀
? 耗時(shí)2年,Android進(jìn)階三部曲第三部《Android進(jìn)階指北》出版!
? 『BATcoder』做了多年安卓還沒編譯過源碼?一個(gè)視頻帶你玩轉(zhuǎn)!
BATcoder技術(shù)群,讓一部分人先進(jìn)大廠
大家好,我是劉望舒,騰訊云最具價(jià)值專家TVP,著有暢銷書《Android進(jìn)階之光》《Android進(jìn)階解密》《Android進(jìn)階指北》,蟬聯(lián)四屆電子工業(yè)出版社年度優(yōu)秀作者,谷歌開發(fā)者社區(qū)特邀講師,百度百科收錄的技術(shù)專家。
前華為面試官,現(xiàn)大廠技術(shù)負(fù)責(zé)人。
想要加入 BATcoder技術(shù)群,公號(hào)回復(fù)BAT 即可。
為了防止失聯(lián),歡迎關(guān)注我的小號(hào)
微信改了推送機(jī)制,真愛請(qǐng)星標(biāo)本公號(hào)??
評(píng)論
圖片
表情
