該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,我會盡可能按照先易後難的順序進行編寫該系列。該系列引用了《Android開發藝術探索》以及《深刻理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相關知識,另外也借鑑了其餘的優質博客,在此向各位大神表示感謝,膜拜!!!android
上一篇文章中詳細分析了Fragment相關知識,那麼做爲「小Activity」,Fragment能作什麼呢,如何使用Fragment獲得最佳實踐呢。Fragment的設計最初也許是爲了大屏幕平板設備的需求,不過如今Fragment已經普遍運用到咱們普通的手機設備上。下圖是咱們幾乎在主流App中都能發現的一個功能。git
熟悉Android的朋友必定都會知道,很簡單嘛,使用TabHost就OK了!可是卻不知,TabHost並不是是那麼的簡單,它的可擴展性很是的差,不能隨意地定製Tab項顯示的內容,並且運行還要依賴於ActivityGroup。ActivityGroup本來主要是用於爲每個TabHost的子項管理一個單獨的Activity,但目前已經被廢棄了。爲何呢?固然就是由於Fragment的出現了!github
好了,,下面我就來實現上圖的效果,不過在開始以前,首先你必須已經瞭解Fragment的用法了,若是你對Fragment還比較陌生的話,建議先去閱讀我前面的一篇文章Android開發之漫漫長途 XII——Fragment詳解segmentfault
新建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_fragmentapp
<?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讀者可自行查看ide
目前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
此致,敬禮