Android笔记系列--Fragment

生命周期函数

  • onAttach:onAttach()在fragment与Activity关联之后调调查用。

需要注意的是,初始化fragment参数可以从getArguments()获得,但是,当Fragment附加到Activity之后,就无法再调用setArguments()。所以除了在最开始时,其它时间都无法向初始化参数添加内容。有关Fragment参数初始化及传递的问题,我们会在后面的篇章中细讲。

  • onCreate:fragment初次创建时调用。

尽管它看起来像是Activity的OnCreate()函数,但这个只是用来创建Fragment的。此时的Activity还没有创建完成,因为我们的Fragment也是Activity创建的一部分。所以如果你想在这里使用Activity中的一些资源,将会获取不到。比如:获取同一个Activity中其它Frament的控件实例。(代码如下:),如果想要获得Activity相关联的资源,必须在onActivityCreated中获取。

具体原因参见onActivityCreated;

/**
*如果把这段代码放到Fragment的onCreate()中,那么btnTry的值将会是NULL。
*注意:getActivity()是有值的,因为一旦Activity与Fragment Attached就可以通过getActivity()来获取相关联的Activity的实例。
*/ 
Button btnTry = getActivity().findViewById(R.id.btn_try); 
  • onCreateView:在这个fragment构造它的用户接口视图(即布局)时调用。在这里期望返回此Fragment的一个视图层次结构。使用LayoutInflater的inflater()方法来构造实图。代码如下:
public View onCreateView(LayoutInflater inflater, ViewGroup container, 
                         Bundle savedInstanceState) { 
    return inflater.inflate(R.layout.fragment3, container, false); 
} 

其中LayoutInflater::inflate()的函数声明如下:

public View inflate(int resource, ViewGroup root, boolean attachToRoot) 

其中第二个参数:container,可以为NULL,如果父窗口值为NULL,这意味着该Fragment不可见,因为它没有附加到任何的视图层次中。

最后一个参数:boolean attachToRoot:一般为FALSE,它的意义为,是否当前XML的根结点作为整个APP视图的根结点,如果设为TRUE,那返回的VIEW将是整个APP视图架构的根结点,我们一般不需要这么做。因为我们的Fragment是依附于Activity的,Activity上可能有其它控件,我们的Fragment只是其中的一个小分支,如果这里设为TRUE,那么Fragment以外的分支将被全部删除,整个APP视图将会只有Fragment;

  • onActivityCreated:在Activity的OnCreate()结束后,会调用此方法。

所以到这里的时候,Activity已经创建完成!在这个函数中才可以使用Activity的所有资源。如果把下面的代码放在这里,获取到的btn_Try的值将不会再是空的!

Button btnTry = getActivity().findViewById(R.id.btn_try); 
  • onStart:当到OnStart()时,Fragment对用户就是可见的了。但用户还未开始与Fragment交互。在生命周期中也可以看到Fragment的OnStart()过程与Activity的OnStart()过程是绑定的。意义即是一样的。以前你写在Activity的OnStart()中来处理的代码,用Fragment来实现时,依然可以放在OnStart()中来处理。

  • onResume:当这个fragment对用户可见并且正在运行时调用。这是Fragment与用户交互之前的最后一个回调。从生命周期对比中,可以看到,Fragment的OnResume与Activity的OnResume是相互绑定的,意义是一样的。它依赖于包含它的activity的Activity.onResume。当OnResume()结束后,就可以正式与用户交互了。

  • onPause:此回调与Activity的OnPause()相绑定,与Activity的OnPause()意义一样。

  • onStop:这个回调与Activity的OnStop()相绑定,意义一样。已停止的Fragment可以直接返回到OnStart()回调,然后调用OnResume()。

  • onDestroyView:如果Fragment即将被结束或保存,那么撤销方向上的下一个回调将是onDestoryView()。会将在onCreateView创建的视图与这个fragment分离。下次这个fragment若要显示,那么将会创建新视图。这会在
    onStop之后和onDestroy之前调用。这个方法的调用同onCreateView是否返回非null视图无关。它会潜在的在这个视图状态被保存之后以及它被它的父视图回收之前调用。

  • onDestroy:当这个fragment不再使用时调用。需要注意的是,它即使经过了onDestroy()阶段,但仍然能从Activity中找到,因为它还没有Detach。

  • onDetach:Fragment生命周期中最后一个回调是onDetach()。调用它以后,Fragment就不再与Activity相绑定,它也不再拥有视图层次结构,它的所有资源都将被释放。

FragmentManager

通过getFragmentManager()或getSupportFragmentManager()获得

常用方法:

manager.findFragmentById();  //根据ID来找到对应的Fragment实例,主要用在静态添加fragment的布局中,因为静态添加的fragment才会有ID 

manager.findFragmentByTag();//根据TAG找到对应的Fragment实例,主要用于在动态添加的fragment中,根据TAG来找到fragment实例 

manager.getFragments();//获取所有被ADD进Activity中的Fragment

FragmentTransaction

一般用来对当前的Fragment进行管理,包括add,replace,remove

