去年逛 github 的時候,發現一個仿今日頭條的項目(鏈接),發現很多好玩的東西,比如顏色的漸變,Fragment 的管理,最後在使用 Fragment 管理工具類——主界面Fragment控制器的時候栽了一個大跟頭,具體情境是我的 Activity 在切換夜間模式的時候,Activity 銷燬以後重建了,但是之前的Fragment並沒有得到重建,導致頁面上同時存在兩個Fragment,而我的Fragment在頁面啓動的時候有一些業務,比如加載數據(進度條展示),這個時候進度條居然幹不掉,物理按鍵沒有響應,最後才知道是因爲 Fragment 在後臺,前臺的 Fragment 將返回事件消費掉了,當時的處理方法是在切換日/夜間模式的時候,預先將所有的 Fragment 從事物中刪除,總算是解決了這個頭痛的問題!代碼如下:
public void removeFragments() {
FragmentTransaction ft = fm.beginTransaction();
for (Fragment fragment : fragments) {
if (fragment != null) {
ft.remove(fragment);
}
}
ft.commitAllowingStateLoss();
}
直到今天才發現之前的處理方式是頭痛醫頭腳痛醫腳的治標不治本的臨時措施,不能算作最後的解決方案,比如最近遇到的兩個問題:
背景
最近做的是簡單的登錄頁,包括用戶密碼登錄、手機號碼和短信驗證碼登錄、註冊賬號、重置密碼幾個狀態,我得到需求後,腦子裏第一個反應就是使用 Fragment 來實現各個狀態之間的切換,最後通過上述工具類很輕鬆的實現了。
bug
- 主頁面二次加載Fragm失敗
當Activity第一次進來加載正常,onDestory以後再次進來則加載失敗。
- Fragment需要銷燬時,內容不能被清除
就是 Fragment 切換的時候,內容居然保留了,比如我註冊的時候輸入了用戶名,然後放棄註冊直接登錄,然後在登錄的時候忘記密碼選擇重新註冊一個賬號,這個時候加載的註冊 Fragment 之前填寫的內容居然還在,,,
第一個問題比較容易解決,就是在 Activity 銷燬的時候,調用工具類的 onDestroy 方法,將靜態的是否實例化 Fragment 給實例化的標誌位給置爲默認值,這樣再次進來則會執行默認的方法,代碼如下:
public static void onDestroy() {
isReload = false;
fragments = null;
controller = null;
}
//實例化的默認方法
private void initFragment() {
fragments = new ArrayList<>();
if (isReload) {
//頁面需要加載的Fragment
fragments.add(new FileGruopFragment());
fragments.add(new AboutFragment());
fragments.add(new EmailFragment());
// fragments.add(new AttentionFragment());
// fragments.add(new MeFragment());
FragmentTransaction ft = fm.beginTransaction();
for (int i = 0; i < fragments.size(); i++) {
ft.add(containerId, fragments.get(i), "" + i);
}
ft.commit();
//此處可以使用shardPrefresh保存數量,以便下面for循環的時候不用手動修改常量
} else {
for (int i = 0; i < 5; i++) {
fragments.add( fm.findFragmentByTag(i+""));
}
}
}
第二個問題其實首頁需要看情況確定是否保留,如果 Fragment 有一些在實例化開銷比較大的情況,則應該保留,如果需要保持時刻刷新的話,則需要跟上述業務相同的邏輯,Fragment 切換的時候清除內容。
後來仔細研究了一下代碼,其實在工具類的構造方法裏面將 Fragment 添加到了 FragmentTransaction 中,後續的 showFragment(int position) 方法則是將實例化隊列中的 Fragment 取出來進行展示,並在展示之前將 所有隊列裏面的 Fragment 調用 hide 方法進行關閉展示。
其實邏輯上很簡單,因爲 FragmentTransaction 裏面的 Fragment 和 Fragment 集合裏面的對象都是一一對應的,因此並沒有進行通過 new 來實現刷新,而我也沒有在 Fragment 的屬性裏面找到刷新頁面的方法,最後我新增加了一個變量,來保存當前顯示的 Fragment 下標值,而在 showFragment(int position) 的時候通過判斷當前的 Fragment 是否是第一次加載,真則直接加載下一個 Fragment ,假則將 Fragment 集合裏對應的 Fragment 進行替換,並將 FragmentTransaction 裏面的Fragment也一併進行替換,代碼如下:
private int lastPosition = -1;
public void showFragment(int position) {
hideFragments();
Fragment fragment = fragments.get(position);
FragmentTransaction ft = fm.beginTransaction();
//ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(position != -1){
ft.remove(fragment);
fragment = getFragment(position);
fragments.set(position,fragment);
ft.add(containerId, fragment, "" + fragment);
}
ft.show(fragment);
ft.commitAllowingStateLoss();
lastPosition = position;
}
這樣就解決上面的問題了,當然,最後可以對這裏進行一個簡單的封裝,通過一個簡單的標誌位來判斷是否需要緩存,比如我們在靜態的方法裏面新增一個 boolean 的字段,代碼如下:
package com.sasucen.ui.fragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import com.sasucen.ui.fragment.forget.ForgetFragment;
import com.sasucen.ui.fragment.mobilelogin.MobileLoginFragment;
import com.sasucen.ui.fragment.rigster.RigsterUserFragment;
import com.sasucen.ui.fragment.userlogin.UserLoginFragment;
import java.util.ArrayList;
/**
* Created by Vicent on 2018/3/23 0023.
* Fragment管理類
*/
public class FragmentController {
private int containerId;
private FragmentManager fm;
private ArrayList<Fragment> fragments;
private static FragmentController controller;
private static boolean isReload;
private int lastPosition = -1;
private static boolean isCache = false;
public static FragmentController getInstance(FragmentActivity activity, int containerId, boolean isReload,boolean isCache) {
FragmentController.isReload = isReload;
FragmentController.isCache = isCache;
if (controller == null) {
controller = new FragmentController(activity, containerId);
}
return controller;
}
public static void onDestroy() {
isReload = false;
fragments = null;
controller = null;
}
private FragmentController(FragmentActivity activity, int containerId) {
this.containerId = containerId;
fm = activity.getSupportFragmentManager();
initFragment();
}
private void initFragment() {
fragments = new ArrayList<>();
if (isReload) {
fragments.add(new UserLoginFragment());
fragments.add(new MobileLoginFragment());
fragments.add(new RigsterUserFragment());
fragments.add(new ForgetFragment());
FragmentTransaction ft = fm.beginTransaction();
for (int i = 0; i < fragments.size(); i++) {
ft.add(containerId, fragments.get(i), "" + i);
}
ft.commit();
} else {
for (int i = 0; i < 4; i++) {
fragments.add( fm.findFragmentByTag(i+""));
}
}
}
public void showFragment(int position) {
hideFragments();
Fragment fragment = fragments.get(position);
FragmentTransaction ft = fm.beginTransaction();
//ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(position != -1 && isCache){
ft.remove(fragment);
fragment = getFragment(position);
fragments.set(position,fragment);
ft.add(containerId, fragment, "" + fragment);
}
ft.show(fragment);
ft.commitAllowingStateLoss();
lastPosition = position;
}
public void hideFragments() {
FragmentTransaction ft = fm.beginTransaction();
for (Fragment fragment : fragments) {
if (fragment != null) {
ft.hide(fragment);
}
}
ft.commitAllowingStateLoss();
}
public Fragment getFragment(int position) {
Fragment fragment = null;
switch (position){
case 0:
fragment = new UserLoginFragment();
break;
case 1:
fragment = new MobileLoginFragment();
break;
case 2:
fragment = new RigsterUserFragment();
break;
case 3:
fragment = new ForgetFragment();
break;
}
return fragment;
}
}
上面只是我的一個日常總結,如果有描述不正確的地方,請指教!如果這篇文章也能幫助到你,這也是我的幸運!十一點半了,天道酬勤這句雞湯我先幹了!