该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!


前言

上一篇文章中详细分析了Fragment相关知识,那么作为“小Activity”,Fragment能做什么呢,如何使用Fragment得到最佳实践呢。Fragment的设计最初也许是为了大屏幕平板设备的需求,不过现在Fragment已经广泛运用到我们普通的手机设备上。下图是我们几乎在主流App中都能发现的一个功能。

熟悉Android的朋友一定都会知道,很简单嘛,使用TabHost就OK了!但是殊不知,TabHost并非是那么的简单,它的可扩展性非常的差,不能随意地定制Tab项显示的内容,而且运行还要依赖于ActivityGroup。ActivityGroup原本主要是用于为每一个TabHost的子项管理一个单独的Activity,但目前已经被废弃了。为什么呢?当然就是因为Fragment的出现了!

好了,,下面我就来实现上图的效果,不过在开始之前,首先你必须已经了解Fragment的用法了,如果你对Fragment还比较陌生的话,建议先去阅读我前面的一篇文章Android开发之漫漫长途 XII——Fragment详解

先创建宿主Activity

新建BestFragmentActivity

public class BestFragmentActivity extends AppCompatActivity {

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_best_fragment); //下面是LuseenBottomNavigation的使用
BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomNavigation); BottomNavigationItem bottomNavigationItem = new BottomNavigationItem
("首页", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_account_balance_white_48dp);
BottomNavigationItem bottomNavigationItem1 = new BottomNavigationItem
("分类", ContextCompat.getColor(this, R.color.secondColor), R.mipmap.ic_list_white_48dp); BottomNavigationItem bottomNavigationItem2 = new BottomNavigationItem
("任务", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_add_circle_outline_white_48dp);
BottomNavigationItem bottomNavigationItem3 = new BottomNavigationItem
("购物车", ContextCompat.getColor(this, R.color.thirdColor), R.mipmap.ic_add_shopping_cart_white_48dp); BottomNavigationItem bottomNavigationItem4 = new BottomNavigationItem
("我的", ContextCompat.getColor(this, R.color.colorAccent), R.mipmap.ic_account_box_white_48dp); bottomNavigationView.addTab(bottomNavigationItem);
bottomNavigationView.addTab(bottomNavigationItem1);
bottomNavigationView.addTab(bottomNavigationItem2);
bottomNavigationView.addTab(bottomNavigationItem3);
bottomNavigationView.addTab(bottomNavigationItem4);
} }

对应的布局文件activity_best_fragment

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main_content"
android:fitsSystemWindows="true"
> <!--Fragment之后就动态的放在该布局文件下-->
<FrameLayout
android:id="@+id/frame_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:layout_above="@+id/bottomNavigation"
/> <!--关于底层布局我这里使用了Github上的开源项目-->
<com.luseen.luseenbottomnavigation.BottomNavigation.BottomNavigationView
android:id="@+id/bottomNavigation"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_alignParentBottom="true"
app:bnv_colored_background="false"
app:bnv_with_text="true"
app:bnv_shadow="false"
app:bnv_tablet="false"
app:bnv_viewpager_slide="true"
app:bnv_active_color="@color/colorPrimary"
app:bnv_active_text_size="@dimen/bottom_navigation_text_size_active"
app:bnv_inactive_text_size="@dimen/bottom_navigation_text_size_inactive"/> </RelativeLayout>

关于底层布局我这里使用了Github上的开源项目LuseenBottomNavigation,该项目地址是https://github.com/armcha/LuseenBottomNavigation读者可自行查看

接着创建Fragment

目前Fragment作为演示使用,可以看到布局内容都非常简单,我这里只给出其中一个Fragment的创建过程和源码,项目完整源码可见文末的源码地址。

我们就拿第一个GoodsFragment举例把

public class GoodsFragment extends Fragment {
private static String TAG= GoodsFragment.class.getSimpleName();
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.d(TAG,"onAttach");
} @Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG,"onCreateView");
View view = inflater.inflate(R.layout.fragment_goods, null);
return view;
} @Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.d(TAG,"onViewCreated");
} @Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(TAG,"onActivityCreated");
} @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG,"onCreate");
} @Override
public void onStart() {
super.onStart();
Log.d(TAG,"onStart");
} @Override
public void onResume() {
super.onResume();
Log.d(TAG,"onResume");
} @Override
public void onPause() {
super.onPause();
Log.d(TAG,"onPause");
} @Override
public void onStop() {
super.onStop();
Log.d(TAG,"onStop");
} @Override
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG,"onDestroyView");
} @Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG,"onDestroy");
} @Override
public void onDetach() {
super.onDetach();
Log.d(TAG,"onDetach");
} }

