Fragivity:像使用Activity一样使用Fragment

简介: 近年来,SPA,即单Activity架构逐渐开始受到欢迎,随之而生了很多优秀的三方库,大部分是基于Fragment作为实现方案,Fragivity 使用 Fragment + Navigatiion 打造最好用的 SPA 框架
" An app only needs an Activity, you can use Fragments, just don't use the backstack with fragments "
<br/> -- Jake Wharton @Droidcon NYC 2017

近年来,SPA,即单Activity架构逐渐开始受到欢迎,随之而生了很多优秀的三方库,大部分是基于Fragment作为实现方案,其中最有代表性的就是Fragmentation了,后来Jetpack Navigation的诞生也标志着Google从官方立场对SPA架构的肯定。

Navigation的出现并没有加速Fragment对Activity的全面取代,一个重要原因是其过于依赖配置(NavGraph),丧失了Activity的灵活性。这一点上Fragmentation做的不错,有接近Activity的使用体验,可惜其不支持Kotlin,且早已停止维护,无法使用近年来在AndroidX中引入的各种新特性。

是否有一个工具,既具备Fragmentation那样灵活性,又能像Navigation那样兼容AndroidX中的新功能呢?
Fragivity正是在这个背景下诞生的:https://github.com/vitaviva/fragivity
<br/>

Fragivity : Use Fragment like Activity


顾名思义,Fragivity希望让Fragment具备Activity一样的使用体验,从而在各种场景中能真正取而代之:

15f716005c78487f943fe3191f98b6ed~tplv-k3u1fbpfcp-watermark.image

  • 生命周期与Activity行为一致
  • 支持多种LaunchMode
  • 支持OnBackPressed事件处理、支持SwipeBack
  • 支持Transition、SharedElement等转场动画
  • 支持以Dialog样式显示
  • 支持Deep Links

Fragivity底层基于Navigation实现,同时兼具Fragmentation的灵活性,无需配置NavGraph即可实现画面跳转。简单对比一下三者的区别:

Fragmentation Navigation Fragivity
自由跳转 yes no (依赖NavGraph) yes
Launch Mode 3种 2种 3种
支持Deep Links no yes(依赖NavGraph) yes(使用注解)
kotlin友好 no yes yes
生命周期 与Activity不一致(add方式) 与Activity不一致(replace方式) 与Activity一致
Fragment间通信 startFragmentForResult viewmodel viewmodel、callback、resultapi等多种方式
过场动画 View Animation Transition Animation Transition Animation
Swipe Back yes(依赖基类) no yes (无需基类)
支持Dialog显示 no yes yes
OnBackPressed拦截 yes (依赖基类) yes(无需基类) yes(无需基类)

通过对比可以发现,比起前两者Fragivity在多个维度上与Activity的行为更加一致。

<br/>

1. 基本使用


Fragivity的接入成本很低。

1.1 gradle依赖

implementation 'com.github.fragivity:core:$latest_version'

1.2 声明NavHostFragment

像Navigation一样,Fragivity需要NavHostFragment作为Parent,然后在ChildFragment之间实现页面跳转。

我们在xml中声明NavHostFragment

frameLabelStart--frameLabelEnd 

1.3 加载首页

通常我们需要定义一个MainActivity作为入口,同样,这里通过loadRoot加载一个初始的Fragment:

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)

    }
}

1.4 页面跳转

接下来便可以在Fragment之间进行跳转了

//跳转到目标Fragment
navigator.push(DestinationFragment::class)

//携带参数跳转
val bundle = bundleOf(KEY_ARGUMENT to "some args")
navigator.push(DestinationFragment::class, bundle)

//可以通过尾lambda设置参数,好处是还可以进行其他配置
navigator.push(DestinationFragment::class) {
    arguments = bundle //设置参数
    launchMade = ... //更多其他设置
    
}

1.5 页面返回

通过pop方法可以返回上一页面

//返回上一页面
navigator.pop()

//返回到指定页面
navigator.popTo(HomeFramgent::class)

1.6 转场动画

基于Navigation的能力,在画面跳转时可以设置Transition动画

