Fragment -- 整理篇

Posted Y_ZhiWen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Fragment -- 整理篇相关的知识,希望对你有一定的参考价值。

产生

为了让界面可以在平板上更好地展示,android在3.0版本引入了Fragment(碎片)功能,它非常类似于Activity,可以像Activity一样包含布局。Fragment通常是嵌套在Activity中使用的

使用

静态用法

<fragment
        android:id="@+id/static_use_fragment"
        android:name="com.example.yzw.demofragment.TestFragment"
        android:layout_width="match_parent"
        android:layout_height="200dp" />

在Activity布局文件中添加fragment标签并设置name属性,指定显示的Fragment

这种使用方法不是很建议使用,因为这样限制Activity的使用Fragment的灵活性

动态用法

先看下两小段代码

<TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="动态使用fragment" />


    <FrameLayout
        android:id="@+id/dynamic_use_fragment"
        android:layout_width="match_parent"
        android:layout_height="200dp" />
mFragment = (TestFragment) getSupportFragmentManager().findFragmentById(R.id.dynamic_use_fragment);
        if (mFragment == null) 
            mFragment = new TestFragment();
            getSupportFragmentManager().beginTransaction().replace(R.id.dynamic_use_fragment, mFragment).commit();
        

在Activity中添加一个布局,并在Activity中通过FragmentManager开启事务添加Fragment到指定布局中去。

这里的事务FragmentTransaction提供的操作Fragment有:

// 添加Fragment到布局id位置,可以指定该Fragment的tag标签,这里为null
add(@IdRes int containerViewId, Fragment fragment)

// 从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈,这个Fragment实例将会被销毁。
remove(Fragment fragment)

// 使用另一个Fragment替换当前的,实际上就是remove()然后add(id,fragment,tag)的合体
replace(@IdRes int containerViewId, Fragment fragment,
            @Nullable String tag)

// 隐藏当前的Fragment,仅仅是设为不可见,并不会销毁
hide(Fragment fragment)

// 显示之前隐藏的Fragment
show(Fragment fragment)

// 文档:Detach the given fragment from the UI.  This is the same state as when it is put on the back stack
// 将Fragment重UI中分离,相当于将此Fragment从回退栈中移除(如果有添加到回退栈的话)
// 会调用Fragment的onPause、onStop、onDestroyView方法
// 如果再次attach,会调用其onCreateView、onActivityCreated、onStart、onResume方法(如果添加到回退栈,再按返回键返回,所调用的方法一样)
detach(Fragment fragment)

// 重建view视图,附加到UI上并显示
attach(Fragment fragment)

remove和detach有一点细微的区别,在不考虑回退栈的情况下,remove会销毁整个Fragment实例,而detach则只是销毁其视图结构,实例并不会被销毁。

FragmentTransaction事务的其他操作:

// 将该事务添加到回退栈,并可指定该事物在栈中的名称,不指定可赋为null
addToBackStack(@Nullable String name);

// 设置动画效果
// 注意:需要在replace/add/remove之前调用
// animation的执行是异步的。如果你想对animation的执行进行监听,你可以重写fragment里面的onCreateAnimation方法,可见下方代码
setCustomAnimations(@AnimRes int enter,
            @AnimRes int exit);

// 提交一个事务,该方法需要在Activity的onPause之前调用,否则会Activity State Loss或者异常
// 如果该事物有添加到回退栈,则返回回退栈实体的id,否则则返回一个负数
// @return Returns the identifier of this transaction's back stack entry, if @link #addToBackStack(String) had been called.  Otherwise, returns a negative number.
int commit()

// 使用该方法避免commit()方法造成的错误,但是不是很建议使用该方法,因为使用该方法意味着有可能发生事务的遗失,如果发生的话是不可知的,所以要尽量保证commit()方法的正确使用
int commitAllowingStateLoss()

相关文章:
Fragment Transactions & Activity State Loss
FragmentTransaction的commit和commitAllowingStateLoss的区别

