《一个Android工程的从零开始》-7、base(六) BaseFragment的运用

来源:互联网 发布:布鲁特斯的心脏 知乎 编辑:程序博客网 时间:2024/06/07 00:29

先扯两句

说实话,昨天的部分BaseFragment就应该已经结束掉了,不过没有具体的应用掩饰,所以今天就再写一篇BaseFragment的基本应用,不过命名为BaseFragment的运用,但其实都是在说的Fragment的一些基本知识点以及BaseActivity中添加的封装方法,用于添加Fragment。
还是先上我的git库,然后开始正文。
MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)

正文

前面已经说过了,这部分主要是对Activity如何调用Fragment进行的封装,不过由于还没有具体实现某一个APP的功能,所以这里只是集成一下最基本的Fragment调用,还没有涉及到ViewPager的部分,虽然也不难,奈何我太懒啊。

Fragment启动方式

在需要创建 Fragment的位置,一般我们都先用FrameLayout占位,随后再通过对应的FragmentTransaction的方法将Fragment放置到FrameLayout中,而使用的方法分为以下两类。

1、add、show、hide

使用方法

看到这个标题不要误会,这并不是说有在1中有三种启动方法,这三个方法放在一起完全是因为他们是个组合,每一个的功能也大家完全可以从字面理解,add(添加)、show(展示)、hide(隐藏)。
首先,我们需要先添加要显示的Fragment,然后需要其显示的时候,调用show方法显示我们要呈现的Fragment(调用show方法之前显示的是你最后add的Fragment),而hide则是将对应的Fragment隐藏起来。
当然,可能会有人问,既然调用show方法则会显示对应的Fragment,那么其他的自然也处于hide的状态,为什么还要去调用这个方法,岂不是多此一举吗?
不过Google怎么可能犯这么傻的错误,既然是设置了这个方法,那么肯定是有用处的,至于什么用呢,那就是当我们使用Fragment的时候,偶尔会有需要我们监听当前Fragment是否显示的操作,而当我们使用了show的时候,是不调用onCreate或者是onResume方法的,所以这个时候我们很难使用Fragment生命周期相关的方法去判断其显隐性,但是当执行show或hide方法时候,却会执行Fragment中的onHiddenChanged方法,其参数是一个boolean值——boolean hidden,所以我们通过调用这个方法,判断hidden的true/false就可以完成显隐性对应的操作,下面贴出来我这个方法的代码:

    @Override    public void onHiddenChanged(boolean hidden) {        super.onHiddenChanged(hidden);        if (hidden) {            showToast("隐藏了");        } else {            showToast("显示了");        }    }

大家再看看效果:

这里写图片描述

咳咳,关于布局,大家就忍一下啊,等后面完成具体功能的时候,我好好设计一下,这里就偷个懒。不过大家应该可以看到,随着Fragment的隐藏/显示,方法中的Toast确实也随着执行了。
当然,我们在工程中使用的时候,show方法就不用想了,肯定只能有一个,谁也不会设计成同一个Fragment的地方,同一个时间显示两个Fragment吧,当然,如果谁遇到这么一个设计,我只能默默为你节哀(同一个Activity中是可以存在多个Fragment的,这个不再考虑范围内,我的意思是一个FrameLayout占位的地方不会同时显示两个Fragment)。
但是add与hide就不一定一次处理几个了,add很简单能理解,例如首页的布局中,就会运用到Fragment,毕竟都是常用界面,我们在初始化的时候自然都要添加完,调用的时候显示就可以,所以很显然,我们需要同时操作多个(谁如果一定要用的时候再添加也不是不可以,只是作为一个懒人,那么做还得各种判断状态,多麻烦)
而hide,一般情况下,肯定是将show的hide掉就可以了,但是为什么我也将其算到不一定处理几个了呢,只存在一种情况,那就是前面说的add的时候,我们为了能够在onHiddenChanged方法中监听各个Fragment的显隐性,所以那些没有显示的就需要在创建之后hide掉。
所以这部分在BaseActivity中集成的方法如下:

    /**     * 添加Fragment     *     * @param containerViewId 对应布局的id     * @param fragments       所要添加的Fragment,可以添加多个     */    public void addFragment(int containerViewId, Fragment... fragments) {        if (fragments != null) {            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();            for (int i = 0; i < fragments.length; i++) {                transaction.add(containerViewId, fragments[i]);            }            transaction.commitAllowingStateLoss();        }    }    /**     * 显示Fragment     *     * @param fragment 所要显示的Fragment     */    public void showFragment(Fragment fragment) {        if (fragment != null) {            getSupportFragmentManager().beginTransaction().show(fragment).commitAllowingStateLoss();        }    }    /**     * 隐藏Fragment     *     * @param fragments 所要隐藏的Fragment,可以添加多个     */    public void hideFragment(Fragment... fragments) {        if (fragments != null) {            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();            for (int i = 0; i < fragments.length; i++) {                transaction.hide(fragments[i]);            }            transaction.commitAllowingStateLoss();        }    }

