0%

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
2
3
4
// 静态加载
public void toStaticLoadActivity(View view) {
startActivity(new Intent(MainActivity.this, StaticLoadActivity.class));
}

StaticLoadActivity的布局文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">

<fragment
android:id="@+id/list_fragment"
android:name="com.example.learnfragment.ListFragment"
android:layout_width="250dp"
android:layout_height="150dp"/>

<fragment
android:layout_marginTop="20dp"
android:id="@+id/list_fragment_two"
android:name="com.example.learnfragment.ListFragment"
android:layout_width="250dp"
android:layout_gravity="center"
android:layout_height="150dp"/>
</LinearLayout>

其中fragment的name属性都指向了com.example.learnfragment.ListFragment:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 列表 Fragment
public class ListFragment extends Fragment {

// 创建视图
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_list, container, false);
TextView textView = view.findViewById(R.id.tv_static_load);
textView.setText("Hello, Fragment!");
return view;
}
}

fragment_list.xml 布局文件如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="vertical"
android:background="@color/colorPrimary"
android:layout_height="match_parent">

<TextView
android:id="@+id/tv_static_load"
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center"
android:textColor="#ffffff"
android:textSize="20sp"
android:text="This is a fragment" />
</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
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
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">

<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center"
android:onClick="toStaticLoadActivity"
android:text="static load fragment" />

<LinearLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<Button
android:id="@+id/load_left"
android:text="加载左边"
android:layout_width="0dp"
android:layout_weight="1"
android:onClick="dynamicLoad"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/load_right"
android:text="加载右边"
android:layout_width="0dp"
android:layout_weight="1"
android:onClick="dynamicLoad"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/remove_left"
android:text="删除左边"
android:layout_width="0dp"
android:layout_weight="1"
android:onClick="dynamicLoad"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/remove_right"
android:text="删除右边"
android:layout_width="0dp"
android:layout_weight="1"
android:onClick="dynamicLoad"
android:layout_height="wrap_content"/>
</LinearLayout>

<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_margin="1dp"
android:id="@+id/list_container"
android:layout_width="150dp"
android:orientation="horizontal"
android:layout_height="400dp">
</LinearLayout>

<LinearLayout
android:layout_margin="1dp"
android:orientation="horizontal"
android:id="@+id/detail_container"
android:layout_width="200dp"
android:layout_height="400dp">
</LinearLayout>
</LinearLayout>
</LinearLayout>

一个Container是list_container、一个是detail_container,四个按钮的点击事件分别为dynamicLoad:

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
public class MainActivity extends AppCompatActivity {
ListFragment leftFragment = null;
ListFragment rightFragment = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

// 静态加载
public void toStaticLoadActivity(View view) {
startActivity(new Intent(MainActivity.this, StaticLoadActivity.class));
}

// 动态加载 1、container 2、fragment 3、fragment -> container
public void dynamicLoad(View view) {
int id = view.getId();
switch (id){
case R.id.load_left:
leftFragment = new ListFragment();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.list_container, leftFragment)
.commit();
break;
case R.id.load_right:
rightFragment = new ListFragment();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.detail_container, rightFragment)
.commit();
break;
case R.id.remove_left:
getSupportFragmentManager()
.beginTransaction()
.remove(leftFragment)
.commit();
break;
case R.id.remove_right:
getSupportFragmentManager()
.beginTransaction()
.remove(rightFragment)
.commit();
break;
}
}
}

动态加载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的方法:

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
public class ListFragment extends Fragment {
private static final String BUNDLE_TITTLE = "bundle_tittle";
private String mTittle;

// 传递一个String tittle进来
public static ListFragment getInstance(String tittle){
ListFragment fragment = new ListFragment();
Bundle bundle = new Bundle();
bundle.putString(BUNDLE_TITTLE, tittle);
fragment.setArguments(bundle);
return fragment;
}

// ....

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle arguments = getArguments();
if(arguments != null){
mTittle = arguments.getString(BUNDLE_TITTLE);
}
}

// 创建视图
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_list, container, false);
TextView textView = view.findViewById(R.id.tv_static_load);
textView.setText(mTittle);
return view;
}
}

这样在创建ListFragment对象的时候,直接ListFragment.getInstance()传参即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
switch (id){
case R.id.load_left:
leftFragment = ListFragment.getInstance("Left Fragment!");
getSupportFragmentManager()
.beginTransaction()
.add(R.id.list_container, leftFragment)
.commit();
break;
case R.id.load_right:
rightFragment = ListFragment.getInstance("Right Fragment!");
getSupportFragmentManager()
.beginTransaction()
.add(R.id.detail_container, rightFragment)
.commit();
break;
// ...
}

Fragment向Activity传值

