whale 2019-06-26
该文章是一个系列文章,是本人在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详解
新建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的创建过程和源码,项目完整源码可见文末的源码地址。
我们就拿第一个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进行深入的研究以及探讨,上面使用了getSupportFragmentManager().beginTransaction()得到了FragmentTransaction对象,并依次调用其replace方法和commit方法。
该方法的作用是,类似于先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。
FragmentTransaction的Add()操作是维持着一个队列的,在这个队列中,根据ADD进去的先后顺序形成了一个链表,我们上面的操作在这个列表中的形式变化如下图所示:
注:①Fragment被hide/show,仅仅是隐藏/显示Fragment的视图,不会有任何生命周期方法的调用。
②在Fragment中重写onHiddenChanged方法可以对Fragment的hide和show状态进行监听。
还有一些其他的方法这里就不一一列举了,有了上面所列出的方法,我们就能对Fragment有个很不错的优化了。
我们上面是使用replace来切换页面,那么在每次切换的时候,Fragment都会重新实例化,重新加载一边数据,这样非常消耗性能和用户的数据流量。这是因为replace操作,每次都会把container中的现有的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
此致,敬礼