Fragment与Activity管理之出入栈、点击事件穿透

单Activity管理多个Fragment进行界面展示、页面跳转是很常见的界面编程方式,Fragment在使用时有一些使用技巧和“坑人”的地方,本文进行总结与分享。

Activity管理Fragment

Activity控制Fragment的展示、布局。

Fragment的展示和切换(出入栈)

通过FragmentManager管理Fragment出入栈,建议为每个Fragment定义一个tag(字符串常量),方便Fragment管理、Activity与Fragment通信,FragmentManager管理Fragment核心代码如下,

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
public static final String TAG_LEAVE = "tag_leave";
public static final String TAG_LEAVE_APPLY = "tag_leave_apply";

private void navigateToTargetFragment(String tag, Bundle param) {
// get fragment
BaseFragment targetFragment;
// 根据tag加载指定的fragment,假定每种fragment只有一个实例存在栈中
if (TAG_LEAVE.equals(tag)) {
if (leaveFragment == null) {
leaveFragment = new LeaveFragment();
}
targetFragment = leaveFragment;
} else if (TAG_LEAVE_APPLY.equals(tag)) {
if (leaveApplyFragment == null) {
leaveApplyFragment = new LeaveApplyFragment();
}
targetFragment = leaveApplyFragment;
} else {
if (leaveFragment == null) {
leaveFragment = new LeaveFragment();
}
targetFragment = leaveFragment;
tag = TAG_LEAVE;
}
// check whether fragment is showing.
if (targetFragment == null || targetFragment.isVisible() || targetFragment.isAdded()) {
return;
}
// 刷新界面,比如activity的titleBar
displayLayout(tag);
// 设置Fragment参数
if (param != null) {
targetFragment.setArguments(param);
}
// 如果FrameLayout为空则用replace方法添加Fragment,
// FrameLayout不为空,直接添加Fragment(注意设置Fragment背景为不透明,如?android:windowBackground)
// 添加Fragment设置tag、压入栈中
if (currentFragment == null) {
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, targetFragment, tag)
.addToBackStack(tag)
.commit();
} else {
fragmentManager.beginTransaction()
.add(R.id.fragment_container, targetFragment, tag)
.addToBackStack(tag)
.commit();
}
currentFragment = targetFragment;
fragmentTag = tag;
}

/**
* 根据tag设置当前界面UI,通常是设置Activity标题栏
*/
private void displayLayout(String tag) {
setCommonRightIcon(R.drawable.common_transparent);
switch (tag) {
case TAG_LEAVE: {
//设置titleBar标题栏
setCommonTitle(R.string.room_leave_resign_manage);
// 设置titleBar右上角图标点击事件
setCommonRightIcon(R.drawable.action_bar_add);
}
break;

case TAG_LEAVE_APPLY: {
setCommonTitle(R.string.room_leave_apply);
}
break;

default:
}
}

如上是Activity添加Fragment到当前界面,并在Fragment入栈时设置Activity UI。

通常情况下,当用户点击返回键(物理返回键、界面虚拟返回键)时,需要返回到上一个Activity而不是直接FinishActivity,而Fragment无法直接拦截物理返回键点击事件,因此需要通过宿主Activity来管理Fragment的出栈。核心代码在Activity,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void onBackPressed() {
backFragment();
}

protected void backFragment() {
fragmentManager.popBackStackImmediate();
if (fragmentManager.getBackStackEntryCount() == 0) {
finish();
} else {
fragmentTag = fragmentManager.getFragments().get(fragmentManager.getBackStackEntryCount() - 1).getTag();
displayLayout(fragmentTag);
}
}

Fragment通信

Fragment和Activity通信、Fragment之间通信(例如左右布局)

Fragment和Activity通信

  • 通过接口回调实现Fragment向Activity的通信,宿主Activity实现通信接口,Fragment调用FragmentEvent的Activity实例传递事件和参数。通信接口类如下:
1
2
3
4
5
6
7
8
9
10
11
public interface FragmentEvent {

/**
* Fragment communicate with activity with FragmentEvent listener.
*
* @param event event event
* @param param event param
*/
Object onFragmentEvent(String event, Object param);
}

Activity实现接口,Fragment获取实例:

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
public class LeaveActivity extends BaseActivity implements FragmentEvent {
...

@Override
public Object onFragmentEvent(String event, Object param) {
if (CommonUtil.isEmpty(event)) {
return null;
}
switch (event) {
case TAG_LEAVE_DETAIL: {
Bundle params = new Bundle();
params.putLong(LeaveDetailFragment.PARAM_LEAVE_ID, (Long) param);
navigateToTargetFragment(TAG_LEAVE_DETAIL, params);
}
break;

case TITLE_TAG: {
fragmentTag = param.toString();
displayLayout(fragmentTag);
}
break;

case BACK_TAG: {
backFragment();
}
break;
}
return null;
}

...
}

/**
* 在BaseFragment中onAttatch()及以后的生命周期方法皆可获取实例Fragment实例
*/
if (getActivity() instanceof FragmentEvent) {
this.mFragmentEvent = (FragmentEvent) getActivity();
}

// Fragment中通过接口实例和Activity通信
mFragmentEvent.onFragmentEvent(BACK_TAG, null);

  • 通过广播,在Activity中注册广播,Fragment发送广播;

Activity向Fragment通信

  • 通过fragmet的setArguments()方法,在fragment初始化的时候传递参数和事件;

  • Fragment中定义public方法,通过Activity中的fragment实例调用;

  • 通过EventBus在activity中向fragment传递事;

Fragment之间通信

  • 通过fragment的public方法,首先fragmentA通过getActivity().getFragmentManager().getFragment…()获取到fragmentB,然后调用fragmentB的public方法,比较繁琐。

  • 使用接口(推荐),首先如上通过接口实现fragment向activity的通信,其次通过public方法实现activity向Fragment的通信,从而间接实现Fragment之间的通信。

  • 使用setTargetFragment()、onActivityResult()、getTargetFragment()进行fragment间的通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FragmentA {

private void send(){
setTargetFragment(fargmentB, ...);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
...
}
}

FragmentB {
getTargetFragment().onActivityResult(requestCode, resultCode, Intent data);
}

Fragment一些坑点

Fragment点击事件穿透

Fragment入栈后若没有被hide,上层Fragment的点击事件会被下发到下一层,通过拦截点击事件消除此影响。
在BaseFragment中为rootView设置点击属性,消化掉此层的点击事件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
if (rootView == null) {
rootView = inflater.inflate(this.getLayoutResId(), container, false);
}
if (rootView.getParent() != null) {
((ViewGroup) rootView.getParent()).removeView(rootView);
}
// preventing click penetration
rootView.setClickable(true);
...
}

参考

https://blog.csdn.net/u012881042/article/details/51798736

(完)


Fragment与Activity管理之出入栈、点击事件穿透
https://blackist.org/2018-12-28-android-fragment-manage/
作者
董猿外
发布于
2018年12月28日
许可协议