最新消息:文章中包含代码时,请遵守代码高亮规范!

Android基类封装之Fragment切换辅助类【原创】

Android 李, 泰愚 174浏览 0评论

在现在的Android开发工作中,我们都会用到Fragment来作为界面显示。关于Fragment生命周期等就不再赘述,这里记录一次在慕课上学习中所学到的一个处理多个Fragment切换的封装。

现在我们经常会用到menu配合BottomNavigationView来作为Activity中Fragment对应的tab,所以这里将辅助类命名为NavHelper,先看辅助类中所定义的变量:

//所有的Tab集合
private final SparseArray<Tab<T>> tabs = new SparseArray();
//用于初始化的必须参数
private final Context context;
private final int containerId;
private final FragmentManager fragmentManager;
private OnTabChangedListener<T> listener;
//当前的一个选中的Tab
private Tab<T> currentTab;

其中FragmentManager不用多说,containerId是布局文件的id,currentTab是储存的当前显示的Fragment,用SparseArray装我们定义的内部类Tab,而Tab则是用来缓存Fragment的,代码如下:

/**
 * 我们的所有的Tab基础属性
 *
 * @param <T> 泛型的额外参数
 */
public static class Tab<T> {

    public Tab(Class<?> clx, T extra) {
        this.clx = clx;
        this.extra = extra;
    }

    //Fragment对应的Class信息
    public Class<?> clx;
    //额外的字段,用户自己设定需要使用
    public T extra;
    //内部缓存的对应的Fragment,Package权限,外部无法使用
    Fragment fragment;
}

可以看到Tab的构造方法中传入Class,定义的泛型T则是可能需要用到的额外字段,Fragment用来缓存需要切换的具体Fragment。

OnTabChangedListener则是处理事件的回调,需要用到的辅助类的Activity实现此接口,做具体的操作,代码如下:

/**
 * 定义事件处理完成后的回调接口
 *
 * @param <T>
 */
public interface OnTabChangedListener<T> {
    void onTabChanged(Tab<T> newTab, Tab<T> oldTab);
}

此辅助类的构造方法,没有太多东西,初始化一些变量而已:

public NavHelper(Context context, int containerId, FragmentManager fragmentManager, OnTabChangedListener<T> listener) {
    this.context = context;
    this.containerId = containerId;
    this.fragmentManager = fragmentManager;
    this.listener = listener;
}

接下来是定义的一个add方法,在Activity中使用时,调用此方法,将依附与Activity上的Fragment都添加进来,返回值是NavHelper自己,方便多次添加时,可以.add(…).add(…):

/**
 * 添加Tab
 *
 * @param menuId Tab对应的菜单Id
 * @param tab    Tab
 */
public NavHelper<T> add(int menuId, Tab<T> tab) {
    tabs.put(menuId, tab);
    return this;
}

还有一个getCurrentTab方法,顾名思义,拿到当前的Tab:

/**
 * 获取当前的显示的Tab
 *
 * @return 当前Tab
 */
public Tab<T> getCurrentTab() {
    return currentTab;
}

然后是performClickMenu方法,在Activity中当我们点击了Menu时调用此方法:

/**
 * 执行点击菜单的操作
 *
 * @param menuId 菜单的Id
 * @return 是否能够处理这个点击
 */
public boolean performClickMenu(int menuId) {
    //集合中寻找点击的菜单对应的Tab,如有则进行处理
    Tab<T> tab = tabs.get(menuId);
    if (tab != null) {
        doSelect(tab);
        return true;
    }
    return false;
}

那么这其中的doSelect方法则是真实的Tab选择时的操作:

/**
 * 进行真实的Tab选择操作
 *
 * @param tab
 */
private void doSelect(Tab<T> tab) {
    Tab<T> oldTab = null;
    if (currentTab != null) {
        oldTab = currentTab;
        if (oldTab == tab) {
            //如果说当前的Tab就是点击的Tab,则我们不做处理
            notifyTabReselect(tab);
            return;
        }
    }
    //赋值并调用切换方法
    currentTab = tab;
    doTabChanged(currentTab, oldTab);
}

其中notifyTabReselect方法则是在点击了当前的tab时调用,相当于二次点击,我们目前实现为空,不做特殊处理,因为此时并没有Fragment的切换发生:

private void notifyTabReselect(Tab<T> tab) {
    //TODO 二次点击Tab所做的操作
}

而doTabChanged方法则是进行Fragment真实的调度操作,代码如下:

/**
 * 进行Fragment的真实的调度操作
 * @param newTab 新的
 * @param oldTab 旧的
 */
private void doTabChanged(Tab<T> newTab, Tab<T> oldTab) {
    FragmentTransaction ft = fragmentManager.beginTransaction();
    if (oldTab != null) {
        if (oldTab.fragment != null) {
            //从界面移出,但是还在Fragment的缓存空间中
            ft.detach(oldTab.fragment);
        }
    }

    if (newTab != null) {
        if (newTab.fragment == null) {
            //首次新建
            Fragment fragment = Fragment.instantiate(context, newTab.clx.getName(), null);
            //缓存起来
            newTab.fragment = fragment;
            //提交到FragmentManger
            ft.add(containerId, fragment, newTab.clx.getName());
        } else {
            //从FragmentManger的缓存空间中重新加载到界面中
            ft.attach(newTab.fragment);
        }
    }
    //提交事务
    ft.commit();
    //通知回调
    notifyTabselect(newTab, oldTab);
}[/cdoe]</pre>
这里用到FragmentTransaction进行调度,方法参数传进来新Tab和老Tab(即当前的),先将老Tab中的Fragment移出,但此时还在Tab中有缓存,然后判断新Tab是否为空,为空则创建且缓存到Tab中,不为空则使用FragmentTransaction变量直接加载进界面中,然后调用notifyTabselect方法,通知回调:
<pre>/**
 * 回调我们的监听器
 * @param newTab 新的Tab
 * @param oldTab 旧的Tab
 */
