標籤:表示 his height blog hub protect .sh tap 調用
該文章是一個系列文章,是本人在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方法
@Overridepublic void onHiddenChanged(boolean hidden) { super.onHiddenChanged(hidden); if (hidden){ //Fragment隱藏時調用 }else { //Fragment顯示時調用 }}
源碼地址:源碼傳送門本篇總結
我們在本篇部落格中比較詳細的給出了一個Fragment的最佳實務,我們在許多主流App中都能看到這種頂部、底部導航的效果,並且在此基礎上我們探討了使用Fragment不當的存在效能問題及最佳化。
下篇預告
下篇打算往Fragment中加點東西,ListView
此致,敬禮
Android開發之漫漫長途 XIII——Fragment最佳實務