navigator.push(UserProfile::class, bundle) { //this:NavOptions
    //配置动画
    enterAnim = R.anim.enter_anim
    exitAnim = R.anim.exit_anim
    popEnterAnim = R.anim.enter_anim
    popExitAnim = R.anim.exit_anim
}

借助FragmentNavigatorExtras还可以设置SharedElement,实现更优雅地动画效果

//跳转时,对imageView设置SharedElement
navigator.push(UserProfile::class, bundle) { //this:NavOptions
                
    enterAnim = R.anim.enter_anim
    exitAnim = R.anim.exit_anim
    popEnterAnim = R.anim.enter_anim
    popExitAnim = R.anim.exit_anim
    
    //配置共享元素
    sharedElements = sharedElementsOf(imageView to "iv_id")
       
}
class UserProfile : Fragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //目标Fragment中设置共享元素动画
        sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
    }

}

5f12670c31d44c98bfbffa51a742f524~tplv-k3u1fbpfcp-zoom-1.image

<br/>

2. 无需配置实现页面跳转


Navigation需要配置NavGraph才能实现页面间跳转,例如:

<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>

每个<navigation/>对应一个NavGraph对象,<fragment/>会对应到NavGraph中的各个DestinationNavController持有NavGraph通过控制Destination之间的跳转。

依赖配置的页面跳转,无法做到像Activity那样灵活。Fragivity通过动态构建NavGraph,无需配置即可实现跳转:

2.1 动态创建Graph

加载首页时,动态创建Graph

fun NavHostFragment.loadRoot(root: KClass<out Fragment>) {

    navController.apply {
        //添加Navigator
        navigatorProvider.addNavigator(
            FragivityNavigator(
                context,
                childFragmentManager,
                id
            )
        )
        
        //创建Graph
        graph = createGraph(startDestination = startDestId) {
            val startDestId = root.hashCode()
            //添加startDestination
            destination(
                FragmentNavigatorDestinationBuilder(
                    provider[FragivityNavigator::class],
                    startDestId,
                    root
                ))
        }
    }
}

FragivityNavigator负责处理页面跳转的逻辑,后文会单独介绍。Graph创建后添加startDestination用来加载首页。

2.2 动态添加Destination

除startDestination以外,每当跳转到新页面,都需要为Graph动态添加此Destination:

fun NavHost.push(
    clazz: KClass<out Fragment>,
    args: Bundle? = null,
    extras: Navigator.Extras? = null,
    optionsBuilder: NavOptions.() -> Unit = {}
) = with(navController) {
    // 动态创建Destination
    val node = putFragment(clazz)
    // 调用NavController的navigate方法进行跳转
    navigate(
        node.id, args,
        convertNavOptions(clazz, NavOptions().apply(optionsBuilder)),
        extras
    )
}

// 创建并添加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)// 添加进Graph
    } else {
        destination = graph.findNode(destId) as FragmentNavigator.Destination
    }
    return destination
}

创建Destination后,通过NavController的navigate方法跳转到此Destination。

<br/>

3. BackStack及生命周期


如J神所说,Fragment无法很好替代Activity的原因之一是在BackStack管理上的差异,这会影响到生命周期的不同。

设想以下场景: A页面 > (启动)> B页面 >(返回)> A页面

41fa65677d364305b45a96b176db6b39~tplv-k3u1fbpfcp-zoom-1.image

我们知道添加Fragment一般有两种方式:addreplace。无论哪种方式其在画面跳转时的生命周期与Activity都不相同:

页面B的启动方式 从B返回时的生命周期
Activity ActivityB:onPasue -> onStop -> onDestroy <br/> ActivityA:onStart -> onResume
Fragment(add ) FragmentB : onPause -> onStop -> onDestroy <br/> FragmentA : no change
Fragment(replace) FragmentB: onPause -> onStop -> onDestroy <br/> FragmentA: onCreateView -> onStart -> onResume

如果希望在画面跳转时Fragment的生命周期与Activity行为一致,则至少需要达成以下三个目标:

  • 目标1:回退时,FragmentB不重新onCreateView (add方式满足)
  • 目标2:回退时,FragmentB会触发onStart -> onResume (replace方式满足)
  • 目标3:后台的Fragment不跟随父生命周期发生变化 (replace方式满足)