其中Fragment… fragments是可变长参数,可以传入大于等于0个同类型参数,这里的意思就是我们可以不传,或者传入任意个Fragment,且生成的是一个字符串。
不过,作为一个懒人嘛,既然前面已经说到了,hide只有在add刚刚添加完之后才可能会多次调用hide,那么我们就直接将这部分hide集成到刚刚add地方岂不是更省事,也就是除了最后一个添加的之外,其他的都设置成hide,修改后的add/hide方法如下:

    /**     * 添加Fragment     *     * @param containerViewId 对应布局的id     * @param fragments       所要添加的Fragment,可以添加多个     */    public void addFragment(int containerViewId, Fragment... fragments) {        if (fragments != null) {            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();            for (int i = 0; i < fragments.length; i++) {                transaction.add(containerViewId, fragments[i]);                if (i != fragments.length - 1){                    transaction.hide(fragments[i]);                }            }            transaction.commitAllowingStateLoss();        }    }    /**     * 隐藏Fragment     *     * @param fragment 所要隐藏的Fragment,可以添加多个     */    public void hideFragment(Fragment fragment) {        if (fragment != null) {            getSupportFragmentManager().beginTransaction().hide(fragment).commitAllowingStateLoss();        }    }

这样在使用的时候直接调用即可,也可以避免“…”的形式如果忘记传参也不会报错的情况,降低出错的可能性。

存在问题

正因为我们是将所需要的一次性都加载完成,所以开发出的APP,在后台运行的过程中,一旦某个Fragment被回收了,那么再次运行的时候,都会出现空指针异常。目前采取的方式一般都是后台运行一段时间后,再次打开则重启APP,例如:高德地图;而没有处理的一般都会卡死,用户只能强制关闭APP,再重新打开,例如:赶集网(或许过段时间再看的时候,也做了处理,但是至少现在还是有卡死的问题的,虽然不确定一定是Fragment引起的,但大家可以参考一下出问题的效果)

2、replace

这个就没有之前那么复杂,还需要三个方法才能完成对应的功能,而是只有一个方法就可以完成,那就是replace。当然,其作用也很好理解,就是替换。而其效果看上去与上面的方法相同,但是实际上却是不同的。
使用add、show、hide的时候,我们是在FrameLayout中生成了多层的Fragment,然后将我们要显示的拿到最上面,其他的都在下面无论隐藏或者不隐藏,都是存在的。
而replace则不同它相当于执行了remove之后,再去执行add方法,也就是先将上一个Fragment删除掉之后,再重新添加一个Fragment方法,所以无论调用多少次replace,都相当于我们只是在FrameLayout中放了一层Fragment,这种情况下,再出现上述的丢失Fragment导致空指针的情况,虽然还是无法避免,但概率绝对会减少许多。
因为每次都是创建的新的Fragment,所以我们就不需要监听onHiddenChanged方法了,我们只需要监听对应的生命周期就可以完成创建或销毁的操作。
代码如下:

    /**     * 替换Fragment     *     * @param containerViewId 对应布局的id     * @param fragment        用于替代原有Fragment的心Fragment     */    public void replaceFragment(int containerViewId, Fragment fragment) {        if (fragment != null) {            getSupportFragmentManager().beginTransaction().replace(containerViewId, fragment).commitAllowingStateLoss();        }    }
存在问题

正因为它是先remove了上一个Fragment,再重新add新的Fragment,所以每次调用的时候都需要重新加载整个Fragment,因此相对而言会更耗费资源,如果不常使用的Fragment,没有问题,如果是ViewPager或者首页这种会经常访问的,建议不要使用这个方法。

至于为什么这里我使用的是commitAllowingStateLoss而不是commit,大家可以参考一下丿北纬91度灬的源码分析commitAllowingStateLoss() 和commit()的区别

BaseFragment的调用

好了,在前面的内容都搞定了以后,我们终于到了本篇博客的起因,也是本篇博客最没有什么技术含量的部分了。
我们就将上面Gif图对应的代码贴出来就可以了,大家看一下:

package com.banshouweng.mybaseapplication.ui.fragment;import android.os.Bundle;import android.support.annotation.Nullable;import android.view.View;import com.banshouweng.mybaseapplication.R;import com.banshouweng.mybaseapplication.base.BaseFragment;import com.banshouweng.mybaseapplication.ui.activity.TestActivity;/** * 《一个Android工程的从零开始》 * * @author 半寿翁 * @博客: * @CSDN http://blog.csdn.net/u010513377/article/details/74455960 * @简书 http://www.jianshu.com/p/1410051701fe */public class MineFragment extends BaseFragment {    @Override    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {        super.onViewCreated(view, savedInstanceState);        setBaseContentView(R.layout.fragment_mine);        //设置title文本        setTitle("MineFragment");        //设置返回拦截        setBaseBack(new OnClickBackCallBack() {            @Override            public void clickBack() {                startActivity(TestActivity.class);            }        });        //设置功能键,以及点击方法回调监听        setBaseRightIcon1(R.mipmap.add, "更多", new OnClickRightIcon1CallBack() {            @Override            public void clickRightIcon1() {                showLoadDialog();            }        });        setBaseRightIcon2(R.mipmap.more, "更多", new OnClickRightIcon2CallBack() {            @Override            public void clickRightIcon2() {                hideLoadDialog();            }        });    }    @Override    public void onHiddenChanged(boolean hidden) {        super.onHiddenChanged(hidden);        if (hidden) {            showToast("隐藏了");        } else {            showToast("显示了");        }    }}

很遗憾的是,这个里面有一个方法没有用,那就是hideLoadDialog(),主要原因还是在CustomProgressDialog中设置了setCanceledOnTouchOutside(false)(dialog弹出后点击物理返回键Dialog消失,但是点击屏幕不会消失)这个属性。不过其他效果都还是可以实现的。也就是按钮的显示,其中返回键返回键拦截跳转的是TestActivity,而MainActivity中的拦截跳转的是NetWorkActivity。为了与上面MainActivity的Title做区分,右侧的图片位置也做了交换,下面我们来看一下效果。

这里写图片描述

附录

《一个Android工程的从零开始》- 目录

阅读全文
0 0
原创粉丝点击