1. 程式人生 > >Android框架之路——Fragmentation的使用(流式互動Demo)

Android框架之路——Fragmentation的使用(流式互動Demo)

簡介:

YoKey大神的Fragment庫Fragmentation,主要用於現在App經常需要實現的單Activity+多Fragment以及多Activity+多Fragment的形式架構。同時最最重要的是,它幫助我們封裝了很多好用的方法,解決了一些官方Fragment庫中存在的一些Bug。
我在學習做一款有關Ble藍芽防丟器的App時想要嘗試以單Activity+多Fragment的架構去實現,恰好可以使用這個庫,也就皮毛的研究了一下。之前我是通過ViewPager去管理多個Fragment的進出,後來還是拋棄這種方式,因為確實不太合理。所以,用了Fragmentation這個庫,還是非常不錯的。

大神的話:

使用教程:

  1. 新增依賴

    compile 'me.yokeyword:fragmentation:最新版'
    

    我的是

    compile 'me.yokeyword:fragmentation:0.10.3'
    
  2. 單Activity需要繼承自SupportActivity,多Fragment都繼承自SupportFragment。可以在AndroidStudio中使用Ctrl+H快捷鍵檢視類的繼承結構,如下所示,SupportActivity繼承自AppCompatActivity,爺爺正好是FragmentActivity,SupportFragment繼承自V4包下的Fragment,所以基本不影響我們使用:
                enter image description here


                enter image description here
  3. 一些API的使用(對照流式互動Demo)

    • 先看一下demo的整體效果
            enter image description here      enter image description here      enter image description here
    • 這裡先貼出單Activity的程式碼,程式碼內我添加了一些註釋,基本可以瞭解清楚MainActivity 中做了哪些事情。這裡可以列出比較重要的Api:

      1. 裝載根Fragment,一般在saveInstanceState==null時load;

        loadRootFragment(R.id.fl_container, HomeFragment.newInstance()); //activity初始載入HomeFragment
        
      2. 在Activity中註冊所有Fragment生命週期的回撥函式,可以監聽該Activity下的所有Fragment的18個 生命週期方法,這裡監聽的是Fragment的建立:

          registerFragmentLifecycleCallbacks(new FragmentLifecycleCallbacks() {
                // 當有Fragment Create時回撥,列印log
        
                @Override
                public void onFragmentCreated(SupportFragment fragment, Bundle savedInstanceState) {
                    Log.i("MainActivity", "onFragmentCreated--->" + fragment.getClass().getSimpleName());
                }
            });
        
      3. 在Activity中重寫onCreateFragmentAnimator()方法來設定所有Fragment的出場消失動畫,如果在某個單獨的Fragment中複寫該方法,則只單獨對該Fragment有效,這裡可以詳細的看一下wiki:使用場景- 轉場動畫
      4. 通過重寫父類SupportActivity的onBackPressedSupport()方法,可以輕鬆的實現按下手機返回鍵要實現的功能,在下面的demo原始碼中給出了詳細的註釋,這裡也可以詳細的看一下wiki:使用場景- Back鍵的事件傳遞機制
      5. 啟動跳轉Fragment,

        // 啟動新的Fragment,啟動者和被啟動者是在同一個棧的
        start(SupportFragment fragment)
        
        // 以某種啟動模式,啟動新的Fragment
        start(SupportFragment fragment, int launchMode)
        
        // 啟動新的Fragment,並能接收到新Fragment的資料返回
        startForResult(SupportFragment fragment,int requestCode)
        
        // 啟動目標Fragment,並關閉當前Fragment;不要嘗試pop()+start(),動畫會有問題
        startWithPop(SupportFragment fragment)
        
      6. 出棧,移出某個Fragment

        // 當前Fragment出棧(在當前Fragment所在棧內pop)
        pop();
        
        // 出棧某一個Fragment棧內之上的所有Fragment
        popTo(Class fragmentClass/String tag, boolean includeSelf);
        
        // 出棧某一個Fragment棧內之上的所有Fragment。如果想出棧後,緊接著.beginTransaction()開始一個新事務,
        //請使用下面的方法, 防止多事務連續執行的異常
        popTo(Class fragmentClass, boolean includeSelf, Runnable afterTransaction);
        
      7. 查詢獲取某一Fragment

        // 獲取所在棧內的棧頂Fragment
        getTopFragment();
        
        // 獲取當前Fragment所在棧內的前一個Fragment
        getPreFragment();
        
        // 獲取所在棧內的某個Fragment,可以是xxxFragment.Class,也可以是tag
        findFragment(Class fragmentClass/String tag);
        
      8. 防止動畫卡頓,可以先讓其載入完Fragment的轉場動畫,然後繼續實現一些繁瑣的業務邏輯。在Fragment中重寫onEnterAnimationEnd(Bundle saveInstanceState)方法,在這個方法裡繼續執行一些繁瑣操作。通常可以採取這樣的一種模式:

        public View onCreateView(...) {
            ...
            // 這裡僅給一些findViewById等輕量UI的操作
            initView();
            return view;
        }
        
        @Override
        protected void onEnterAnimationEnd(Bundle saveInstanceState) {
             // 這裡設定Listener、各種Adapter、請求資料等等
            initLazyView();
        }
        
      9. Fragment例項呼叫startForResult(SupportFragment fragment,int requestCode)用來Fragment之間返回資料,類似於Activity的startActivityForResult()。大致用法如下;

        public class DetailFragment extends SupportFragment{
        
          private void goDetail(){
              // 啟動ModifyDetailFragment
              startForResult(ModifyDetailFragment.newInstance(mTitle), REQ_CODE);
          }
        
          // ModifyDetailFragment呼叫setFragmentResult()後,在其出棧時,會回撥該方法
          @Override
          public void onFragmentResult(int requestCode, int resultCode, Bundle data) {
              super.onFragmentResult(requestCode, resultCode, data);
              if (requestCode == REQ_CODE && resultCode == RESULT_OK ) {
                  // 在此通過Bundle data 獲取返回的資料
              }
          }
        }
        
        public class ModifyTitleFragment extends SupportFragment{
            // 設定傳給上個Fragment的bundle資料
            private void setResult(){
                Bundle bundle = new Bundle();
                bundle.putString("title", "xxxx");
                setFramgentResult(RESULT_OK, bundle);
            }
        }
        
      10. 以某種啟動模式啟動Fragment:start(SupportFragment fragment, int launchMode)。下面是以SingleTask模式重新啟動一個已存在的Fragment的標準程式碼: 比如:HomeFragment->B Fragment->C Fragment,C Fragment以SingleTask模式重新啟動HomeFragment。在被以SingleTask模式啟動的Fragment中重寫onNewBundle()方法,可以接收到SINGLETASK/SINGTOP啟動模式傳遞的資料。類似於Activity中的onNewIntent()。

        // 任意同棧內的Fragment中:
        HomeFragment fragment = findFragment(HomeFragment.class);
        Bundle newBundle = new Bundle();
        newBundle.putString("from", "主頁-->來自:" + topFragment.getClass().getSimpleName());
        fragment.putNewBundle(newBundle);
        // 在棧內的HomeFragment以SingleTask模式啟動(即在其之上的Fragment會出棧)
        start(fragment, SupportFragment.SINGLETASK);
        
        public class HomeFragment extends SupportFragment{
            @Override
            protected void onNewBundle(Bundle newBundle){
                // 在此可以接收到SINGLETASK/SINGTOP啟動模式傳遞的資料  類似Activity中的onNewIntent()
                Toast.makeText(_mActivity, args.getString("from"), Toast.LENGTH_SHORT).show();
            }
        }
        

      附:MainActivity .java

          /**
           * 流程式demo  tip: 多使用右上角的"檢視棧檢視"
           * Created by YoKeyword on 16/1/29.
           */
          public class MainActivity extends SupportActivity
                  implements NavigationView.OnNavigationItemSelectedListener, BaseMainFragment.OnFragmentOpenDrawerListener
                  , LoginFragment.OnLoginSuccessListener, SwipeBackSampleFragment.OnLockDrawLayoutListener {
              public static final String TAG = MainActivity.class.getSimpleName();
      
              // 再點一次退出程式時間設定
              private static final long WAIT_TIME = 2000L;
      
              private long TOUCH_TIME = 0;  //點選返回鍵時間
      
              private DrawerLayout mDrawer;
              private NavigationView mNavigationView;
              private TextView mTvName;   // NavigationView上的名字
              private ImageView mImgNav;  // NavigationView上的頭像
      
              @Override
              protected void onCreate(Bundle savedInstanceState) {
                  super.onCreate(savedInstanceState);
                  setContentView(R.layout.activity_main);
      
                  if (savedInstanceState == null) {
                      loadRootFragment(R.id.fl_container, HomeFragment.newInstance()); //activity初始載入HomeFragment
                  }
      
                  initView();
      
                  //在Activity中註冊所有Fragment生命週期的回撥函式,可以監聽該Activity下的所有Fragment的18個 生命週期方法
                  registerFragmentLifecycleCallbacks(new FragmentLifecycleCallbacks() {
                      // 當有Fragment Create時回撥,列印log
      
                      @Override
                      public void onFragmentCreated(SupportFragment fragment, Bundle savedInstanceState) {
                          Log.i("MainActivity", "onFragmentCreated--->" + fragment.getClass().getSimpleName());
                      }
                  });
              }
      
              //設定所有Fragment的轉場動畫
              @Override
              public FragmentAnimator onCreateFragmentAnimator() {
                  // 設定預設Fragment動畫  預設豎向(和安卓5.0以上的動畫相同)
                  return super.onCreateFragmentAnimator();
                  // 設定橫向(和安卓4.x動畫相同)
          //        return new DefaultHorizontalAnimator();
                  // 設定自定義動畫
          //        return new FragmentAnimator(enter,exit,popEnter,popExit);
              }
      
              private void initView() {
                  mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
                  ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                          this, mDrawer, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
          //        mDrawer.setDrawerListener(toggle);
                  toggle.syncState();
      
                  mNavigationView = (NavigationView) findViewById(R.id.nav_view);
                  mNavigationView.setNavigationItemSelectedListener(this);  //設定NavigationItem的點選事件
                  mNavigationView.setCheckedItem(R.id.nav_home);    //預設初始設定首頁被選中
      
                  //繫結NavigationView的headview裡的控制元件
                  //設定點選事件登入(延時250ms跳轉LoginFragment)
                  LinearLayout llNavHeader = (LinearLayout) mNavigationView.getHeaderView(0);
                  mTvName = (TextView) llNavHeader.findViewById(R.id.tv_name);
                  mImgNav = (ImageView) llNavHeader.findViewById(R.id.img_nav);
                  llNavHeader.setOnClickListener(new View.OnClickListener() {
                      @Override
                      public void onClick(View v) {
                          mDrawer.closeDrawer(GravityCompat.START);
      
                          mDrawer.postDelayed(new Runnable() {
                              @Override
                              public void run() {
                                  goLogin();
                              }
                          }, 250);
                      }
                  });
              }
      
              //設定手機返回鍵事件
              //如果側邊欄開啟則關閉,否則: 如果棧頂的Fragment是BaseMainFragment的例項,那麼先設定mNavigationView的nav_home被選中
              //                          同時如果棧中不止一個Fragment,就出棧一個,否則提示是否要再按一次退出。在WAIT_TIME時間內再按則退出。
              @Override
              public void onBackPressedSupport() {
                  if (mDrawer.isDrawerOpen(GravityCompat.START)) {
                      mDrawer.closeDrawer(GravityCompat.START);
                  } else {
                      Fragment topFragment = getTopFragment();
      
                      // 主頁的Fragment
                      if (topFragment instanceof BaseMainFragment) {
                          mNavigationView.setCheckedItem(R.id.nav_home);
                      }
      
                      if (getSupportFragmentManager().getBackStackEntryCount() > 1) {
                          pop();
                      } else {
                          if (System.currentTimeMillis() - TOUCH_TIME < WAIT_TIME) {
                              finish();
                          } else {
                              TOUCH_TIME = System.currentTimeMillis();
                              Toast.makeText(this, R.string.press_again_exit, Toast.LENGTH_SHORT).show();
                          }
                      }
                  }
              }
      
              /**
               * 開啟抽屜
               */
              @Override
              public void onOpenDrawer() {
                  if (!mDrawer.isDrawerOpen(GravityCompat.START)) {
                      mDrawer.openDrawer(GravityCompat.START);
                  }
              }
      
              @Override
              public boolean onNavigationItemSelected(final MenuItem item) {
                  mDrawer.closeDrawer(GravityCompat.START);
      
                  mDrawer.postDelayed(new Runnable() {
                      @Override
                      public void run() {
                          int id = item.getItemId();
      
                          //獲取棧頂的Fragment
                          final SupportFragment topFragment = getTopFragment();
      
                          if (id == R.id.nav_home) {
      
                              HomeFragment fragment = findFragment(HomeFragment.class); //根據Fragment類名查詢對應的Fragment
                              //fragment再次啟動時,fragment類中通過重寫onNewBundle方法取出資料
                              Bundle newBundle = new Bundle();
                              newBundle.putString("from", "主頁-->來自:" + topFragment.getClass().getSimpleName());
                              fragment.putNewBundle(newBundle);
      
                              start(fragment, SupportFragment.SINGLETASK);  //跳轉HomeFragment
      
                          } else if (id == R.id.nav_discover) {
                              DiscoverFragment fragment = findFragment(DiscoverFragment.class);
                              if (fragment == null) {
                                  //出棧某一個Fragment之上的所有Fragment,並執行一個新事務
                                  //這裡是將所有HomeFragment之上的Fragment出棧,並立即start DiscoverFragment
                                  popTo(HomeFragment.class, false, new Runnable() {
                                      @Override
                                      public void run() {
                                          start(DiscoverFragment.newInstance());
                                      }
                                  });
                              } else {
                                  // 如果已經在棧內,則以SingleTask模式start
                                  start(fragment, SupportFragment.SINGLETASK);
                              }
                          } else if (id == R.id.nav_msg) {
                              ShopFragment fragment = findFragment(ShopFragment.class);
                              if (fragment == null) {
                                  popTo(HomeFragment.class, false, new Runnable() {
                                      @Override
                                      public void run() {
                                          start(ShopFragment.newInstance());
                                      }
                                  });
                              } else {
                                  // 如果已經在棧內,則以SingleTask模式start,也可以用popTo
          //                        start(fragment, SupportFragment.SINGLETASK);
                                  popTo(ShopFragment.class, false);
                              }
                          } else if (id == R.id.nav_login) {
                              goLogin();
                          } else if (id == R.id.nav_swipe_back) {
                              startActivity(new Intent(MainActivity.this, SwipeBackSampleActivity.class));
                          } else if (id == R.id.nav_swipe_back_f) {
                              start(SwipeBackSampleFragment.newInstance());
                          }
                      }
                  }, 250);
      
                  return true;
              }
      
              private void goLogin() {
                  //啟動目標Fragment
                  start(LoginFragment.newInstance());
              }
      
              @Override
              public void onLoginSuccess(String account) {
                  mTvName.setText(account);
                  mImgNav.setImageResource(R.drawable.ic_account_circle_white_48dp);
                  Toast.makeText(this, "登入成功,NavigationView的使用者名稱已經更改!", Toast.LENGTH_SHORT).show();
              }
      
              @Override
              public void onLockDrawLayout(boolean lock) {
                  if (lock) {
                      mDrawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
                  } else {
                      mDrawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
                  }
              }
          }
      

      HomeFragment.java

          package me.yokeyword.sample.demo_flow.ui.fragment.home;
      
          import android.os.Bundle;
          import android.support.design.widget.FloatingActionButton;
          import android.support.design.widget.Snackbar;
          import android.support.v4.view.GravityCompat;
          import android.support.v7.widget.LinearLayoutManager;
          import android.support.v7.widget.PopupMenu;
          import android.support.v7.widget.RecyclerView;
          import android.support.v7.widget.Toolbar;
          import android.view.LayoutInflater;
          import android.view.MenuItem;
          import android.view.View;
          import android.view.ViewGroup;
          import android.widget.Toast;
      
          import java.util.ArrayList;
          import java.util.List;
      
          import me.yokeyword.fragmentation.anim.DefaultHorizontalAnimator;
          import me.yokeyword.fragmentation.anim.DefaultNoAnimator;
          import me.yokeyword.fragmentation.anim.DefaultVerticalAnimator;
          import me.yokeyword.fragmentation.anim.FragmentAnimator;
          import me.yokeyword.sample.R;
          import me.yokeyword.sample.demo_flow.adapter.HomeAdapter;
          import me.yokeyword.sample.demo_flow.listener.OnItemClickListener;
          import me.yokeyword.sample.demo_flow.entity.Article;
          import me.yokeyword.sample.demo_flow.base.BaseMainFragment;
      
      
          public class HomeFragment extends BaseMainFragment implements Toolbar.OnMenuItemClickListener {
              private static final String TAG = "Fragmentation";
      
              private String[] mTitles = new String[]{
                      "航拍“摩托大軍”返鄉高峰 如螞蟻搬家(組圖)",
                      "蘋果因漏電召回部分電源插頭",
                      "IS宣稱對敘利亞爆炸案負責"
              };
      
              private String[] mContents = new String[]{
                      "1月30日,距離春節還有不到十天,“摩托大軍”返鄉高峰到來。航拍廣西梧州市東出口服務站附近的騎行返鄉人員,如同螞蟻搬家一般。",
                      "昨天記者瞭解到,蘋果公司在其官網發出交流電源插頭轉換器更換計劃,召回部分可能存在漏電風險的電源插頭。",
                      "極端組織“伊斯蘭國”31日在社交媒體上宣稱,該組織製造了當天在敘利亞首都大馬士革發生的連環爆炸案。"
              };
      
              private Toolbar mToolbar;
      
              private FloatingActionButton mFab;
              private RecyclerView mRecy;
              private HomeAdapter mAdapter;
      
              public static HomeFragment newInstance() {
                  return new HomeFragment();
              }
      
              @Override
              public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                       Bundle savedInstanceState) {
                  View view = inflater.inflate(R.layout.fragment_home, container, false);
      
                  initView(view);
      
                  return view;
              }
      
              @Override
              protected FragmentAnimator onCreateFragmentAnimator() {
                  // 預設不改變
          //         return super.onCreateFragmentAnimation();
                  // 在進入和離開時 設定無動畫
                  return new DefaultNoAnimator();
              }
      
              private void initView(View view) {
                  mToolbar = (Toolbar) view.findViewById(R.id.toolbar);
                  mFab = (FloatingActionButton) view.findViewById(R.id.fab);
                  mRecy = (RecyclerView) view.findViewById(R.id.recy);
      
                  mToolbar.setTitle(R.string.home);
                  initToolbarNav(mToolbar, true);
                  mToolbar.inflateMenu(R.menu.home);
                  mToolbar.setOnMenuItemClickListener(this);
      
                  //_mActivity是SupportFragment中成員變數,在onAttach方法中初始化
                  mAdapter = new HomeAdapter(_mActivity);
      
                  //設定RecyclerView分界線和介面卡
                  LinearLayoutManager manager = new LinearLayoutManager(_mActivity);
                  mRecy.setLayoutManager(manager);
                  mRecy.setAdapter(mAdapter);
      
                  //設定RecyclerView上滑時FAB隱藏,下滑顯示
                  mRecy.addOnScrollListener(new RecyclerView.OnScrollListener() {
                      @Override
                      public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                          super.onScrolled(recyclerView, dx, dy);
                          if (dy > 5) {
                              mFab.hide();
                          } else if (dy < -5) {
                              mFab.show();
                          }
                      }
                  });
      
                  //設定RecyclerView的item點選事件,啟動DetailFragment
                  mAdapter.setOnItemClickListener(new OnItemClickListener() {
                      @Override
                      public void onItemClick(int position, View view) {
                          start(DetailFragment.newInstance(mAdapter.getItem(position).getTitle()));
                      }
                  });
      
                  // Init Datas
                  List<Article> articleList = new ArrayList<>();
                  for (int i = 0; i < 15; i++) {
                      int index = (int) (Math.random() * 3);
                      Article article = new Article(mTitles[index], mContents[index]);
                      articleList.add(article);
                  }
                  mAdapter.setDatas(articleList);
      
                  mFab.setOnClickListener(new View.OnClickListener() {
                      @Override
                      public void onClick(View view) {
                          Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                                  .setAction("Action", null).show();
                      }
                  });
              }
      
              /**
               * 類似於 Activity的 onNewIntent()
               */
              @Override
              protected void onNewBundle(Bundle args) {
                  super.onNewBundle(args);
      
                  Toast.makeText(_mActivity, args.getString("from"), Toast.LENGTH_SHORT).show();
              }
      
              //設定點選動畫彈出popMenu,設定動畫模式
              @Override
              public boolean onMenuItemClick(MenuItem item) {
                  switch (item.getItemId()) {
                      case R.id.action_anim:
                          final PopupMenu popupMenu = new PopupMenu(_mActivity, mToolbar, GravityCompat.END);
                          popupMenu.inflate(R.menu.home_pop);
                          popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                              @Override
                              public boolean onMenuItemClick(MenuItem item) {
                                  switch (item.getItemId()) {
                                      case R.id.action_anim_veritical:
                                          _mActivity.setFragmentAnimator(new DefaultVerticalAnimator());
                                          Toast.makeText(_mActivity, "設定全域性動畫成功! 豎向", Toast.LENGTH_SHORT).show();
                                          break;
                                      case R.id.action_anim_horizontal:
                                          _mActivity.setFragmentAnimator(new DefaultHorizontalAnimator());
                                          Toast.makeText(_mActivity, "設定全域性動畫成功! 橫向", Toast.LENGTH_SHORT).show();
                                          break;
                                      case R.id.action_anim_none:
                                          _mActivity.setFragmentAnimator(new DefaultNoAnimator());
                                          Toast.makeText(_mActivity, "設定全域性動畫成功! 無", Toast.LENGTH_SHORT).show();
                                          break;
                                  }
                                  popupMenu.dismiss();
                                  return true;
                              }
                          });
                          popupMenu.show();
                          break;
                  }
                  return true;
              }
      
              @Override
              public void onDestroyView() {
                  super.onDestroyView();
                  mRecy.setAdapter(null);
              }
          }
      

個人公眾號:每日推薦一篇技術部落格,堅持每日進步一丟丟…歡迎關注,想建個微信群,主要討論安卓和Java語言,一起打基礎、用框架、學設計模式,菜雞變菜鳥,菜鳥再起飛,願意一起努力的話可以公眾號留言,謝謝…