还是根据上面的例子,首先在ListFragment中定义

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
public class ListFragment extends Fragment {
......

// 创建视图
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_list, container, false);
TextView textView = view.findViewById(R.id.tv_static_load);
textView.setText(tittle);
textView.setOnClickListener((v) -> {
if(mOnTittleListener != null) {
mOnTittleListener.onClick(tittle);
}
});
return view;
}

// 1、定义接口
// 当TextView被点击的时候可以把Tittle传出去
public interface OnTittleListener {
void onClick(String tittle);
}

// 2、定义全局变量
private OnTittleListener mOnTittleListener;

// 3、设置接口的方法
public void setOnTittleListener(OnTittleListener onTittleListener) {
this.mOnTittleListener = onTittleListener;
}
}

MainActivity中:

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
public class MainActivity extends AppCompatActivity implements ListFragment.OnTittleListener {
ListFragment leftFragment = null;
boolean leftDisplay = false;

.......

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1、container 2、fragment 3、fragment -> container
leftFragment = ListFragment.getInstance("Left Fragment!");
getSupportFragmentManager()
.beginTransaction()
.add(R.id.list_container, leftFragment)
.commit();
leftDisplay = true;
leftFragment.setOnTittleListener(this);
}

@Override
public void onClick(String tittle) {
// 设置Lable为ListFragment传回来的值
setTitle(tittle);
}
}

如果在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
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
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">

<Button
android:onClick="loadFragment"
android:text="加载Fragment"
android:textAllCaps="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="切换到另一个Activity"
android:textAllCaps="false"
android:onClick="toAnotherActivity" />
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
android:layout_weight="2" />
</LinearLayout>

MyFragment的界面:

1
2
3
4
5
6
7
8
9
10
11
12
13
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MyFragment">
<TextView
android:gravity="center"
android:layout_marginTop="50dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:text="Fragment的布局显示" />
</RelativeLayout>

AnotherActivity的界面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AnotherActivity">

<TextView
android:layout_centerInParent="true"
android:layout_width="match_parent"
android:gravity="center"
android:text="这是另一个Activity"
android:textAllCaps="false"
android:layout_height="match_parent"/>
</RelativeLayout>

MainActivity的逻辑,其实主要就是两个点击事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

// 加载Fragment
public void loadFragment(View view) {
MyFragment fragment = MyFragment.newInstance();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.container, fragment, "MyFragment")
.commit();
}

// 跳转另一个Activity
public void toAnotherActivity(View view) {
startActivity(new Intent(this, AnotherActivity.class));
}
}

MyFragment代码如下:

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
public class MyFragment extends Fragment {
private static final String TAG = "MyFragment";

public static MyFragment newInstance() {
return new MyFragment();
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate: Fragment创建");
}

@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Log.i(TAG, "onCreateView: Fragment绑定布局");
return inflater.inflate(R.layout.my_fragment, container, false);
}

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.i(TAG, "onActivityCreated: 依附的Activity创建");
}

@Override
public void onStart() {
super.onStart();
Log.i(TAG, "onStart: Fragment启动");
}

@Override
public void onResume() {
super.onResume();
Log.i(TAG, "onResume: Fragment正在运行");
}

@Override
public void onPause() {
super.onPause();
Log.i(TAG, "onPause: Fragment不再交互");
}

@Override
public void onStop() {
super.onStop();
Log.i(TAG, "onStop: Fragment停止运行");
}

@Override
public void onDestroyView() {
super.onDestroyView();
Log.i(TAG, "onDestroyView: Fragment视图销毁");
}

@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: Fragment销毁");
}

@Override
public void onDetach() {
super.onDetach();
Log.i(TAG, "onDetach: Fragment脱离Activity");
}
}

通过演示可以看到如下效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
10-18 17:18:08.735 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onCreate: Fragment创建
10-18 17:18:08.736 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onCreateView: Fragment绑定布局
10-18 17:18:08.742 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onActivityCreated: 依附的Activity创建
10-18 17:18:08.742 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onStart: Fragment启动
10-18 17:18:08.742 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onResume: Fragment正在运行
10-18 17:18:11.353 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onPause: Fragment不再交互
10-18 17:18:11.820 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onStop: Fragment停止运行
10-18 17:18:14.072 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onStart: Fragment启动
10-18 17:18:14.072 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onResume: Fragment正在运行
10-18 17:18:16.221 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onPause: Fragment不再交互
10-18 17:18:16.606 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onStop: Fragment停止运行
10-18 17:18:16.607 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onDestroyView: Fragment视图销毁
10-18 17:18:16.619 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onDestroy: Fragment销毁
10-18 17:18:16.619 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onDetach: Fragment脱离Activity
  • 本文作者: Tim
  • 本文链接: https://zouchanglin.cn/2892194764.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!