最近通过网课,了解到一种十分巧妙的fragment的封装以及调度方式。遂记录下理解
Fragment的封装
1 | public abstract class Fragment extends android.support.v4.app.Fragment { |
Fragment的使用
fragment经常与BottomNavigationView一起使用,作为底部导航栏切换界面。所以接着学习一下BottomNavigationView的使用以及封装一个Helper让BottomNavigationView使用更加便捷和更好的提升fragment性能
BottomNavigationView使用
menu布局
1 | <!--navigation_items.xml--> |
- always表示一定会显示,ifRoom表示如果存在空间就放上去。
- 默认在顶部菜单栏显示第一个控件,其余放在下拉选项中。也可用于底部导航栏
底部导航栏示例
菜单栏+颜色选择器1
2
3
4
5
6
7<android.support.design.widget.BottomNavigationView
android:layout_width="match_parent"
android:layout_height="@dimen/len_52"
android:layout_gravity="bottom"
app:menu="@menu/navigation_items"
app:itemIconTint="@color/text_nav"
app:itemTextColor="@color/text_nav">设置监听
- 设置监听 setOnNavigationItemSelectedListener(Activity)
- 实现接口 BottomNavigationView.OnNavigationItemSelectedListener
1
2
3
4
5
6
7// 底部导航栏被点击时触发
public boolean onNavigationItemSelected( MenuItem menuItem){
// 实现UI操作
return true;
// 记得返回true代表可以处理本次点击
}fragment的替换及调度
1 | // 一般替换fragment的方法 |
- getSupporrtFragment()会返回一个FragmentManager,里面带有一个当前Acivity下加载的Fragment列表,所以可以通过它查看当前Fragmant的状态
- 本以为用replace()代替add(),已经可以避免覆盖的问题,并且节约资源了。其实,每次都要new一遍fragment,就不是很节约资源,重要的是,很多fragment数据不能保留复用。于是定义一个NavHelper类,优化fragmnent的调度。NavHelper定义逻辑:
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121/**
* 解决Fragment的调度与重用问题
* 达到最优的Fragment切换
* 用于管理当前已加载的fragment
*/
public class NavHelper<T> {
//所有的Tab集合
private final SparseArray<Tab> tabs = new SparseArray<>();
private final FragmentManager manager;
private final int containerId;
private final Context context;
private final OnTabChangeListener<T> listener;
//当前选中的Tab
private Tab<T> currentTab;
public NavHelper(Context context, int containerId, FragmentManager manager, OnTabChangeListener<T> listener) {
this.manager = manager;
this.containerId = containerId;
this.context = context;
this.listener = listener;
}
// 返回NavHelper实例,便于链式调用
public NavHelper<T> add(int menuId, Tab<T> tab){
tabs.put(menuId, tab);
return this;
}
public Tab<T> getCurrentTab(){
return currentTab;
}
/**
* 执行点击菜单的操作,接管对Meun的调度
* @param menuId 菜单项id
* @return 是否可以处理该点击
*/
public boolean performClickMenu(int menuId){
Tab<T> tab = tabs.get(menuId);
if(tab != null){
doSelect(tab);
return true;
}
return false;
}
/**
* 进行tab的选择操作
*/
private void doSelect(Tab<T> tab){
Tab<T> oldTab = null;
if(currentTab != null){
oldTab = currentTab;
if(oldTab == tab){
// 即二次点击
notifyReselect(tab);
return;
}
}
currentTab = tab;
doTabChanged(currentTab, oldTab);
}
/**
* 进行Fragment的真实调度
*/
private void doTabChanged(Tab<T> newTab, Tab<T> oldTab){
FragmentTransaction ft = manager.beginTransaction();
if(oldTab != null){
if(oldTab.fragment != null){
// 从界面中移除,但是还在fragment的缓存空间中
ft.detach(oldTab.fragment);
}
}
if(newTab != null){
if(newTab.fragment == null){
// 首次新建
Fragment fragment = Fragment.instantiate(context, newTab.clx.getName(), null);
// 保存到缓存中
newTab.fragment = fragment;
// 提交到FragmentManger
ft.add(containerId, fragment, newTab.clx.getName());
}else{
// 从FragmentManger的缓存空间中重新加载到界面中
ft.attach(newTab.fragment);
}
}
// 提交事务
ft.commit();
notifyTabSelect(newTab, oldTab);
}
/**
* 回调监听器
*/
private void notifyTabSelect(Tab<T> newTab, Tab<T> oldTab){
if(listener != null){
listener.onTabChanged(newTab, oldTab);
}
}
private void notifyReselect(Tab<T> tab){
// 刷新Tab
}
/**
* 定义菜单的类型
* static不能让外部类引用
*/
public static class Tab {
// Fragment对应的Class信息
Class<?> clx;
// 额外字段,用户自己设定需要使用
public T extra;
// 内部缓存的对应的fragment,不能被外部获取
Fragment fragment;
public Tab(Class<?> clx, T extra){
this.clx = clx;
this.extra = extra;
}
}
public interface OnTabChangeListener<T>{
void onTabChanged(Tab<T> newTab, Tab<T> oldTab);
}
} - 定义performClickMenu(),从导航栏被点击时自动触发的onNavigationItemSelected()方法那接管对menu的调度。返回boolean标示是否处理该点击
performClickMenu()方法判断点击的tab是否已添加
- Helper类中定义一个集合保存menu中所有的tab。(集合实现使用比Set,List更加轻量级的SparseArray)所以必须添加一个add()方法用于初始化helper后把menu中的tab添加进去。添加tab时需要传入menu的ItemId和Tab的实例。add()中调用SparseArray.put()添加并return this。所以调用时才能helper.add().add()…
定义一个内部类Tab,作用类似于一个中间变量,一个menu项,fragment置于其内。并定义为static类使得能够在其他类中引用。
- 需要传递具体Fragment的Class信息,有内部成员Class<? extends Fragment>
- 定义字段fragment,缓存fragment信息。
- 切换fragment的同时切换一些其他的UI或数据,所以再定义一个额外字段,由用户自己设定需要的改动类型,所以定义为泛型。因此class Tab
。由于在Helper中定义了存放Tab的集合,所以Helper 若添加了该字段,那么Tab的构造方法中需要传入Fragment的具体Class,以及该字段信息。
此处并不一定添加该字段 对于切换时更改UI,也可以放在Helper接管前执行。这样的话,Hepler中的所有泛型及后面的回调方法也可以不需实现。只是这样Helper就没有原来那么全权接管Nav的调度。感觉都可以。
若已添加进集合,则调用doSelect()开始处理操作。
doSelect()中调用doTabChanged()开始真正真正的替换操作。因为doTabChanged()方法中fragment替换分步进行而非一般直接用replace(),即先舍去旧的,再放上新的,需要一个oldTab和newTab。
- 所以添加一个成员字段currentTab,方便操作。所以需要对该字段进行更新,该工作由doSelect()承担。
定义一个oldTab,不做初始化。用更新前的currentTab给oldTab赋值。
若点击的tab是当前tab,则执行刷新notifyReselect()
若不是,更新currentTab,调用替换方法为了严谨,在此之前判断currentTab是否为空,若为空,调用替换方法时传入的oldTab即为null。
doTabChanged()方法大抵与一般替换fragment方式相同:
用FragmentManager.beginTransaction()初始化一个fragmentTransaction- FragmentManager需要在接管时由activity传入,因此需定义内部字段manager
使用transaction调用detach(),attach()在内存中替换fragment。提交,然后调用notifyTabSelect()通知并刷新。
- notifyTabSelect()方法中回调接口方法onTabChanged(),所以需要添加成员字段OnTabChangeListener listener;
在替换前需要判断是否为空。若oldTab不为空,且oldTab.fragment也不为空,则在界面中移除其中的fragment。
若newTab不为空,但newTab.fragment为空,则新建一个Fragment,缓存到fragment中,并transaction.add(containerId, fragment实例, newTab.clx.getName())提交到manager。这里需要一个containerId容器ID,也需要从外部传入。
- 通过Fragment.instantiate(Context, newTab.clx.getName(), null)初始化。这里 使用到了Tab的Class信息,也需要一个Context。所以需要在成员字段中添加一个Context,并在初始化时传入。此处相当于一般操作时 new MyFragment()。
若fragment不为空,从manage缓存空间中重新接载到界面attach()。
- 所以最后编写构造方法,传入所以需要从外部传入的实例ContainerId, Context, OnTabChangeListener, FragmentManager。这些内部字段包括Sparse集合在类中都为final字段。不是很明白为什么
1 | /** |
实际调用:
- 先初始化NavHelper类,传入Context(this), FragmentManager(getSupportFragmentManager()), ContainerId(IdRes), OnTabChangeListener(this, activity已继承该接口)
- 调用NavHelper.add()将所有的fragment以Tab的形式添加到集合中,在此处传入其具体类型为Class clx赋值
别忘了为BottomNavigationView添加监听。
添加监听时继承OnNavigationItemSelectedListener接口,需要实现接口方法onNavigationItemSelected,标示能否处理此次点击,同时调用NavHelper.performClickMenu()方法实现fragment调度的交接。
在NavHelper中完成切换后,会回调接口方法OnTabChangeListener.onTabChanged()。实现该方法,完成切换fragment时UI修改。
FragmentTransaction操作区别
- add() :将fragment添加到container中
- replace() :替换container中的fragment,移除其中所有的fragment。
- hide/show() :隐藏与显示,不移除container中的fragment
- attach/detach() :从布局上移除,但存储在缓存队列中。不会被测量到,但可以被重用,减少frag的重复创建,减少内存抖动。
- remove() :直接移除