安卓开发中Fragment的一些使用心得
最近我们综合课程设计小组在开发一款名为“快行”的安卓App,核心是基于高德地图SDK,通过我们自己设计的算法,给用户推荐最快捷的一条出行路线。
快行的Github仓库地址 DannyDiao/KuaiXing
为什么使用Fragment来进行开发?
因为之前第一轮学习安卓开发(大一)的时候,我跳过了Fragment碎片这章Orz,所以最早开发快行的时候我的构思是写三个LinearLayout配合ViewPager实现页面切换。(快行有三块内容,地图、路线和关于)
但是上Github看了一圈没有现成的轮子,自己折腾了一个下午都没有撸出来代码,遂放弃。最后还是决定用Fragment配合谷歌官方的BottomNavigation实现页面切换。
Fragment的两种常用用法
静态使用
这种用法是最简单和最基础的,适合这个碎片基本不做变动时使用,例子:
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/fragment_test"
android:name="com.dannydiao.kuaixing.FragmentTest">
</fragment>
layout长宽和id属性就不做解释了哈,主要是这个name属性,它指定了一个Fragment类,这里呢一般都是自己写一个新的碎片类继承自Fragment类,本例中是指定了FragmentTest类,它的代码如下:
public class FragementTest extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater,ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.id.FragmentTest_Layout,container,false);
return view;
}
}
这里的代码很简单,我们新建FragmentTest类继承自Fragment类,并在其中重写了onCreateView这个方法,意为在碎片新建的时候创建该fragment对应的视图,并返回给调用者。R.id.FragmentTest_Layout就是该碎片对应的Layout文件。
其实标准的Fragment写法并不是这么简单的,但是只要重写了这个方法并返回view就可以使碎片正常运作了,运行一下就会发现fragment那个地方已经正确渲染并显示出了我们设计的界面。
如果我们用File>New>Fragment的方式新建一个碎片,那情况会是怎么样的呢?
我们会发现代码很长很长…
public class BlankFragment extends Fragment {
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private String mParam1;
private String mParam2;
private OnFragmentInteractionListener mListener;
public BlankFragment() {
}
public static BlankFragment newInstance(String param1, String param2) {
BlankFragment fragment = new BlankFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_blank, container, false);
}
public void onButtonPressed(Uri uri) {
if (mListener != null) {
mListener.onFragmentInteraction(uri);
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
void onFragmentInteraction(Uri uri);
}
}
其中重写了很多方法,包括我们已经重写过的onCreateView以及相近的onCreate方法。这里涉及到Fragment的构造器问题,我之后有机会再另外写博客跟大家分享,大家也可以参考以下文章进一步研究:
动态使用
如果只能静态使用碎片的话,那未免也太单调了一些,所以Fragment还有一个杀手级用法,就是动态使用碎片。以我写的快行为例,我把地图、路线和关于三个板块的内容分别放在了三个碎片中,名字也很直接LeftFragment、MiddleFragment、RightFragment。我在里面分别实现了各个模块的功能,也写好了布局,最后通过BottomNavigation实现了动态切换碎片到主页面上。代码如下:
//监听NavigationMenu切换事件
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.nav_map:
showFragment(leftFragment);
return true;
case R.id.nav_route:
showFragment(middleFragment);
return true;
case R.id.nav_about:
showFragment(rightFragment);
return true;
}
return false;
}
onNavigationItemSelected是BottomNavigation的监听器,传入Menu Item的id值,用switch来做筛选,当选到nav_map,也就是地图板块的时候,我调用了showFragment(leftFragment)方法来把leftFragment显示到主布局上。 showFragment()代码如下:
private void showFragment(Fragment fg){
FragmentTransaction transaction = fragmentManager.beginTransaction();
//如果之前没有添加过
if(!fg.isAdded()){
transaction
.add(R.id.fragment_layout,fg)
.hide(currentFragment);
}else{
transaction
.hide(currentFragment)
.show(fg);
}
currentFragment = fg;
//transaction.addToBackStack(null);
transaction.commit();
}
代码先对这个碎片有没有被添加过进行了判断。如果没有被添加过,就调用FragmentTransaction.add()方法添加碎片。如果被添加过了,就hide当前碎片(全局变量currentFragment记录当前碎片),再show参数碎片。最后把currentFragment的值设为参数碎片,使用commit方法提交一下transaction。
那么你可能会问了,明明有FragmentTransaction.replace()方法不用,为啥要这么麻烦?那是因为replace方法是先销毁这个碎片实例再add下一个,这样前一个碎片的数据状态就不能保存。此外,replace方法也有可能导致碎片栈溢出,具体可以参考其他博客。