在Fragment中监听动画效果


    /**
     * if you need add animation listener for the fragment
     * please use this method
     */
    @Override
    public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) 
        Animation anim;
        if (enter) 
            anim = AnimationUtils.loadAnimation(getActivity(),
                    android.R.anim.fade_in);
         else 
            anim = AnimationUtils.loadAnimation(getActivity(),
                    android.R.anim.fade_out);
        

        anim.setAnimationListener(new AnimationListener() 
            public void onAnimationEnd(Animation animation) 

            

            public void onAnimationRepeat(Animation animation) 

            

            public void onAnimationStart(Animation animation) 

            
        );

        return anim;
    

生命周期

这里是一张相对比较完整的Fragment生命周期图,包括Activity的生命周期也在其中,从中可以看出,Fragment的生命周期是挺复杂的,结合上面事务操作和回退栈理解下
Fragment生命周期图


// 与Activity关联,如果Activity实现了某个接口,可以在这里进行转换
public void onAttach(Activity activity)

// Fragment的初始化创建,(应该还没完成,接着执行onCreateView()方法)
// Note:Activity还没完全创造完成,与Activity密切相关的操作不要在这里操作
// 可以获取Context,(getActivity().getApplicationContext())
public void onCreate(Bundle savedInstanceState)

// 初始化Fragment的界面View ,如果返回null,则是默认界面,不为null,则需要调用onDestroyView()
@Nullable
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) 

// 这个方法在onCreateView()初始化Fragment界面View后立即调用,并在数据恢复到View之前调用,如果参数bundle不为null,则表示该Fragment是恢复来自之前的Fragment
public void onViewCreated(View view, @Nullable Bundle savedInstanceState)

// 这个方法是当Fragment的Activity跟View都初始化完成时调用,可以用来做最后的一些数据的初始化
public void onActivityCreated(Bundle savedInstanceState)

// 控制Fragment是否在Activity重建时重建自己
public void setRetainInstance(boolean retain)

// 当Fragment可见时调用,跟Activity的onResume相联系
public void onStart() 

// 当Fragment开始的后调用,跟Activity的onResume相联系
public void onResume()

// 暂停,跟Activity的onPause相联系
public void onPause()

// 保存状态,但是有可能状态没有被保存,such as when placed on the back stack with no UI showing
public void onSaveInstanceState(Bundle outState)

// 停止,跟Activity的onStop相联系
public void onStop()

// 在数据保存之后,界面移除之前调用
// 如果从回退栈返回,则调用哦你createView方法
public void onDestroyView()

// 摧毁fragment
public void onDestroy() 

// 与Activity分离
public void onDetach()
  • setRetainInstance(true)

当Fragment在onCreate方法中setRetainInstance(true);
当Activity配置改变时,Fragment不会重建,onCreate方法不会调用,其他方法照常,在onCreateView方法中设置也是一样

Fragment的创建方式

相关文章
Using newInstance() to Instantiate a Fragment

先看一下Fragment的创建方法

public class MyFragment extends Fragment 

    /**
     * Static factory method that takes an int parameter,
     * initializes the fragment's arguments, and returns the
     * new fragment to the client.
     */
    public static MyFragment newInstance(int index) 
        MyFragment f = new MyFragment();
        Bundle args = new Bundle();
        args.putInt("index", index);
        f.setArguments(args);
        return f;
    

为什么要用上面静态工厂方法创建Fragment呢?

因为Fragment的生命周期是与Activity相关联的,而当手机方式某些配置变化时,Activity会发生重建,这种情况下,如果没有设置setRetainInstance(true)的话,Fragment会发生通过默认构造器重建,所以最好是尽量通过上面的方式创建Fragment

这里需要提一下Bundle的底层是通过ArrayMap< String,Object >保存数据

配置变化(Configuration Change)

配置变化重建的必要性(为什么要重建):

先是要了解什么是配置变化。
当手机横竖屏切换、语言改变、键盘可见性等等情况下就会发生配置变化
重建的目的是为了让应用程序通过自动加载可替代资源来适应新的配置

不推荐通过设置< android:configChanges>属性的方法来避免activity被销毁再重建的原因:

比如说,当用户离开应用,在回到应用前被销毁的话,例如点击了屏幕的Home键或者有个电话打进来,用户很久之后才回到应用程序,但是在此之前系统因为资源紧张而销毁了应用进程,当用户返回还是要重新创建activity,问题等于没解决。

