Android框架之路——Fragmentation的使用(流式互動Demo)
簡介:
YoKey大神的Fragment庫Fragmentation,主要用於現在App經常需要實現的單Activity+多Fragment以及多Activity+多Fragment的形式架構。同時最最重要的是,它幫助我們封裝了很多好用的方法,解決了一些官方Fragment庫中存在的一些Bug。
我在學習做一款有關Ble藍芽防丟器的App時想要嘗試以單Activity+多Fragment的架構去實現,恰好可以使用這個庫,也就皮毛的研究了一下。之前我是通過ViewPager去管理多個Fragment的進出,後來還是拋棄這種方式,因為確實不太合理。所以,用了Fragmentation這個庫,還是非常不錯的。
大神的話:
使用教程:
新增依賴
compile 'me.yokeyword:fragmentation:最新版'
我的是
compile 'me.yokeyword:fragmentation:0.10.3'
- 單Activity需要繼承自SupportActivity,多Fragment都繼承自SupportFragment。可以在AndroidStudio中使用Ctrl+H快捷鍵檢視類的繼承結構,如下所示,SupportActivity繼承自AppCompatActivity,爺爺正好是FragmentActivity,SupportFragment繼承自V4包下的Fragment,所以基本不影響我們使用:
一些API的使用(對照流式互動Demo)
- 先看一下demo的整體效果
這裡先貼出單Activity的程式碼,程式碼內我添加了一些註釋,基本可以瞭解清楚MainActivity 中做了哪些事情。這裡可以列出比較重要的Api:
裝載根Fragment,一般在saveInstanceState==null時load;
loadRootFragment(R.id.fl_container, HomeFragment.newInstance()); //activity初始載入HomeFragment
在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()); } });
- 在Activity中重寫onCreateFragmentAnimator()方法來設定所有Fragment的出場消失動畫,如果在某個單獨的Fragment中複寫該方法,則只單獨對該Fragment有效,這裡可以詳細的看一下wiki:使用場景- 轉場動畫;
- 通過重寫父類SupportActivity的onBackPressedSupport()方法,可以輕鬆的實現按下手機返回鍵要實現的功能,在下面的demo原始碼中給出了詳細的註釋,這裡也可以詳細的看一下wiki:使用場景- Back鍵的事件傳遞機制;
啟動跳轉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)
出棧,移出某個Fragment
// 當前Fragment出棧(在當前Fragment所在棧內pop) pop(); // 出棧某一個Fragment棧內之上的所有Fragment popTo(Class fragmentClass/String tag, boolean includeSelf); // 出棧某一個Fragment棧內之上的所有Fragment。如果想出棧後,緊接著.beginTransaction()開始一個新事務, //請使用下面的方法, 防止多事務連續執行的異常 popTo(Class fragmentClass, boolean includeSelf, Runnable afterTransaction);
查詢獲取某一Fragment
// 獲取所在棧內的棧頂Fragment getTopFragment(); // 獲取當前Fragment所在棧內的前一個Fragment getPreFragment(); // 獲取所在棧內的某個Fragment,可以是xxxFragment.Class,也可以是tag findFragment(Class fragmentClass/String tag);
防止動畫卡頓,可以先讓其載入完Fragment的轉場動畫,然後繼續實現一些繁瑣的業務邏輯。在Fragment中重寫onEnterAnimationEnd(Bundle saveInstanceState)方法,在這個方法裡繼續執行一些繁瑣操作。通常可以採取這樣的一種模式:
public View onCreateView(...) { ... // 這裡僅給一些findViewById等輕量UI的操作 initView(); return view; } @Override protected void onEnterAnimationEnd(Bundle saveInstanceState) { // 這裡設定Listener、各種Adapter、請求資料等等 initLazyView(); }
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); } }
以某種啟動模式啟動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); } }
- 先看一下demo的整體效果