源码非常的简单,在onCreateView中加载布局文件,该布局文件也非常简单,仅仅定义了一个帧布局,在帧布局中包含了一个TextView

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Goods"
android:textStyle="bold"
android:textSize="30sp"
android:layout_gravity="center"/> </FrameLayout>

按照上面的流程我们建立了所需的Fragment,接着该更改BestFragmentActivity的代码,更改后的源码如下

public class BestFragmentActivity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_best_fragment);
//底部导航布局
BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomNavigation); BottomNavigationItem bottomNavigationItem = new BottomNavigationItem
("首页", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_account_balance_white_48dp);
BottomNavigationItem bottomNavigationItem1 = new BottomNavigationItem
("分类", ContextCompat.getColor(this, R.color.secondColor), R.mipmap.ic_list_white_48dp); BottomNavigationItem bottomNavigationItem2 = new BottomNavigationItem
("任务", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_add_circle_outline_white_48dp);
BottomNavigationItem bottomNavigationItem3 = new BottomNavigationItem
("购物车", ContextCompat.getColor(this, R.color.thirdColor), R.mipmap.ic_add_shopping_cart_white_48dp); BottomNavigationItem bottomNavigationItem4 = new BottomNavigationItem
("我的", ContextCompat.getColor(this, R.color.colorAccent), R.mipmap.ic_account_box_white_48dp); bottomNavigationView.addTab(bottomNavigationItem);
bottomNavigationView.addTab(bottomNavigationItem1);
bottomNavigationView.addTab(bottomNavigationItem2);
bottomNavigationView.addTab(bottomNavigationItem3);
bottomNavigationView.addTab(bottomNavigationItem4); //为底部导航布局设置点击事件
bottomNavigationView.setOnBottomNavigationItemClickListener(new OnBottomNavigationItemClickListener() {
@Override
public void onNavigationItemClick(int i) {
switch (i){
case 0:
switchToHome();
break;
case 1:
switchToCategory();
break;
case 2:
switchToTask();
break;
case 3:
switchToGoodCar();
break;
case 4:
switchToAbout();
break;
}
}
}); //初始加载首页,即GoodsFragment
switchToHome();
} private void switchToAbout() {
getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new AboutFragment(),AboutFragment.class.getName()).commit();
}
private void switchToCategory() {
getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new CategoryFragment(),CategoryFragment.class.getName()).commit();
} private void switchToTask() {
getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new TaskFragment(),TaskFragment.class.getName()).commit();
} private void switchToGoodCar() {
getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new GoodCarFragment(),GoodCarFragment.class.getName()).commit();
} private void switchToHome() {
getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new GoodsFragment(),GoodsFragment.class.getName()).commit();
}
}

上面的代码可以根据上一篇文章比较容易的写出来,而且正常运行,可是在实际开发过程中我们不得不考虑代码的性能问题。其实上面的代码存在性能问题,尤其是在底部导航这种场景中,Fragment之间的来回切换,这里使用的replace方法。关于这个方法带来的问题以及如何进行优化,将在下一节详细说明。

Fragment 性能优化问题#

FragmentTransaction##

谈到Fragment的性能优化问题,就不得不对FragmentTransaction进行深入的研究以及探讨,上面使用了getSupportFragmentManager().beginTransaction()得到了FragmentTransaction对象,并依次调用其replace方法和commit方法。

  • replace(int containerViewId, Fragment fragment)、replace(int containerViewId, Fragment fragment, String tag)

该方法的作用是,类似于先remove掉视图容器所有的Fragment,再add方法参数中的fragment,并为该Fragment设置标签tag。

 	getSupportFragmentManager().beginTransaction().add(R.id.frame_content,new AboutFragment(),AboutFragment.class.getName()).commit();
getSupportFragmentManager().beginTransaction().add(R.id.frame_content,new CategoryFragment(),CategoryFragment.class.getName()).commit();
getSupportFragmentManager().beginTransaction().add(R.id.frame_content,new TaskFragment(),TaskFragment.class.getName()).commit();
getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new GoodsFragment(),GoodsFragment.class.getName()).commit();

