前言
上一篇文章中詳細分析了Fragment相關知識,那麼作爲“小Activity”,Fragment能做什麼呢,如何使用Fragment得到最佳實踐呢。Fragment的設計最初也許是爲了大屏幕平板設備的需求,不過現在Fragment已經廣泛運用到我們普通的手機設備上。下圖是我們幾乎在主流App中都能發現的一個功能。
熟悉Android的朋友一定都會知道,很簡單嘛,使用TabHost就OK了!但是殊不知,TabHost並非是那麼的簡單,它的可擴展性非常的差,不能隨意地定製Tab項顯示的內容,而且運行還要依賴於ActivityGroup。ActivityGroup原本主要是用於爲每一個TabHost的子項管理一個單獨的Activity,但目前已經被廢棄了。爲什麼呢?當然就是因爲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不當的存在性能問題及優化。
Good luck!
Reprinted by Jimmy.li