fragment实现Tab切换效果

上篇播客从源码和注释说了下fragment的生命周期,这里还是接着说fragment并实现Tab切换效果,先看下运行的效果;

GIF.gif

实现这个效果其实很简单,通过getSupportFragmentManager()返回一个FragmentManager对象,调用FragmentManager中的beginTransaction()方法返回一个FragmentTransaction对象,调用FragmentTransaction中的add方法将fragment添加到FragmentActivity中,调用FragmentTransaction中的commit()方法就实现了;

    /**
     * 添加Fragment
     * @param fragment
     */
    public void add(Fragment fragment){
        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
        //添加fragment
        fragmentTransaction.add(mComtainViewId, fragment);
        fragmentTransaction.commit();
    }

在进行fragment切换的时候调用FragmentTransaction 中的replace()方法和commit()方法就可以了;

    /**
     * 替换Fragment
     * @param fragment
     */
    public void replace(Fragment fragment){
        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
        //添加fragment
        fragmentTransaction.replace(mComtainViewId, fragment);
        fragmentTransaction.commit();
    }

就这些代码就实现了,接下来看看我们在调用这些方法的时候,源码里面干了哪些事情;
我们在调用getSupportFragmentManager获取FragmentManager对象的时候,涉及到了FragmentController、FragmentManagerImpl、FragmentHostCallback这些类;
FragmentManagerImpl继承自FragmentManager;
一开始调用的是FragmentActivity这个方法,

final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
  /**
     * Return the FragmentManager for interacting with fragments associated
     * with this activity.
     */
    public FragmentManager getSupportFragmentManager() {
        return mFragments.getSupportFragmentManager();
    }

这里的mFragments就是FragmentController集合,接着又调用了FragmentController中的

  /**
     * Returns a {@link FragmentManager} for this controller.
     */
    public FragmentManager getSupportFragmentManager() {
        return mHost.getFragmentManagerImpl();
    }

这里的mHost就是FragmentHostCallback类,最后调用了FragmentHostCallback中的

FragmentManagerImpl getFragmentManagerImpl() {
        return mFragmentManager;
    }

返回一个FragmentManagerImpl对象;

FragmentManager在调用beginTransaction()方法应该是调用了子类FragmentManagerImpl中的beginTransaction()方法,返回一个BackStackRecord对象,而BackStackRecord是FragmentTransaction的子类,

  @Override
    public FragmentTransaction beginTransaction() {
        return new BackStackRecord(this);
    }

拿到FragmentTransaction对象后,调用add等方法时,一看源码FragmentTransaction中的这些方法都是抽象方法,都是在BackStackRecord子类中做了相应的动作;

    @Override
    public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
        doAddOp(containerViewId, fragment, tag, OP_ADD);
        return this;
    }

调用add最终是调用了doAddOp将fragment添加进来;

  private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
        final Class fragmentClass = fragment.getClass();
        final int modifiers = fragmentClass.getModifiers();
        if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
                || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
            throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
                    + " must be a public static class to be  properly recreated from"
                    + " instance state.");
        }

        fragment.mFragmentManager = mManager;

        if (tag != null) {
            if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
                throw new IllegalStateException("Can't change tag of fragment "
                        + fragment + ": was " + fragment.mTag
                        + " now " + tag);
            }
            fragment.mTag = tag;
        }

        if (containerViewId != 0) {
            if (containerViewId == View.NO_ID) {
                throw new IllegalArgumentException("Can't add fragment "
                        + fragment + " with tag " + tag + " to container view with no id");
            }
            if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
                throw new IllegalStateException("Can't change container ID of fragment "
                        + fragment + ": was " + fragment.mFragmentId
                        + " now " + containerViewId);
            }
            fragment.mContainerId = fragment.mFragmentId = containerViewId;
        }

        Op op = new Op();
        op.cmd = opcmd;
        op.fragment = fragment;
        addOp(op);
    }

