Fragment小结
Fragment和Activity的区别
- Fragment是到Android3.0+ 以后,Android新增了Fragments,在没有 Fragment 之前,一个屏幕只能放一个 Activity。这是一个起源时间大家要知道是什么时候开始引入的。
- .Activity 代表了一个屏幕的主体,而Fragment可以作为Activity的一个组成元素。
一个Activity可以有若干个(0或n)Fragment构成。你可以把Fragment想象成Activity中的一个控件,只不过相对于一般控件,Fragment与Activity联系更为紧密,随着Activity的生命周期变化,Fragment也随之相应不同的生命周期函数。
Fragment 从功能上讲相当于一个子活动(Activity),它可以让多个活动放到同一个屏幕上,也就是对用户界面和功能的重用,因为对于大屏设备来说,纯粹的 Activity 有些力不从心。 - Fragment 像是一个子活动,但是 Fragment 不是 Activity 的扩展,因为 Fragment 扩展自 android.app 中的 Object,而 Activity 是 Context 的子类。Fragment 有自己的视图层级结构,有自己的活动周期,还可以像活动一样响应后退按钮,Fragment 还有一个用作其初始化参数的包(Bundle),类似 Activity,Fragment 也可由系统自动保存并在以后还原。当系统还原 Fragment 时,它调用默认的构造函数(没有参数),然后将此Bundle还原到新创建的 Fragment 中,所以无论新建还是还原 Fragment,都要经过两个步骤:(1)调用默认构造函数(2)传入新的或者保存起来的Bundle。
- 一个Activity可以运行多个 Fragment,Fragment 切换时,由 FragmentTransaction 执行,切换时,上一个 Fragment 可以保存在后退栈中(Back Stack),这里的后退栈由 FragmentManager 来管理,注意 Fragment 和 Activity 的后退栈是有区别的:Activity 的后退栈由系统管理,而 Fragment 的后退栈由所在的Activity 管理。
- Fragment不能脱离Activity而存在,只有Activity才能作为接收intent的载体。其实两者基本上是载体和组成元素的关系。
- Fragment用来描述一些行为或一部分用户界面在一个Activity中,你可以合并多个fragment在一个单独的activity中建立多个UI面板,同时重用fragment在多个activity中.你可以认为fragment作为一个activity中的一节模块,fragment有自己的生命周期,接收自己的输入事件,你可以添加或移除从运行中的activity.一个fragment必须总是嵌入在一个activity中,同时fragment的生命周期受activity而影响,举个例子吧,当activity暂停,那么所有在这个activity的fragments将被destroy释放。然而当一个activity在运行比如resume时,你可以单独的操控每个fragment,比如添加或删除。不过因为Fragment和Activity的生命周期都比较复杂,我们分别对比下:创建一个fragment你必须创建一个Fragment的子类或存在的子类,比如类似下面的代码
public static class AndroidFragment extends Fragment{
@Override
public View onCreateView(LayoutInflaterinflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.android_fragment,container, false);
}
}
Fragment类的一些代码看起来有些像Activity为了让大家了解清楚,Android开发网给大家整理下 Fragment的生命周期大家可以参考一下网上关于生命周期的介绍 http://www.cnblogs.com/purediy/p/3276545.html,部分类似Activity的,我们详细解释
onCreate()
当fragment创建时被调用,你应该初始化一些实用的组件,比如在fragment暂停或停止时需要恢复的
onCreateView()
当系统调用fragment在首次绘制用户界面时,如果画一个UI在你的fragment你必须返回一个View当然了你可以返回null代表这个fragment没有UI.
那么如何添加一个Fragment到Activity中呢? Activity的布局可以这样写
<?xml version="1.0"encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.android.cwj.ArticleListFragment"
android:id="@+id/list"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.android.cwj.ArticleReaderFragment"
android:id="@+id/viewer"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
1、新建一个Activity,在Activity中的布局是垂直的线性布局,2个button,一个FramLayout,为Framlayout设置一个id
2、新建2个Fragment,继承Fragment,在oncreatview中初始化fragment的布局
动态添加Fragment主要分为4步:
- 1.获取到FragmentManager,在V4包中通过getSupportFragmentManager,在系统中原生的Fragment是通过getFragmentManager获得的。
- 2.开启一个事务,通过调用beginTransaction方法开启。
- 3.向容器内加入Fragment,一般使用add或者replace方法实现,需要传入容器的id和Fragment的实例。
- 4.提交事务,调用commit方法提交。
在Activity中的布局
在Activity中的代码
其它代码都没有动,主要的是在MainActivity里,点击这两个按钮时做的处理:
主要的步骤是
- FragmentManager manager = getSupportFragmentManager();
- FragmentTransaction transaction = manager.beginTransaction();
- Fragment1 fragment1 = new Fragment1();
- transaction.add(R.id.fragment_container, fragment1);
- transaction.commit();
LayoutInflater
在看inflate()方法时,我们随便看下如何获得 LayoutInflater ,获得LayoutInflater 实例有三种方式
-
LayoutInflater inflater =
getLayoutInflater();//调用Activity的getLayoutInflater() -
LayoutInflater inflater = LayoutInflater.from(context);
-
LayoutInflater inflater = (LayoutInflater)context.getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
其实,这三种方式本质是相同的,从源码中可以看出:
- getLayoutInflater()
Activity 的 getLayoutInflater() 方法是调用 PhoneWindow 的getLayoutInflater()方法,看一下该源代码:
- 1
- 2
- 3
- 4
- 5
可以看出它其实是调用 LayoutInflater.from(Context context)
方法。
- LayoutInflater.from(Context context)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
可以看出它其实调用 context.getSystemService()。
所以这三种方式最终本质是都是调用的Context.getSystemService()。
接下来我们来看下inflate()方法,
inflate()
LayoutInflater的inflate方法一共有四种
-
public View inflate(int, ViewGroup)
-
public View inflate(XmlPullParser, ViewGroup)
-
public View inflate(int, ViewGroup, boolean)
-
public View inflate(XmlPullParser, ViewGroup, boolean)
查看源码我们会发现inflate(int, ViewGroup)
调用的是inflate(int,
ViewGroup, boolean)
方法
- 1
- 2
- 3
而inflate(int, ViewGroup, boolean)
调用的是inflate(XmlPullParser,
ViewGroup, boolean)
方法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
再看View inflate(XmlPullParser, ViewGroup)
我们会发现它调用的也是inflate(XmlPullParser,
ViewGroup, boolean)
方法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
所以呢?这四个方法都是public View inflate(XmlPullParser, ViewGroup, boolean)
方法,那其他三个我们就不看了,我们来分析这个方法。
看下别人对这个方法的参数是怎样描述的:
- 返回值View:
返回的值是View指向的根节点。大家可能会有个疑问,第二个参数root不就是根结点吗?那返回根结点的话,不就是我们传进去的root吗?这可不一定,大家知道返回根结点的VIEW之后,继续往下看参数的具体讲解。
- 第一个参数XmlPullParser:
也就说根据其他几个方法传进来的xml布局文件在这里会被用传进来的parser进行解析 - 第二个参数root:
表示根结点,它的作用主要取决于第三个参数
- 第三个参数attachToRoot:
表示是否将转换后的VIEW直接添加在根结点上,如果是TRUE,那么在转换出来VIEW之后,内部直接会调用root.addView()来将其添加到root结点上,然后返回根结点,当然是我们传进去的ROOT结点。如果设为FALSE,那只会将XML布局转换成对应的VIEW,不会将其添加的我们传进去的root结点上。
第三个参数可能这样说比较难理解,我们来举个例子:
1 . 创建一个activity_root.xml文件,一个垂直的线性布局id为root,只有一个TextView
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
2 . 然后再建一个布局:add_layout.xml,也是一个垂直的线性布局背景颜色是红色,里面有个TextView
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
我们先来试试TRUE这个参数
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
效果:
那如果将TRUE换为FALSE呢?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
效果:
可以看到,我们的主布局没有任何变化,也就是说add_layout.xml的布局没有被添加到activity_mian.xml中;
我们开头就讲过,如果attachToRoot设为false,那转换后的布局是不会被添加到root中的,会作为结果返回。
其实attachToRoot设为TRUE的代码与下面的代码是等价的:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
透过源码分析LayoutInflater.inflate()的过程
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)方法源码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
我们一点点来分析:
再源码的基础上我们保留下一些核心代码来分析下
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
我们来逐步分析一下:
- 第一步:一进来是初始化部分:
- 1
- 2
- 3
//注意这里,在初始化时,result表示要返回的视图,默认是返回root
View result = root;
通过XML文件获取布局中各个控件的属性集:AttributeSet,注意:这里通过XML只能知道布局里每个控件的布局参数。那整个LAYOUT的布局参数呢,虽然我们在XML的根结点的布局可能写的是layout_width:fill_parent,layout_height:fill_parent。但这只是很笼统的,系统要确实计算出它的宽高数来。这个宽高数的计算就是通过root中的属性来得出来的,下面代码中会提到。
一个很重要的部分在于最后一句话!!!
[java] view plain copy
View result = root;
result表示最后返回的视图VIEW,所以这表示在默认情况下,返回root的视图!!注意这只是在默认情况下,下面会对result进行赋值的!
第二步:创建XML对应的空白视图temp
[java] view plain copy
- //第二步:创建XML对应的空白VIEW:temp
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
在这里首先判断XML的根结点是不是merge标签,大家知道我们的merge标签的主要作用是用来将merge标签下的所有控件合并到其上层布局上,也就是说merge标签下是没有根控件的!因为merge标签下的控件都是并列关系,所以如果merge标签使用的inflate函数,那我们根本没有办法给它返回根视图,所以肯定是要抛出异常的
如果不是merge标签,就创建一个空白视图,返回给temp,这里的temp就是我们XML所对应的布局!
- 第三步:获取root的布局参数,设置到temp中
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
在这里看到,首先获取到了ROOT的布局参数赋值为params,然后当attachToRoot为FALSE时,将参数设置到temp中;
- 第四步:初始化temp中的子控件
[java] view plain copy
//第四步:初始化temp中的子控件
- 1
- 2
- 第五步:如果root不为空,而且attachToRoot设为TRUE,则将其视图通过addView添加到root中
- 1
- 2
- 3
在这里,就是当root不为空、attachToRoot为TRUE时,将构建好的temp视图添加到root中,注意这里的参数仍然从root中获取的布局参数params!!!所以,无论attachToRoot值为如何,temp的布局参数都是从ROOT里获取的!!!!
- 第六步:如果root为空,或者attachToRoot设为FALSE,那么就将TEMP视图做为result返回
- 1
- 2
- 3
从这里可以看到,如果root为空,获取attachToRoot为FALSE,就会将temp做为结果返回!
到这里整个过程就分析结束了,下面我们总结一下:
- root的最基本作用,就是给我们传进去的Layout提供布局参数信息
-
如果attachToRoot为TRUE,那么会将Layout产生的布局视图添加到root中,返回root,如果attachToRoot为FALSE,那么会将Layout产生的布局视图直接返回