Fragment
已經成爲Android
開發界面設計中不可或缺的一部分,同時也發揮着愈來愈重要的角色,雖然Fragment
已經能出色的項目開發,可是在使用過程當中也暴露了愈來愈多的問題,雖然google
也一直在及時的修復,可是仍是有不少坑,因此決定記錄Fragment
使用過程當中的使用問題,避免小夥伴們重複踩坑。java
在瞭解踩坑以前,咱們須要先了解Fragment
的使用要點和使用方法android
Fragment
介紹做爲 view
界面的一部分,Fragment
的存在必須依附於 FragmentActivit
使用,而且與 FragmentActivit
同樣,擁有本身的獨立的生命週期,同時處理用戶的交互動做。同一個 FragmentActivit
能夠有一個或多個 Fragment
做爲界面內容,一樣Fragment
也能夠擁有多個子Fragment
,而且能夠動態添加、刪除 Fragment
,讓UI
的重複利用率和易修改性得以提高,一樣能夠用來解決部分屏幕適配問題。git
另外一方面,support v4 包中也提供了 Fragment,兼容 Android 3.0 以前的系統,使用兼容包須要注意兩點:github
宿主Activity
必須繼承自 FragmentActivity
;網絡
使用getSupportFragmentManager()
方法獲取 FragmentManager
對象;異步
Fragment
一樣是具有了獨立的生命週期,可是和Activity
的生命週期還有不同的地方,如圖:原圖地址ide
Fragment
初始化Fragment
默認有兩種初始化的方法,一種new
另外一種是嵌入xml
動畫
new
this
FirstFragment firstFragment=new FirstFragment();複製代碼
xml
google
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.wzgiceman.fragmentpit.Fragment.FirstFragment"/>複製代碼
上面兩種方法均可以初始獲得一個Fragment
對象,可是前者比後者的有點在於,前者更加的靈活,因此推薦使用第一種方式。
Activity
和Fragment
傳參默認建立Fragment
系統已經給咱們初始了傳參的代碼
/** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment FirstFragment. */
// TODO: Rename and change types and number of parameters
public static FirstFragment newInstance(String param1, String param2) {
FirstFragment fragment = new FirstFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}複製代碼
這無疑是最好的選擇
Fragment
類提供有startActivityForResult()
方法用於 Activity
間的頁面跳轉和數據回傳,其實內部也是調用 Activity
的對應方法。可是在頁面返回時須要注意 Fragment 沒有提供 setResult()
方法,能夠經過宿主 Activity
實現。
FragmentManager
和FragmentTransaction
使用FragmentManager
在Activity
中使用Fragment
可使用getSupportFragmentManager
獲取一個FragmentManager
對象,可是在Fragment
中顯示子Fragment
須要調用Fragment
的getChildFragmentManager()
源碼以下:
public final FragmentManager getChildFragmentManager() {
throw new RuntimeException("Stub!");
}複製代碼
FragmentTransaction
Fragment
的動態添加、刪除等操做都須要藉助於 FragmentTransaction
類來完成,好比上面提到的 commit()
操做,下面是幾種經常使用的方法:
add() 系列:添加 Fragment 到 Activity 界面中;
remove():移除 Activity 中的指定 Fragment;
replace() 系列:經過內部調用 remove() 和 add() 完成 Fragment 的修改;
hide() 和 show():隱藏和顯示 Activity 中的 Fragment;
addToBackStack():添加當前事務到回退棧中,即當按下返回鍵時,界面迴歸到當前事物狀態;
commit():提交事務,全部經過上述方法對 Fragment 的改動都必須經過調用 commit() 方法完成提交
replace()
和hide()
區別
replace()
和hide()
均可以動態的在Activity
中顯示多個Fragment
,而且能夠來回靈活的切換,可是它們有很大的區別,replace()
方法不會保留 Fragment
的狀態,也就是說諸如 EditText
內容輸入等用戶操做在 remove()
時會消失;可是hide()
卻不會,能完整的保留用戶的處理信息。
addToBackStack()
退棧
當用戶按下返回鍵時,若是回退棧中保存有以前的事務,會先執行事務回退,而後再執行Activity
的finish()
方法 。
經過FragmentManager
和FragmentTransaction
結合使用,咱們能夠將第一種初始化的Fragment
動態的顯示到界面中,這裏使用replace()
演示:
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fl_fragment, FirstFragment.newInstance("A","B"));
ft.commit();複製代碼
在瞭解了Fragment
的基礎使用後,能夠開始使用過程當中的踩坑了
getActivity()
引用問題在Fragment
中經常須要使用到content
對象,好比網絡加載如今一個progress
等等,這時候可能你遇到過getActivity()
返回null
,或者平時運行無缺的代碼,在「內存重啓」以後,調用getActivity()的地方卻返回null
,報了空指針異常。
大多數狀況下的緣由:你在調用了getActivity()
時,當前的Fragment已經onDetach()
了宿主Activity
。
好比:你在pop
了Fragment
以後,該Fragment
的異步任務仍然在執行,而且在執行完成後調用了getActivity()
方法,這樣就會空指針;
用getContext()
替代getActivity()
定義全局變量,在Fragment
的onAttach(Activity activity)準備廢棄
或者onAttach(Context context)
方法中初始化
Context context;
@Override
public void onAttach(Context context) {
super.onAttach(context);
this.context=context;
}複製代碼
顯然第一種方法更加靈活方便了。
當子Fragment
須要調用宿主Acitivity
的方法時,好比子Fragment
須要發送一個廣播,可是Fragment
沒有改方法,因此須要藉助宿主Activity
去發送,這時候經常須要強制轉換content
對象,而後調用宿主Acitivity
發方發送廣播,這種直接使用的方式違背了高聚低耦的設計原則;
經過接口抽象的方法,經過接口去調用宿主Activity的方法。
/** * 發送廣播 * Created by WZG on 2016/12/31. */
public interface SendBListener {
void send();
}複製代碼
public class FirstFragment extends Fragment {
SendBListener listener;
public void setListener(SendBListener listener) {
this.listener = listener;
}
@OnClick(value = R.id.tv)
void onTvClick(View view) {
listener.send();
}
}複製代碼
public class MainActivity extends AppCompatActivity implements SendBListener{
@BindView(R.id.fl_fragment)
FrameLayout mFlFragment;
@Override
public void send() {
sendBroadcast(new Intent("xxxxxx"));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FirstFragment firstFragment=new FirstFragment();
firstFragment.setListener(this);
}
}複製代碼
因爲採用建立對象的方式去初始化Fragment
對象,當宿主Activity
在界面銷燬或者界面從新執行onCreate()
方法時,就有可能再一次的執行Fragment
的建立初始,而以前已經存在的 Fragment 實例也會銷燬再次建立,這不就與 Activity 中 onCreate() 方法裏面第二次建立的 Fragment 同時顯示從而發生 UI 重疊的問題。
若是宿主界面Acitivity
能夠橫豎屏切換,致使的生命週期從新刷新也同理可致使界面的重疊問題。
savedInstanceState
判斷@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
FirstFragment firstFragment;
if (savedInstanceState==null) {
firstFragment=new FirstFragment();
ft.add(R.id.fl_fragment, firstFragment, "FirstFragment");
}else {
firstFragment = (FirstFragment) fm.findFragmentByTag("FirstFragment");
}
}複製代碼
Activity
提供的 onAttachFragment()
方法中處理@Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment instanceof FirstFragment){
firstFragment = (FirstFragment) fragment;
}
}複製代碼
Fragment
時判斷Fragment fragment = getSupportFragmentManager().findFragmentByTag("FirstFragment");
if (fragment==null) {
firstFragment =new FirstFragment();
ft.add(R.id.fl_fragment, firstFragment, "FirstFragment");
}else {
firstFragment = (FirstFragment) fragment;
}複製代碼
Fragment
轉場動畫若是你想給下一個Fragment設置進棧動畫和出棧動畫,setCustomAnimations(enter, exit)
只能設置進棧動畫,第二個參數並非設置出棧動畫;
請使用setCustomAnimations(enter, exit, popEnter, popExit)
,這個方法的第1個參數對應進棧動畫,第4個參數對應出棧動畫,因此是setCustomAnimations(進, exit, popEnter, 出))
Fragment
狀態監聽不少時候,咱們須要在多Fragment
中刷新界面,固然因爲Fragment
有本身獨立的生命週期可是也依賴宿主Activity
存在,因此在刷新界面的時候須要注意如:
當宿主Activity
A
進入B
中,又衝B
返回到A
,這時候宿主A
執行onResume()
方法,固然這時候的Fragment
也會執行onResume()
當宿主Activity
A
中的Fragment
所有初始完成顯示過,在切換Fragment
的時候不會再一次觸發onResume()
方法,可是卻能夠觸發Fragment的onHiddenChanged(boolean hidden)
方法
因此當咱們須要實時刷新Fragment
界面的時候,須要同時結合onResume()
和onHiddenChanged(boolean hidden)
方法去刷新當前顯示Fragment
而避免刷新hide()
的Fragment
使用
Override public void onResume() {
super.onResume();
//當前是不是現實狀態
if (isVisible()){
//刷新界面
updateUI();
}
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
//方法重複發起刷新界面
if (isVisible() && isResumed()){
updateUI();
}
}複製代碼