调用commit()方法提交事务的时候,调用的是FragmentManager的scheduleCommit()方法,再通过FragmentHostCallBack中的getHandler返回一个Handler对象去执行,

    /**
     * Schedules the execution when one hasn't been scheduled already. This should happen
     * the first time {@link #enqueueAction(OpGenerator, boolean)} is called or when
     * a postponed transaction has been started with
     * {@link Fragment#startPostponedEnterTransition()}
     */
    private void scheduleCommit() {
        synchronized (this) {
            boolean postponeReady =
                    mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
            boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
            if (postponeReady || pendingReady) {
                mHost.getHandler().removeCallbacks(mExecCommit);
                mHost.getHandler().post(mExecCommit);
            }
        }
    }

这里的mHost是FragmentHostCallBack对象,在fragment切换的时候调用replace就可以了,其实还是调用了添加的方法来实现的,

    @Override
    public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
        if (containerViewId == 0) {
            throw new IllegalArgumentException("Must use non-zero containerViewId");
        }

        doAddOp(containerViewId, fragment, tag, OP_REPLACE);
        return this;
    }

细心下就会发现,调用replace方法后,再切换回来的时候,页面又重新加载一遍了,

QQ截图20170702220000.jpg

是不是感觉对于不想重新加载页面来说是不是太坑了,其实如果不想重新加载页面的话可以调用FragmentTransaction中的hide和show方法就可以了,

    /**
     * 切换显示Fragment
     * @param fragment
     */
    public void switchFragment(Fragment fragment){
        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
        //获取当前所有的fragment
        List<Fragment> childFragments = mFragmentManager.getFragments();
        //先隐藏所有的fragment
        for(Fragment childFragment:childFragments){
            fragmentTransaction.hide(childFragment);
        }
        //没有的话就添加,有就显示
        if(!childFragments.contains(fragment)){
            //添加
            fragmentTransaction.add(mComtainViewId,fragment);
        }else{
            fragmentTransaction.show(fragment);
        }
        fragmentTransaction.commit();
    }

想重新加载页面的话就调用replace,不想的话就调用hide和show就可以了,最后说下相关类的关系及包含什么东西;
FragmentManagerImpl是FragmentManager的子类,提供了beginTransaction、getFragment、getFragments、putFragment等方法,
BackStackRecord是FragmentTransaction的子类,提供了add、replace、hide、show、remove等方法,
FragmentController提供了getSupportFragmentManager等方法
FragmentHostCallback类,其实fragment中很多方法的调用都走到这里,就那getActivity来说,

    /**
     * Return the {@link FragmentActivity} this fragment is currently associated with.
     * May return {@code null} if the fragment is associated with a {@link Context}
     * instead.
     */
    final public FragmentActivity getActivity() {
        return mHost == null ? null : (FragmentActivity) mHost.getActivity();
    }

这里的mHost 其实就FragmentHostCallback,调的就是FragmentHostCallback中的getActivity返回一个activity对象,

 Activity getActivity() {
        return mActivity;
    }

getContext()、startActivity()、startActivityForResult()等都是这样调用的;
startActivity通过调用FragmentHostCallback中的onStartActivityFromFragment方法,再由Context调用startActivity来实现的;

fragment中startActivity

    /**
     * Call {@link Activity#startActivity(Intent, Bundle)} from the fragment's
     * containing Activity.
     */
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (mHost == null) {
            throw new IllegalStateException("Fragment " + this + " not attached to Activity");
        }
        mHost.onStartActivityFromFragment(this /*fragment*/, intent, -1, options);
    }

FragmentHostCallback中onStartActivityFromFragment

    /**
     * Starts a new {@link Activity} from the given fragment.
     * See {@link FragmentActivity#startActivityForResult(Intent, int, Bundle)}.
     */
    public void onStartActivityFromFragment(
            Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) {
        if (requestCode != -1) {
            throw new IllegalStateException(
                    "Starting activity with a requestCode requires a FragmentActivity host");
        }
        mContext.startActivity(intent);
    }

其他的一些东西有时间可以慢慢研究,上面这些如有写的不对欢迎交流。
源码地址:http://pan.baidu.com/s/1miDkUEG

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容