无论Navigation还是Fragmentation都不能同时满足上面三条。

3.1 重写FragmentNavigator

NavController通过FragmentNavigator实现具体的跳转逻辑,FragmentNavigator是Navigator的派生类,专门负责FragmentNavigator.Destination类型的跳转。

navigate()实现了Fragment跳转的具体逻辑,其核心代码如下

@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();
        
        //实例化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);
  
        //事务压栈
        ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
            
        ft.setReorderingAllowed(true);
        ft.commit();
        
    }

}

FragmentNavigator通过replace进行Fragment跳转,前面分析我们知道这在回退时会重新onCreateView,不符合预期。我们实现子类FragivityNavigator,重写navigate()方法,将replace改为add,避免重新onCreateView,达成“目标1”

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()));
        
    }
}

3.2 添加OnBackStackChangedListener

在合适的时机为FragmentManger添加OnBackStackChangedListener,当监听到backstack变化时,手动触发生命周期回调,达成“目标2”

private final FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener =
        new FragmentManager.OnBackStackChangedListener() {

            @Override
            public void onBackStackChanged() {
                if (mIsPendingAddToBackStackOperation) {
                    mIsPendingAddToBackStackOperation = !isBackStackEqual();

                    if (mFragmentManager.getFragments().size() > 1) {
                        // 切到后台时的生命周期
                        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();
                    // 回到前台时的生命周期
                    Fragment fragment = mFragmentManager.getPrimaryNavigationFragment();
                    if (fragment instanceof ReportFragment) {
                        ((ReportFragment) fragment).setShow(true);
                        fragment.performStart();
                        fragment.performResume();
                    }
                }
            }
        };

3.3 ReportFragment代理

为了达成“目标3”, 在实例化Fragment时,为其创建ReportFragment作为代理。所谓代理其实是通过ParentFragment对内进行生命周期的分发和控制:

//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)
        //将目标Framgent作为child进行管理
        mChildFragmentManager.beginTransaction().apply {
            _realFragment.arguments = arguments
            add(R.id.container, _realFragment)
            commitNow()
        }
    }

}
//ReportFragmentManager
internal class ReportFragmentManager : FragmentManager() {
    //isShow:在后台时,不响应生命周期分发
    internal var isShow = true
    public override fun dispatchResume() {
        if (isShow) super.dispatchResume()
    }

    //...
}

<br/>

4. 支持Launch Modes


Fragivity支持三种Launch Modes:StandardSingleTopSingleTask

启动方式非常简单:

navigator.push(LaunchModeFragment::class, bundle) { //this: NavOptions
    launchMode = LaunchMode.STANDARD // 默认可省略
    //or LaunchMode.SINGLE_TOP, LaunchMode.SINGLE_TASK
}

这里着重介绍一下SingleTop的实现。Navigation也支持SingleTop,但是在Navigator中完成的,由于我们重写了Navigator(replace改为add),因此对SingleTop的实现也要做相应调整:

@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
    
    final Fragment preFrag = mFragmentManager.getPrimaryNavigationFragment();
    
    //当以singleTop启动时
    if (isSingleTopReplacement) {
            if (mBackStack.size() > 1) {
                ft.remove(preFrag);// 删除旧实例
                
                //更新FragmentTransaction中的实例信息
                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;
                        }
                    }
                }
            }
        } 
}

S​ingleTop要求当栈顶类型和目标类型相同时只能存在一个实例,所以需要删除旧实例避免重复添加。同时为了保证BackStack回退时的事务行为正常,需要将添加旧实例的事务中的相关信息更新为新实例。

<br/>

5. Fragment间通信


Fragivity支持androidx.fragment的所有通信方式,例如使用ViewModel,或者使用ResultApi(Fragment 版本高于1.3.0-beta02)等。除此之外,Fragivity提供了更简单的基于Callback的通信方式:

//SourceFragment
val cb = { it : Boolean -> 
    //...
}
navigator.push {
    DestinationFragment(cb)
}

//Destination
class DestinationFragment(val cb:(Boolean) -> Unit) {...}