private void notifyTabselect(Tab<T> newTab, Tab<T> oldTab) {
    if(listener != null) {
        listener.onTabChanged(newTab, oldTab);
    }
}

到这里,已经是代码的全部了,其中比较巧妙得用到了自定义一个内部类Tab进行Fragment的储存与缓存操作,且定义方法进行Tab的切换操作,更定义了回调接口供Activity实现。最后就是完整代码:

/**
 * 解决对Fragment的调度与重用问题,
 * 达到最优的Fragment切换
 * <p>
 * Created by Tiger on 2017/7/1.
 */

public class NavHelper<T> {
    //所有的Tab集合
    private final SparseArray<Tab<T>> tabs = new SparseArray();
    //用于初始化的必须参数
    private final Context context;
    private final int containerId;
    private final FragmentManager fragmentManager;
    private OnTabChangedListener<T> listener;
    //当前的一个选中的Tab
    private Tab<T> currentTab;

    public NavHelper(Context context, int containerId, FragmentManager fragmentManager, OnTabChangedListener<T> listener) {
        this.context = context;
        this.containerId = containerId;
        this.fragmentManager = fragmentManager;
        this.listener = listener;
    }

    /**
     * 添加Tab
     *
     * @param menuId Tab对应的菜单Id
     * @param tab    Tab
     */
    public NavHelper<T> add(int menuId, Tab<T> tab) {
        tabs.put(menuId, tab);
        return this;
    }

    /**
     * 获取当前的显示的Tab
     *
     * @return 当前Tab
     */
    public Tab<T> getCurrentTab() {
        return currentTab;
    }

    /**
     * 执行点击菜单的操作
     *
     * @param menuId 菜单的Id
     * @return 是否能够处理这个点击
     */
    public boolean performClickMenu(int menuId) {
        //集合中寻找点击的菜单对应的Tab,如有则进行处理
        Tab<T> tab = tabs.get(menuId);
        if (tab != null) {
            doSelect(tab);
            return true;
        }

        return false;
    }

    /**
     * 进行真实的Tab选择操作
     *
     * @param tab
     */
    private void doSelect(Tab<T> tab) {
        Tab<T> oldTab = null;
        if (currentTab != null) {
            oldTab = currentTab;
            if (oldTab == tab) {
                //如果说当前的Tab就是点击的Tab,则我们不做处理
                notifyTabReselect(tab);
                return;
            }
        }
        //赋值并调用切换方法
        currentTab = tab;
        doTabChanged(currentTab, oldTab);
    }

    /**
     * 进行Fragment的真实的调度操作
     * @param newTab 新的
     * @param oldTab 旧的
     */
    private void doTabChanged(Tab<T> newTab, Tab<T> oldTab) {
        FragmentTransaction ft = fragmentManager.beginTransaction();

        if (oldTab != null) {
            if (oldTab.fragment != null) {
                //从界面移出,但是还在Fragment的缓存空间中
                ft.detach(oldTab.fragment);
            }
        }

        if (newTab != null) {
            if (newTab.fragment == null) {
                //首次新建
                Fragment fragment = Fragment.instantiate(context, newTab.clx.getName(), null);
                //缓存起来
                newTab.fragment = fragment;
                //提交到FragmentManger
                ft.add(containerId, fragment, newTab.clx.getName());
            } else {
                //从FragmentManger的缓存空间中重新加载到界面中
                ft.attach(newTab.fragment);
            }
        }
        //提交事务
        ft.commit();
        //通知回调
        notifyTabselect(newTab, oldTab);
    }

    /**
     * 回调我们的监听器
     * @param newTab 新的Tab
     * @param oldTab 旧的Tab
     */
    private void notifyTabselect(Tab<T> newTab, Tab<T> oldTab) {
        if(listener != null) {
            listener.onTabChanged(newTab, oldTab);
        }
    }

    private void notifyTabReselect(Tab<T> tab) {
        //TODO 二次点击Tab所做的操作
    }

    /**
     * 我们的所有的Tab基础属性
     *
     * @param <T> 泛型的额外参数
     */
    public static class Tab<T> {

        public Tab(Class<?> clx, T extra) {
            this.clx = clx;
            this.extra = extra;
        }

        //Fragment对应的Class信息
        public Class<?> clx;
        //额外的字段,用户自己设定需要使用
        public T extra;
        //内部缓存的对应的Fragment,Package权限,外部无法使用
        Fragment fragment;
    }

    /**
     * 定义事件处理完成后的回调接口
     *
     * @param <T>
     */
    public interface OnTabChangedListener<T> {
        void onTabChanged(Tab<T> newTab, Tab<T> oldTab);
    }
}

转载时请注明出处及相应链接,本文永久地址:http://blog.it985.com/22389.html


pay_weixin
pay_weixin
微信打赏
pay_weixin
支付宝打赏
感谢您对作者rick的打赏,我们会更加努力!    如果您想成为作者,请点我

您必须 登录 才能发表评论!