如上面所示代码块中,我们先进行了3次添加操作,之后的replace操作会移出前面添加的Fragment,再添加方法参数中指定的Frament。

  • add(int containerViewId, Fragment fragment, String tag)、 remove(Fragment fragment)

    FragmentTransaction的Add()操作是维持着一个队列的,在这个队列中,根据ADD进去的先后顺序形成了一个链表,我们上面的操作在这个列表中的形式变化如下图所示:

  • remove(Fragment fragment) : 移除一个已经存在的Fragment.

  • show(Fragment fragment): 显示一个以前被隐藏过的Fragment

  • hide(Fragment fragment) : 隐藏一个存在的Fragment

    注:①Fragment被hide/show,仅仅是隐藏/显示Fragment的视图,不会有任何生命周期方法的调用。

    ②在Fragment中重写onHiddenChanged方法可以对Fragment的hide和show状态进行监听。

还有一些其他的方法这里就不一一列举了,有了上面所列出的方法,我们就能对Fragment有个很不错的优化了。

Fragment性能问题分析与解决##

Fragment性能问题分析###

我们上面是使用replace来切换页面,那么在每次切换的时候,Fragment都会重新实例化,重新加载一边数据,这样非常消耗性能和用户的数据流量。这是因为replace操作,每次都会把container中的现有的fragment实例清空,然后再把指定的fragment添加进去,就就造成了在切换到以前的fragment时,就会重新实例会fragment。

Fragment性能问题解决###

知道了问题的根源所在,那么解决的办法也呼之欲出了。我们不能使用replace来进行页面的切换,那么可使用的方法貌似只有add了,我们可以在加载的时候判断Fragment是不是已经被添加到队列中,如果已添加,我们就显示(show)该Fragment,隐藏(hide)其他,如果没有添加过呢,就添加。这样就能做到多个Fragment切换不重新实例化。具体到代码中就是这样的

public class BestFragmentActivity extends AppCompatActivity{

    //当前的Fragment
private Fragment mCurFragment = new Fragment();
//初始化其他的Fragment
private GoodsFragment mGoodsFragment = new GoodsFragment();
private GoodCarFragment mGoodCarFragment = new GoodCarFragment();
private TaskFragment mTaskFragment = new TaskFragment();
private AboutFragment mAboutFragment = new AboutFragment();
private CategoryFragment mCategoryFragment = new CategoryFragment(); @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_best_fragment);
BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomNavigation); BottomNavigationItem bottomNavigationItem = new BottomNavigationItem
("首页", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_account_balance_white_48dp);
BottomNavigationItem bottomNavigationItem1 = new BottomNavigationItem
("分类", ContextCompat.getColor(this, R.color.secondColor), R.mipmap.ic_list_white_48dp); BottomNavigationItem bottomNavigationItem2 = new BottomNavigationItem
("任务", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_add_circle_outline_white_48dp);
BottomNavigationItem bottomNavigationItem3 = new BottomNavigationItem
("购物车", ContextCompat.getColor(this, R.color.thirdColor), R.mipmap.ic_add_shopping_cart_white_48dp); BottomNavigationItem bottomNavigationItem4 = new BottomNavigationItem
("我的", ContextCompat.getColor(this, R.color.colorAccent), R.mipmap.ic_account_box_white_48dp); bottomNavigationView.addTab(bottomNavigationItem);
bottomNavigationView.addTab(bottomNavigationItem1);
bottomNavigationView.addTab(bottomNavigationItem2);
bottomNavigationView.addTab(bottomNavigationItem3);
bottomNavigationView.addTab(bottomNavigationItem4); bottomNavigationView.setOnBottomNavigationItemClickListener(new OnBottomNavigationItemClickListener() {
@Override
public void onNavigationItemClick(int i) {
switch (i){
case 0:
switchToHome();
break;
case 1:
switchToCategory();
break;
case 2:
switchToTask();
break;
case 3:
switchToGoodCar();
break;
case 4:
switchToAbout();
break;
}
}
});
switchToHome();
} private void switchFragment(Fragment targetFragment){
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
if (!targetFragment.isAdded()) {//如果要显示的targetFragment没有添加过
transaction
.hide(mCurFragment)//隐藏当前Fragment
.add(R.id.frame_content, targetFragment,targetFragment.getClass().getName())//添加targetFragment
.commit();
} else {//如果要显示的targetFragment已经添加过
transaction//隐藏当前Fragment
.hide(mCurFragment)
.show(targetFragment)//显示targetFragment
.commit();
}
//更新当前Fragment为targetFragment
mCurFragment = targetFragment; } private void switchToAbout() {
switchFragment(mAboutFragment);
}
private void switchToCategory() {
switchFragment(mCategoryFragment);
}
private void switchToTask() {
switchFragment(mTaskFragment);
}
private void switchToGoodCar() {
switchFragment(mGoodCarFragment);
}
private void switchToHome() {
switchFragment(mGoodsFragment);
} }

