去年逛 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
当Activity第一次进来加载正常,onDestory以后再次进来则加载失败。
就是 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 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;
}
}
上面只是我的一个日常总结,如果有描述不正确的地方,请指教!如果这篇文章也能帮助到你,这也是我的幸运!十一点半了,天道酬勤这句鸡汤我先干了!