以前Fragment如果必须使用无参的构造函数,否则打包时会出错。感谢AndroidX带来的进步,目前已经取消了此限制,允许自定义带参数的构造函数。因此我们可以通过lambda动态创建Fragment并将callback作为构造参数传入。

inline fun <reified T : Fragment> NavHost.push(
    noinline optionsBuilder: NavOptions.() -> Unit = {},
    noinline block: () -> T
) {
    //...
    push​(T::class, optionsBuilder)
}

如上,其内部仍然是使用Fragment的Class作为参数进行跳转,只是借助kotlin的reified特性,获取了泛型的Class信息而已。

<br/>

6. 支持Deep Links


Activity可以通过URI隐式启动,为了覆盖此类使用场景,需要为Fragment提供Deep Links支持。Navigation在NavGraph中为Destination配置URI信息;Fragivity虽然没有NavGraph,但可以通过注解配置URI。基本思想类似于ARouter的路由原理:

  1. 在编译期通过kapt解析注解,获取URI信息,并与Fragment相关联
  2. 在Activity的入口处拦截Intent,解析URI并跳转到相关联的Fragment

6.1 添加kapt依赖

kapt 'com.github.fragivity:processor:$latest_version'

6.2 配置URI

定义Fragment时,使用@DeepLink配置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)
    }
}

6.3 处理Intent

在MainActivity入口处,处理Intent中的URI

//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)

}

handleDeepLink内部最终会调用NavController的相关方法对URI进行解析:

//NavController
public void navigate(@NonNull Uri deepLink) {
    navigate(new NavDeepLinkRequest(deepLink, null, null));
}

之后,我们就可以从APP外部通过URI的方式跳转到目标Fragment了:

val intent = Intent(Intent.ACTION_VIEW, Uri.parse("myapp://fragitiy.github.com/"))
startActivity(intent)

<br/>

7. OnBackPressed事件拦截

Fragment没有Activity的OnBackPressed方法,Fragmentation通过继承的方式增加了onBackPressedSupport方法,但这会引入新的基类,对业务代码的侵入性较高。

Fragivity基于androidx.activityOnBackPressedDispatcher,以更加无侵的方式拦截back键事件。OnBackPressedDispatcher通过责任链模式保证了back事件消费的顺序,同时感知Lifecycle,在适当的时机自动注销,避免泄露。
参考: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()返回

Fragivity提供pop方法,通过代码实现返回,其内部最终会调用Navigator#popBackStack。为了保证回退逻辑统一,我们希望back键回退时也由popBackStack统一处理。Navigation通过NavHostFragment进行了实现:

//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(); // 最终回调Navigator#popBackStack
    }
};

<br/>

8. SwipeBack


Navigation没有提供滑动返回的能力,我们从Fragmentation中找到解决方案:onCreateView的时候,将SwipeLayout作为Container容器

使用方式非常简单:

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
    }

}

借助ReportFragment代理,避免了额外基类的引入。swipeBackLayout是扩展属性,实际获取的是parentFragment(ReportFragment)的实例

val Fragment.swipeBackLayout
    get() = (parentFragment as ReportFragment).swipeBackLayout

ReportFragment中的处理非常简单,将SwipeLayout作为Container即可

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
    }

为了避免滑动过程中的背景穿透,调用applyBackgroud()为Fragment添加与当前主题相同的默认背景色

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)
}

e25db2d44831450082c86a09a0fec7f4~tplv-k3u1fbpfcp-zoom-1.image

<br/>

9. Show Dialog


Activity通过设置Theme可以以Dialog样式启动,使用DialogFragment同样可以实现Dialog样式的Fragment。Navigation对DialogFragment已经做了支持,Fragivity只要调用相关方法即可:

9.1 定义DialogFragment

class DialogFragment : DialogFragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_dialog, container, false)
    }
}

9.2 显示Dialog

navigator.showDialog(DialogFragment::class)

DialogFramgent也需要在Graph上动态添加Destination,只是与普通的Fragment有所区别,其配套的Navigator类型是DialogFragmentNavigator

//创建Destination
val destination = DialogFragmentNavigatorDestinationBuilder(
       navigatorProvider[DialogFragmentNavigator::class],
       destId, clazz ).apply {
            label = clazz.qualifiedName
       }.build()

