UI片段——Fragment
为什么需要Fragment?Fragment与Activity又是什么关系?Fragment的生命周期是怎样的?Fragment如何使用呢?其实Fragment是一种可以嵌入在活动中的UI片段,能够让程序更加合理和充分地利用大屏幕的空间,出现的初衷是为了适应大屏幕的平板电脑,可以将其看成一个小型Activity,又称作Activity碎片。下面来看看Fragment到底有哪些神奇之处吧 ~
Fragment基本概述
使用Fragment可以把屏幕划分成几块,然后进行分组,进行一个模块化管理。Fragment不能够单独使用,需要嵌套在Activity中使用,其生命周期也受到宿主Activity的生命周期的影响。
1、一个Activity可以运行多个Fragment 2、Fragment不能脱离Activity而存在 3、Activity是屏幕的主体,而Fragment是Activity的一个组成元素 4、一个Fragment可以被多个Activity重用 5、Fragment有自己的生命周期,并能接收输入事件 6、可以在Activity运行时动态地添加或删除Fragment
Fragment的优势: 1、模块化:我们不必把所有代码全部写在Activity中,而是把代码写在各自的Fragment中。 2、可重用:多个Activity可以重用一个Fragment。 3、可适配:根据硬件的屏幕尺寸、屏幕方向,能够方便地实现不同的布局,这样用户体验更好。
静态加载与动态加载
使用Fragment有两种方式,分别是静态加载和动态加载。
1、静态加载
关于静态加载的流程如下:
- 定义Fragment的xml布局文件
- 自定义Fragment类,继承Fragment类或其子类,同时实现onCreate()方法,在方法中,通过inflater.inflate加载布局文件,接着返回其View
- 在需要加载Fragment的Activity对应布局文件中的name属性设为全限定类名,即包名.fragment
- 最后在Activity调用setContentView()加载布局文件即可
静态加载一旦添加就不能在运行时删除
比如我现在要先进入到一个专门用于静态加载的Activity
1// 静态加载
2public void toStaticLoadActivity(View view) {
3 startActivity(new Intent(MainActivity.this, StaticLoadActivity.class));
4}
StaticLoadActivity的布局文件如下:
1<LinearLayout
2 xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:orientation="vertical"
5 android:layout_height="match_parent">
6
7 <fragment
8 android:id="@+id/list_fragment"
9 android:name="com.example.learnfragment.ListFragment"
10 android:layout_width="250dp"
11 android:layout_height="150dp"/>
12
13 <fragment
14 android:layout_marginTop="20dp"
15 android:id="@+id/list_fragment_two"
16 android:name="com.example.learnfragment.ListFragment"
17 android:layout_width="250dp"
18 android:layout_gravity="center"
19 android:layout_height="150dp"/>
20</LinearLayout>
其中fragment的name属性都指向了com.example.learnfragment.ListFragment:
1// 列表 Fragment
2public class ListFragment extends Fragment {
3
4 // 创建视图
5 @Nullable
6 @Override
7 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
8 View view = inflater.inflate(R.layout.fragment_list, container, false);
9 TextView textView = view.findViewById(R.id.tv_static_load);
10 textView.setText("Hello, Fragment!");
11 return view;
12 }
13}
fragment_list.xml 布局文件如下所示:
1<LinearLayout
2 xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:orientation="vertical"
5 android:background="@color/colorPrimary"
6 android:layout_height="match_parent">
7
8 <TextView
9 android:id="@+id/tv_static_load"
10 android:layout_width="match_parent"
11 android:layout_height="100dp"
12 android:gravity="center"
13 android:textColor="#ffffff"
14 android:textSize="20sp"
15 android:text="This is a fragment" />
16</LinearLayout>
现在只需要点击Main Activity中的TextView即可看到静态加载Fragment的效果:
由此可见,其实Fragment是可复用的,因为StaticLoadActivity的布局文件中写了两个Fragment标签。
2、动态加载
动态加载Fragment的流程如下:
- 提前准备好Container,即Fragment的容器
- 获得FragmentManager对象,通过getSupportFragmentManager()
- 获得FragmentTransaction对象,通过fm.beginTransaction()
- 调用add()方法或者repalce()方法加载Fragment
- 最后调用commit()方法提交事务
下面演示一下动态加载Fragment,先在activity_main.xml布局文件中准备好两个Container和按钮:
1<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 android:orientation="vertical"
6 tools:context=".MainActivity">
7
8 <TextView
9 android:layout_width="match_parent"
10 android:layout_height="100dp"
11 android:gravity="center"
12 android:onClick="toStaticLoadActivity"
13 android:text="static load fragment" />
14
15 <LinearLayout
16 android:layout_width="match_parent"
17 android:orientation="horizontal"
18 android:layout_height="wrap_content">
19 <Button
20 android:id="@+id/load_left"
21 android:text="加载左边"
22 android:layout_width="0dp"
23 android:layout_weight="1"
24 android:onClick="dynamicLoad"
25 android:layout_height="wrap_content"/>
26 <Button
27 android:id="@+id/load_right"
28 android:text="加载右边"
29 android:layout_width="0dp"
30 android:layout_weight="1"
31 android:onClick="dynamicLoad"
32 android:layout_height="wrap_content"/>
33 <Button
34 android:id="@+id/remove_left"
35 android:text="删除左边"
36 android:layout_width="0dp"
37 android:layout_weight="1"
38 android:onClick="dynamicLoad"
39 android:layout_height="wrap_content"/>
40 <Button
41 android:id="@+id/remove_right"
42 android:text="删除右边"
43 android:layout_width="0dp"
44 android:layout_weight="1"
45 android:onClick="dynamicLoad"
46 android:layout_height="wrap_content"/>
47 </LinearLayout>
48
49 <LinearLayout
50 android:orientation="horizontal"
51 android:layout_width="match_parent"
52 android:layout_height="wrap_content">
53 <LinearLayout
54 android:layout_margin="1dp"
55 android:id="@+id/list_container"
56 android:layout_width="150dp"
57 android:orientation="horizontal"
58 android:layout_height="400dp">
59 </LinearLayout>
60
61 <LinearLayout
62 android:layout_margin="1dp"
63 android:orientation="horizontal"
64 android:id="@+id/detail_container"
65 android:layout_width="200dp"
66 android:layout_height="400dp">
67 </LinearLayout>
68 </LinearLayout>
69</LinearLayout>
一个Container是list_container、一个是detail_container,四个按钮的点击事件分别为dynamicLoad:
1public class MainActivity extends AppCompatActivity {
2 ListFragment leftFragment = null;
3 ListFragment rightFragment = null;
4 @Override
5 protected void onCreate(Bundle savedInstanceState) {
6 super.onCreate(savedInstanceState);
7 setContentView(R.layout.activity_main);
8 }
9
10 // 静态加载
11 public void toStaticLoadActivity(View view) {
12 startActivity(new Intent(MainActivity.this, StaticLoadActivity.class));
13 }
14
15 // 动态加载 1、container 2、fragment 3、fragment -> container
16 public void dynamicLoad(View view) {
17 int id = view.getId();
18 switch (id){
19 case R.id.load_left:
20 leftFragment = new ListFragment();
21 getSupportFragmentManager()
22 .beginTransaction()
23 .add(R.id.list_container, leftFragment)
24 .commit();
25 break;
26 case R.id.load_right:
27 rightFragment = new ListFragment();
28 getSupportFragmentManager()
29 .beginTransaction()
30 .add(R.id.detail_container, rightFragment)
31 .commit();
32 break;
33 case R.id.remove_left:
34 getSupportFragmentManager()
35 .beginTransaction()
36 .remove(leftFragment)
37 .commit();
38 break;
39 case R.id.remove_right:
40 getSupportFragmentManager()
41 .beginTransaction()
42 .remove(rightFragment)
43 .commit();
44 break;
45 }
46 }
47}
动态加载Fragment中,FragmentTransaction类提供了方法完成增删等操作,完成后调用FragmentTransaction.commit()方法提交修改。
-
transaction.add():往Activity里面添加一个片段
-
transaction.remove():从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈,这个Fragment实例将会被销毁
-
transaction.replace():使用另一个Fragment替换当前的,实际上是remove()然后add()的合体
-
transaction.hide():隐藏当前Fragment,仅不可见,不会销毁
-
transaction.show():显示之前隐藏的Fragment
-
detach():会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护
-
attach():重建view视图,附加到UI上并显示。
FragmentTransaction的commit方法一定要在Activity.onSaveInstance()之前调用,commit()操作是异步的,内部通过mManager.enqueueAction()加入处理队列。对应的同步方法为commitNow(),commit()内部会有checkStateLoss()操作,如果开发人员使用不当(比如commit()操作在onSaveInstanceState()之后),可能会抛出异常,而commitAllowingStateLoss()方法则是不会抛出异常版本的commit()方法,但是尽量使用commit(),而不要使用commitAllowingStateLoss()。
FragmentManager拥有回退栈(BackStack),类似于Activity的任务栈,如果添加了该语句,就把该事务加入回退栈,当用户点击返回按钮,会回退该事务(回退指的是如果事务是add(frag1),那么回退操作就是remove(frag1));如果没添加该语句,用户点击返回按钮会直接销毁Activity。
3、使用注意点
1、Fragment的onCreateView()方法返回Fragment的UI布局,需要注意的是inflate()的第三个参数是false,因为在Fragment内部实现中,会把该布局添加到container中,如果设为true,那么就会重复做两次添加,则会抛IllegalStateException异常。
2、如果在创建Fragment时要传入参数,必须要通过setArguments(Bundle bundle)方式添加,而不建议通过为Fragment添加带参数的构造函数,因为通过setArguments()方式添加,在由于内存紧张导致Fragment被系统杀掉并恢复(re-instantiate)时能保留这些数据。
3、可以在Fragment的onAttach()中通过getArguments()获得传进来的参数。如果要获取Activity对象,不建议调用getActivity(),而是在onAttach()中将Context对象强转为Activity对象。
Activity向Fragment传值
在ListFragment写一个获取ListFragment的方法:
1public class ListFragment extends Fragment {
2 private static final String BUNDLE_TITTLE = "bundle_tittle";
3 private String mTittle;
4
5 // 传递一个String tittle进来
6 public static ListFragment getInstance(String tittle){
7 ListFragment fragment = new ListFragment();
8 Bundle bundle = new Bundle();
9 bundle.putString(BUNDLE_TITTLE, tittle);
10 fragment.setArguments(bundle);
11 return fragment;
12 }
13
14 // ....
15
16 @Override
17 public void onCreate(@Nullable Bundle savedInstanceState) {
18 super.onCreate(savedInstanceState);
19 Bundle arguments = getArguments();
20 if(arguments != null){
21 mTittle = arguments.getString(BUNDLE_TITTLE);
22 }
23 }
24
25 // 创建视图
26 @Nullable
27 @Override
28 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
29 View view = inflater.inflate(R.layout.fragment_list, container, false);
30 TextView textView = view.findViewById(R.id.tv_static_load);
31 textView.setText(mTittle);
32 return view;
33 }
34}
这样在创建ListFragment对象的时候,直接ListFragment.getInstance()传参即可:
1switch (id){
2 case R.id.load_left:
3 leftFragment = ListFragment.getInstance("Left Fragment!");
4 getSupportFragmentManager()
5 .beginTransaction()
6 .add(R.id.list_container, leftFragment)
7 .commit();
8 break;
9 case R.id.load_right:
10 rightFragment = ListFragment.getInstance("Right Fragment!");
11 getSupportFragmentManager()
12 .beginTransaction()
13 .add(R.id.detail_container, rightFragment)
14 .commit();
15 break;
16 // ...
17}
Fragment向Activity传值
还是根据上面的例子,首先在ListFragment中定义
1public class ListFragment extends Fragment {
2 ......
3
4 // 创建视图
5 @Nullable
6 @Override
7 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
8 View view = inflater.inflate(R.layout.fragment_list, container, false);
9 TextView textView = view.findViewById(R.id.tv_static_load);
10 textView.setText(tittle);
11 textView.setOnClickListener((v) -> {
12 if(mOnTittleListener != null) {
13 mOnTittleListener.onClick(tittle);
14 }
15 });
16 return view;
17 }
18
19 // 1、定义接口
20 // 当TextView被点击的时候可以把Tittle传出去
21 public interface OnTittleListener {
22 void onClick(String tittle);
23 }
24
25 // 2、定义全局变量
26 private OnTittleListener mOnTittleListener;
27
28 // 3、设置接口的方法
29 public void setOnTittleListener(OnTittleListener onTittleListener) {
30 this.mOnTittleListener = onTittleListener;
31 }
32}
MainActivity中:
1public class MainActivity extends AppCompatActivity implements ListFragment.OnTittleListener {
2 ListFragment leftFragment = null;
3 boolean leftDisplay = false;
4
5 .......
6
7 @Override
8 protected void onCreate(Bundle savedInstanceState) {
9 super.onCreate(savedInstanceState);
10 setContentView(R.layout.activity_main);
11 // 1、container 2、fragment 3、fragment -> container
12 leftFragment = ListFragment.getInstance("Left Fragment!");
13 getSupportFragmentManager()
14 .beginTransaction()
15 .add(R.id.list_container, leftFragment)
16 .commit();
17 leftDisplay = true;
18 leftFragment.setOnTittleListener(this);
19 }
20
21 @Override
22 public void onClick(String tittle) {
23 // 设置Lable为ListFragment传回来的值
24 setTitle(tittle);
25 }
26}
如果在Fragment中需要Context,可以通过getActivity()
,如果该Context需要在Activity被销毁后还存在,则使用getActivity.getApplicationContext()
; 考虑Fragment的重复使用问题,降低与Activity的耦合,Fragment操作应该由它的管理者Activity决定。
Fragment生命周期
Fragment中常用的生命周期方法
当Fragment从创建到运行时回调的生命周期方法有
1、onAttach():当Fragment依附到Activity时调用的方法 2、onCreate():当Fragment创建时调用的方法 3、onCreateView():给Fragment加载布局时调用的方法 4、onActivityCreated():当该Fragment依附的Activity创建时调用的方法 5、onStart():当Fragment启动时调用的方法 6、onResume():当Fragment正在运行时调用的方法
当Fragment不再使用时调用的生命周期方法
7、onPause():当Fragment不在交互时调用该方法 8、onStop():当Fragment不再可见时调用该方法 9、onDestroyView():销毁Fragment布局时调用的方法 10、onDestroy():当Frament销毁时调用的方法 11、onDetach():当Fragment完全脱离Activity时调用的方法
下面通过代码演示一下Fragment的生命周期:
MainActivity的布局中有两个按钮,一个是用于加载Fragment的,另外一个是切换到另外一个Activity的,MainActivity的界面代码如下所示:
1<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 android:orientation="vertical"
6 tools:context=".MainActivity">
7
8 <Button
9 android:onClick="loadFragment"
10 android:text="加载Fragment"
11 android:textAllCaps="false"
12 android:layout_width="match_parent"
13 android:layout_height="wrap_content"/>
14 <Button
15 android:layout_width="match_parent"
16 android:layout_height="wrap_content"
17 android:text="切换到另一个Activity"
18 android:textAllCaps="false"
19 android:onClick="toAnotherActivity" />
20 <LinearLayout
21 android:id="@+id/container"
22 android:layout_width="match_parent"
23 android:layout_height="0dp"
24 android:orientation="vertical"
25 android:layout_weight="2" />
26</LinearLayout>
MyFragment的界面:
1<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 tools:context=".MyFragment">
6 <TextView
7 android:gravity="center"
8 android:layout_marginTop="50dp"
9 android:layout_width="match_parent"
10 android:layout_height="match_parent"
11 android:layout_centerInParent="true"
12 android:text="Fragment的布局显示" />
13</RelativeLayout>
AnotherActivity的界面:
1<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 tools:context=".AnotherActivity">
6
7 <TextView
8 android:layout_centerInParent="true"
9 android:layout_width="match_parent"
10 android:gravity="center"
11 android:text="这是另一个Activity"
12 android:textAllCaps="false"
13 android:layout_height="match_parent"/>
14</RelativeLayout>
MainActivity的逻辑,其实主要就是两个点击事件:
1public class MainActivity extends AppCompatActivity {
2 @Override
3 protected void onCreate(Bundle savedInstanceState) {
4 super.onCreate(savedInstanceState);
5 setContentView(R.layout.activity_main);
6 }
7
8 // 加载Fragment
9 public void loadFragment(View view) {
10 MyFragment fragment = MyFragment.newInstance();
11 getSupportFragmentManager()
12 .beginTransaction()
13 .add(R.id.container, fragment, "MyFragment")
14 .commit();
15 }
16
17 // 跳转另一个Activity
18 public void toAnotherActivity(View view) {
19 startActivity(new Intent(this, AnotherActivity.class));
20 }
21}
MyFragment代码如下:
1public class MyFragment extends Fragment {
2 private static final String TAG = "MyFragment";
3
4 public static MyFragment newInstance() {
5 return new MyFragment();
6 }
7
8 @Override
9 public void onCreate(@Nullable Bundle savedInstanceState) {
10 super.onCreate(savedInstanceState);
11 Log.i(TAG, "onCreate: Fragment创建");
12 }
13
14 @Override
15 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
16 @Nullable Bundle savedInstanceState) {
17 Log.i(TAG, "onCreateView: Fragment绑定布局");
18 return inflater.inflate(R.layout.my_fragment, container, false);
19 }
20
21 @Override
22 public void onActivityCreated(@Nullable Bundle savedInstanceState) {
23 super.onActivityCreated(savedInstanceState);
24 Log.i(TAG, "onActivityCreated: 依附的Activity创建");
25 }
26
27 @Override
28 public void onStart() {
29 super.onStart();
30 Log.i(TAG, "onStart: Fragment启动");
31 }
32
33 @Override
34 public void onResume() {
35 super.onResume();
36 Log.i(TAG, "onResume: Fragment正在运行");
37 }
38
39 @Override
40 public void onPause() {
41 super.onPause();
42 Log.i(TAG, "onPause: Fragment不再交互");
43 }
44
45 @Override
46 public void onStop() {
47 super.onStop();
48 Log.i(TAG, "onStop: Fragment停止运行");
49 }
50
51 @Override
52 public void onDestroyView() {
53 super.onDestroyView();
54 Log.i(TAG, "onDestroyView: Fragment视图销毁");
55 }
56
57 @Override
58 public void onDestroy() {
59 super.onDestroy();
60 Log.i(TAG, "onDestroy: Fragment销毁");
61 }
62
63 @Override
64 public void onDetach() {
65 super.onDetach();
66 Log.i(TAG, "onDetach: Fragment脱离Activity");
67 }
68}
通过演示可以看到如下效果:
110-18 17:18:08.735 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onCreate: Fragment创建
210-18 17:18:08.736 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onCreateView: Fragment绑定布局
310-18 17:18:08.742 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onActivityCreated: 依附的Activity创建
410-18 17:18:08.742 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onStart: Fragment启动
510-18 17:18:08.742 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onResume: Fragment正在运行
610-18 17:18:11.353 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onPause: Fragment不再交互
710-18 17:18:11.820 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onStop: Fragment停止运行
810-18 17:18:14.072 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onStart: Fragment启动
910-18 17:18:14.072 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onResume: Fragment正在运行
1010-18 17:18:16.221 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onPause: Fragment不再交互
1110-18 17:18:16.606 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onStop: Fragment停止运行
1210-18 17:18:16.607 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onDestroyView: Fragment视图销毁
1310-18 17:18:16.619 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onDestroy: Fragment销毁
1410-18 17:18:16.619 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onDetach: Fragment脱离Activity