这样就达到了我们的目的,我们在来回切换的操作中,Fragment只实例一次,少了销毁又重新创建等带来的性能消耗,另我们想要在Fragment中更新数据时,我们可以在自定义Fragment中重写其onHiddenChanged方法

@Override
public void onHiddenChanged(boolean hidden) {
   super.onHiddenChanged(hidden);
   if (hidden){
      //Fragment隐藏时调用
   }else {
       //Fragment显示时调用
   } }

源码地址:源码传送门

本篇总结

我们在本篇博客中比较详细的给出了一个Fragment的最佳实践,我们在许多主流App中都能看到这种顶部、底部导航的效果,并且在此基础上我们探讨了使用Fragment不当的存在性能问题及优化。


下篇预告

下篇打算往Fragment中加点东西,ListView


此致,敬礼

Android开发之漫漫长途 XIII——Fragment最佳实践的更多相关文章

  1. Android开发之漫漫长途 XII——Fragment详解

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  2. Android开发之漫漫长途 Fragment番外篇——TabLayout+ViewPager+Fragment

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  3. Android开发之漫漫长途 番外篇——自定义View的各种姿势2

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  4. Android开发之漫漫长途 XI——从I到X的小结

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  5. Android开发之漫漫长途 XVI——ListView与RecyclerView项目实战

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  6. Android开发之漫漫长途 Ⅰ——Android系统的创世之初以及Activity的生命周期

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>中的相关知识,再次表示该书 ...

  7. Android开发之漫漫长途 Ⅱ——Activity的显示之Window和View(2)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  8. Android开发之漫漫长途 Ⅱ——Activity的显示之Window和View(1)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  9. Android开发之漫漫长途 Ⅳ——Activity的显示之ViewRootImpl初探

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

随机推荐

  1. MySQL中各种数据类型的长度及在开发中如何选择

    接触MySQL这个数据库大概快要两年了,不过由于没有特别深入系统的去学习,大多也是停留在一知半解的状态.今天在工作中刚好碰到了表设计的问题,顺便写篇博客,把MySQL中数据类型和字段类型选择这方面给弄 ...

  2. 【Java框架型项目从入门到装逼】第七节 - 学生管理系统项目搭建

    本次的教程是打算用Spring,SpringMVC以及传统的jdbc技术来制作一个简单的增删改查项目,对用户信息进行增删改查,就这么简单. 1.新建项目 首先,打开eclipse,新建一个web项目. ...

  3. C#wxpay和alipay

    wxpayapi using System; namespace EPayInterfaceApp { public class EPayInterfaceApp { /** * 提交被扫支付API ...

  4. 第十七章:Python の Web开发基础(四) MVC与Django

    本課主題 MVC 介绍 Django 介紹 MVC 介绍 controllers 处理用户请求 views 放置HTML模版 models 操作数据库 MVC框架就是目录的归类 MVC 是一种软件开发 ...

  5. mysql之数据操作

    一 介绍 MySQL数据操作: DML 在MySQL管理软件中,可以通过SQL语句中的DML语言来实现数据的操作,包括 使用INSERT实现数据的插入 UPDATE实现数据的更新 使用DELETE实现 ...

  6. 【Splay】bzoj1500(听说此题多码上几遍就能不惧任何平衡树题)

    1500: [NOI2005]维修数列 Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 15112  Solved: 4996[Submit][Statu ...

  7. BZOJ 3211: 花神游历各国【线段树区间开方问题】

    3211: 花神游历各国 Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 3514  Solved: 1306[Submit][Status][Discu ...

  8. 51Nod 1046 A^B Mod C(日常复习快速幂)

    1046 A^B Mod C 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 给出3个正整数A B C,求A^B Mod C. 例如,3 5 8,3^5 Mod 8 = ...

  9. Spring框架学习笔记(8)——AspectJ实现AOP

    使用代理对象实现AOP虽然可以满足需求,但是较为复杂,而Spring提供一种简单的实现AOP的方法AspectJ 同样的计算器的DEMO 首先配置applicationContext.xml < ...

  10. 基于C#的数据库文件管理助手

    我们经常会遇到这样的问题,在数据库中的文件存放的是web格式或者是绝对路径,以及使用的是百度上传或者其他上传组件,造成了很多异步上传的冗余文件,如果客户需要我们导出企业官网中的产品图片,我们该如何处理 ...