//添加到Graph      
graph.plusAssign(destination)

<br/>

最后


Fragivity在核心逻辑上力求最大程度复用Navigation的能力,并保持与最新版本同步,这有利于保证框架的先进性和稳定性。同时Fragivity致力于打造与Activity相近的使用体验,以帮助开发者更低成本地转向单Activity架构。

工程源码中有本文介绍的各种API的demo,欢迎大家下载体验,提issue,觉得好用别忘了start
https://github.com/vitaviva/fragivity

3b45b45ee6434f31be981f554f5ce0d4~tplv-k3u1fbpfcp-zoom-1.image

目录
相关文章
|
25天前
|
XML 监控 Android开发
Activity详解2
Activity详解
30 1
|
25天前
|
存储
Activity详解1
Activity详解
28 0
|
9月前
|
XML 缓存 Android开发
QMUI实战(二)—Activity 和 Fragment,我们该选择谁?
在一开始,官方只提供了 Activity 来作为 UI 界面的载体,因此我们也别无选择,只能用它。而在 Android 3.0 后,Fragment 也面世了,它一开始是用于适配平板的,以邮件列表与详情的适配为例,手机端够小,因此开始展示列表,点击进入详情,而平板够大,则可以列表显示在左侧,详情显示在右侧,点击列表只是切换详情。对于这种适配场景,列表页和详情页必须在同一个 Activity 里了,而这便是我所知道的 Fragment 诞生的场景了。
116 0
|
11月前
|
XML API Android开发
Fragment初识
Fragment初识
54 0
|
Java 调度 Android开发
Activity
前言:这世界所有的惊喜与好运,都是你积攒的温柔与善良。 1.Activity,Window与View的关系 下面是自己查阅资料,看了下一点源码的归纳所得,如果哪写错了欢迎指出!下面贴下小结图: 流程解析:Activity调用startActivity后最后会调用attach方法,然后在PolicyManager实现一个Ipolicy接口,接着实现一个Policy对象,接着调用makenewwindow(Context)方法,该方法会返回一个PhoneWindow对象,而PhoneWindow是Window的子类,在这个PhoneWindow中有一个DecorView的内部类,是所有应用窗
56 0
|
传感器 存储 定位技术
Activity初学乍练
Activity是一个应用程序的组件,他在屏幕上提供了一个区域,允许用户在上面做一些交互性的操作,比如打电话,照相,发送邮件,或者显示一个地图!Activity可以理解成一个绘制用户界面的窗口,而这个窗口可以填满整个屏幕,也可能比屏幕小或者浮动在其他窗口的上方!
52 0
|
Java 调度 Android开发
Activity登堂入室
Activity调用startActivity后最后会调用attach方法,然后在PolicyManager实现一个Ipolicy接口,接着实现一个Policy对象,接着调用makenewwindow(Context)方法,该方法会返回一个PhoneWindow对象,而PhoneWindow是Window的子类,在这个PhoneWindow中有一个DecorView的内部类,是所有应用窗口的根View,即View的老大,直接控制Activity是否显示(引用老司机原话..),好吧,接着里面有一个LinearLayout,里面又有两个FrameLayout他们分别拿来装ActionBar和Cus
75 0
|
XML Java Android开发
第二讲 Activity详解
第二讲 Activity详解
144 0
第二讲 Activity详解
|
Java
为Fragment也写一个ViewInject
title: 为Fragment也写一个ViewInject date: 2018-10-28 11:29:51 tags: fragment ViewInject 1.简介 鸿洋博客:https://blog.csdn.net/lmj623565791/article/details/39269193 前段时间根据鸿洋的博客,写了一个ViewInject,本以为可以和findViewById说再见了,但是今天在fragment中使用ViewInject,却报了空指针的错误,原来是控件没有实例化就调用了。
1123 0
|
Android开发 开发者 容器
Fragment详解
前言 谢谢你那么的、安静的捧场。 Fragment的概述及其设计初衷 Fragment代表了Activity的子模块,因此可以把Fragment理解成Activity片段。
1129 0