所以这种问题还是要正式面对一下,而不是逃避

通过Fragment可以一定程度上解决这个问题:

将Activity中的某些操作在Fragment中执行,并且设置Fragment的setRetainInstance(true);方法,通过该方法以及下方的测试图片可以看出,当Activity重建时Fragment生命周期中onDestroy()方法不会被调用,然后新建的Activity通过回退栈找到之前的Fragment,并调用onAttach(Activity)和onActivityCreated(Bundle)方法将Fragment连接到新的Activity,此时onCreate不会被调用,其他方法被调用

Fragment懒加载

当Fragment跟ViewPager搭配使用或者其他情况时,Fragment的懒加载很有必要。可以避免资源的消耗

通过重写Fragment的方法

setUserVisibleHint(boolean isVisibleToUser)

通过参数isVisibleToUser可以知道当前Fragment是否对用户可见

但是该方法跟Fragment的生命周期跟Fragment的构造方法的调用顺序是怎样呢??

这里我简单测试并描述一下,布局中的ViewPager加载4个Fragment,而一开始显示第一个Fragment时,同时也会加载第二个Fragment,这里可能跟FragmentPagerAdapter源码的实现有关,这里mark一下,接下来看一下其调用顺序

ViewPager中第一个Fragment

ViewPager中第二个Fragment

这里简单理解一下setUserVisibleHint方法,以此可以在Fragment可见时加载数据,避免浪费资源。

Fragment实现Tab的四种模式

1、FragmentManager+Fragment

2、ViewPager+PagerAdapter

3、ViewPager+FragmentPagerAdapter

4、TabPageIndicator+ViewPager+FragmentPagerAdapter

具体操作请查看鸿洋大神的博客
Android项目Tab类型主界面大总结 Fragment+TabPageIndicator+ViewPager

Fragment的通信

1、Activity传递参数给Fragment,可以通过Fragment Arguments,在文章中的Fragment的创建已经简单介绍了其用法。

2、Fragment传递参数给Activity,或者Fragment的按钮点击事件由所在Activity处理,这种情况可以用接口回调处理

3、Fragment启动另外一个Activity,等待数据返回,在Fragment中存在startActivityForResult()以及onActivityResult()方法,但是呢,没有setResult()方法,我们就需要通过调用getActivity().setResult(.. , ..);来实现。

4、Fragment与Fragment之间的数据传递,在Fragment 你应该知道的一切 这篇文章中通过一个Fragment中添加onActivityResult( … )方法,另外一个Fragment提供.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE);方法,并在结束时调用TargetFragment的onActivityResult方法得到数据返回

注意

使用Fragment时的注意事项

Fragment是由FragmentManager来管理的,每一个Activity有一个FragmentManager,管理着一个Fragment的栈,所以,Activity是系统级别的,由系统来管理ActivityManager,栈也是系统范围的。而Fragment则是每个Activity范围内的。

同一个Activity中,只能有一个ID或TAG标识的Fragment实例。

这很容易理解,同一个范围内,有标识的实例肯定是要唯一才行(否则还要标识干嘛)这个在布局中经常犯错,在布局中写Fragment最好不要加ID或者TAG,否则很容易出现不允许创建的错误。我的原则是如果放在布局中,就不要加ID和TAG;如果需要ID和TAG就全用代码控制。创建新实例前先到FragmentManager中查找一番,这也正是有标识的意义所在。

最后推荐一篇文章:
Android实战技巧:Fragment的那些坑

总结

本篇文章只是将Fragment的常见使用整理一下,没有太过深入的了解,还是需要不断的反复实践才能得到更好的体会。


如果文章中有不足之处或者错误的地方,希望能够指出,谢谢。

以上是关于Fragment -- 整理篇的主要内容,如果未能解决你的问题,请参考以下文章

Android Fragment 隐藏或显示时调用的生命周期方法

Fragment的生命周期

每次 viewWillAppear 发生重大位置变化时调用 didUpdateLocations

整理之Fragment

为啥popbackstack会调用fragment的onCreateView?

fragment生命周期