常用的针对Fragment的方法有:

//将一个fragment实例添加到Activity的最上层 
add(int containerViewId, Fragment fragment, String tag); 

//将一个fragment实例从Activity的fragment队列中删除 
remove(Fragment fragment); 

//repalce()操作,会清空同一个container中的所有fragment视图!注意用词:请空的是fragment的VIEW!fragment的实例并不会被销毁! 
replace(int containerViewId, Fragment fragment);

使用

FragmentManager manager = getSupportFragmentManager(); 
Fragment fragment = manager.findFragmentByTag("fragment2"); 
FragmentTransaction transaction = manager.beginTransaction(); 
transaction.remove(fragment); 
transaction.commit();

回滚

提交事务

在transaction.commit()之前,使用addToBackStack()将其添加到回退栈中。

transaction.addToBackStack(String tag); 
在需要回退时,使用popBackStack()将最上层的操作弹出回退栈。

manager.popBackStack(); 
这里的popBackStack()是弹出默认的最上层的栈顶内容。

回退事务

回退是以commit()提交的一次事务为单位的,而不是以其中的add,replace等等操作为单位回退的。

例如:
1. 首先,添加Fragment1,提交一次事务
2. 然后,一次添加Fragment2,Fragment3,Fragment4,然后提交一次事务
3. 利用popBackStack()将顶层事务出栈,可以看到把Fragment2,Fragment3,Fragment4一次出栈,界面显示在了Fragment1的位置

函数如下:

void popBackStack(int id, int flags); 
void popBackStack(String name, int flags); 
  • 参数:
    • int id是当提交变更时transaction.commit()的返回值。
    • string name是transaction.addToBackStack(String tag)中的tag值.
    • int flags有两个取值:
      • 当取值0时,表示除了参数一指定这一层之上的所有层都退出栈,指定的这一层为栈顶层;
      • 当取值POP_BACK_STACK_INCLUSIVE时,表示连着参数一指定的这一层一起退出栈;

使用popBackStack()来弹出栈内容的话,调用该方法后会将事物操作插入到FragmentManager的操作队列,只有当轮询到该事物时才能执行。如果想立即执行事务的话,需要使用下面几个对应的方法:

popBackStackImmediate() 
popBackStackImmediate(String tag) 
popBackStackImmediate(String tag, int flag) 
popBackStackImmediate(int id, int flag) 

回退栈(back stack)状态改变监听

FragmentManager::addOnBackStackChangedListener(listener);//添加监听器
FragmentManager::removeOnBackStackChangedListener(listener);//移除监听器,在页面对应的销毁时,都要记得remove掉

hide() show()

public FragmentTransaction hide(Fragment fragment);//将指定的fragment隐藏不显示
public FragmentTransaction show(Fragment fragment);//将以前hide()过的fragment显示出来

replace操作,每次都会把container中的现有的fragment实例清空,然后再把指定的fragment添加进去,就就造成了在切换到以前的fragment时,就会重新实例化fragment。

正确的切换方式是add(),切换时hide(),add()另一个Fragment;再次切换时,只需hide()当前,show()另一个。
这样就能做到多个Fragment切换不重新实例化:

public void switchContent(Fragment from, Fragment to) { 
    if (!to.isAdded()) {    // 先判断是否被add过 
        transaction.hide(from).add(R.id.content_frame, to).commit(); // 隐藏当前的fragment,add下一个到Activity中 
    } else { 
        transaction.hide(from).show(to).commit(); // 隐藏当前的fragment,显示下一个 
    } 
}

detach()、attach()

public FragmentTransaction detach(Fragment fragment); 
public abstract FragmentTransaction attach(Fragment fragment);
  • detach():会将view与fragment分离,将此将view从viewtree中删除!而且将fragment从Activity的ADD队列中移除!所以在使用detach()后,使用fragment::isAdded()返回的值是false;但此fragment实例并不会删除,此fragment的状态依然保持着使用,所以在fragmentManager中仍然可以找到,即通过FragmentManager::findViewByTag()仍然是会有值的。

  • attach():显然这个方法与detach()所做的工作相反,它一方面利用fragment的onCreateView()来重建视图,一方面将此fragment添加到ADD队列中;这里最值得注意的地方在这里:由于是将fragment添加到ADD队列,所以只能添加到列队头部,所以attach()操作的结果是,最新操作的页面始终显示在最前面!这也就解释了下面的例子中,为了fragment2 detach()后,当再次attach()后,却跑到了fragment3的前面的原因。还有,由于这里会将fragment添加到Activity的ADD队列中,所以在这里调用fragment::isAdded()将返回True;

例子

(1)同样,先依次添加Fragment1,Fragment2,Fragment3

(2)然后点击“frag3 detach”,将fragment3的View视图删除,然后从ADD队列中将fragment移除。之后点击“fragment is added?”根据TOAST可以看出,fragment::isAdded()函数返回值是false;

(3)然后点击“frag3 attach”,将fragment重新与Activity绑定,它有两个动作,一方面重建fragment视图,一方面将fragment添加到Activity的ADD队列中;所以这时候点击“fragment is added?”,fragment::isAdded()函数返回值是true;

(4)然后点击“frag2 detach”,由于fragment2在fragment3之下,所以给fragment2使用detach,在界面上看不出任何效果。

(5)但当点击“frag2 attach”时,问题出现了,由于attach()会做两件事,一方面重建fragment视图,一方面将fragment添加到Activity的ADD队列中;由于是ADD队列,所以肯定添加的位置肯定在队首;所以fragment2就显示在了最上方,把fragment3盖住了,这就是为什么在点击“frag2 attach”之后,却可以看到fragment2的视图的原因!

replace操作后Fragment状态保持

这个过程是这样的:
1. 首先在Fragment1的EditText中先几个字
2. 然后如果调用addFragment()添加Fragment2,然后当从fragment2返回时,发现这几个字还是有的。
3. 但如果我们通过调用replace()添加Fragment2的话,会发现,当返回的时候,那几个字没了!

调用addFragment添加的fragment的View会保存到视图树(ViewTree)中,其中各个控件的状态都会被保存。但如果调用replace()来添加fragment,我们前面讲到过,replace()的实现是将同一个container中的所有fragment视图从ViewTree中全部清空!然后再添加指定的fragment。由于repalce操作会把以前的所有视图全部清空,所以当使用Transaction回退时,也就只有重建每一个fragment视图,所以就导致从replace操作回退回来,所有的控件都被重建,以前的用户输入全部没了。

到这里,大家首先要明白一个问题,repalce()操作,会清空同一个container中的所有fragment视图!注意用词:请空的是fragment的VIEW!fragment的实例并不会被销毁!因为fragment的实例是通过FragmentManager来管理的。当fragment的VIEW被销毁时,fragment实例并不会被销毁。他们两个不是同时的,即在fragment中定义的变量,所上次运行中被赋予的值是一直存在的。那fragment实例什么时候会被销毁呢,当然是在不会被用到的时候才会被销毁。那什么时候不会被用到呢,即不可能再回退到这个操作的时候,就会被销毁。
在上面的例子中,fragment1虽然被fragment2的repalce操作把它的视图给销毁了,但在执行replace操作时,将操作加入到了回退栈,这时候,FragmentManager就知道,用户还可能通过回退再次用到fragment1,所以就会保留fragment1的实例。相反,如果,在执行repalce操作时,没有加入到回退栈,那FragmentManager就肯定也知道,用户不可能再回到上次那个Fragment1界面了,所以它的fragment实例就会在清除fragment1视图的同时也被清除了。

法一:加入回退栈,用变量保存空间信息

在清除Fragment视图的时候,如果我们将操作同时加入到回退栈,那么它的VIEW虽然从ViewTree中清除了,但它的实例会被保存在FragmentManager中,那它的变量也会一直保存着,直到下次回来。但视图在回来的时候会重建。
用一个变量来保存EditText当前字符串,在replace前将EditText中的值保存在这个变量中,当返回来再次创建视图时,再次给EditTxt赋值

public class Fragment1 extends Fragment {  
    private String mEditStr;  
    private EditText editText;  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState) {  
        View rootView = inflater.inflate(R.layout.fragment1, container, false);  
        editText = (EditText)rootView.findViewById(R.id.fragment1_edittext);  
        editText.setText(mEditStr);  
        return rootView;  
    }  

    @Override  
    public void onActivityCreated(Bundle savedInstanceState) {  
        super.onActivityCreated(savedInstanceState);  
        Button btnReplace = (Button)getView().findViewById(R.id.fragment1_repalce);  
        btnReplace.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                mEditStr = editText.getText().toString();  

                …………  
            }  
        });  
        …………  
    }  
}  

法二:为控件添加ID

法三:保存FragmentView视图

直接将Fragment的视图直接保存到变量中,在系统在利用onCreateView()创建视图的时候,我们直接返回保存的视图

private View rootView;  

public View onCreateView(LayoutInflater inflater, ViewGroup container,  
        Bundle savedInstanceState) {  
    return getPersistentView(inflater, container, savedInstanceState, R.layout.fragment1);  
}  

public View getPersistentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState, int layout) {  
    if (rootView == null) {  
        // Inflate the layout for this fragment  
        rootView = inflater.inflate(layout, container,false);  
    } else {  
        ((ViewGroup) rootView.getParent()).removeView(rootView);  
    }  
    return rootView;  
}  

当rootView==null,即第一次创建时,就利用inflater.inflate()来创建初始化状态的视图,当下次再进到这个界面时,比如下面的通过回退操作进入到fragment1时,这时候的rootView就不再是空了。但在onCreateView()中返回的视图是要添加到ViewTree中去的。而这里的rootView视图在上次已经添加到里面去了,一个视图实例不能被add两次,不然就会被下面这个错误!所以,我们针对这种情况,如果rootView已经存在于ViewTree中的时候,要先从ViewTree中移除。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁星点点-

请我喝杯咖啡呗

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值