Fragment使用姿势之NoFragment

版权声明:转载须标明本文转自严振杰的博客 https://blog.csdn.net/yanzhenjie1003/article/details/54562328

版权声明:转载必须注明本文转自严振杰的博客:http://blog.yanzhenjie.com

我还算是个比较传统的人,所以我一直觉得明年是从春节开始,所以这是我今年最后一骗博客了,当然也是我阳历2017年第一骗博客,希望大家可以吸收到对自己有帮助的东西,因此这是一骗承上启下的文章。对了,你没看错,是骗(反正你也不能顺着网线来打我)。

今天会先介绍Fragment通常的四种用法,然后再介绍一个开源项目NoFragment的特点和用法。

对于Fragment大家一定不陌生,记得第一次接触时我还是个大三的学生。是在2013年底参加Google全国大学生Android应用设计大赛时,初赛获得华中五省第二名,有资格参加全国总决赛。总决赛是现场编码,需求大概是这样的:要求使用的技术FragmentSocketNFC二维码识别,远程控制一个装有摄像头的小车沿着规定的路线做task,并且一路拍照并上传到Android端,Android端分析图片颜色,根据指定颜色再进行下一个随机任务,重点是,Android端手动发送一个start命令后,人就不能触碰手机了,剩下的事情全部由我们开发的Android端智能完成。项目和任务都完成了,遗憾的是由于其它人耗时更短,所以只获得了15/34。不过从那个比赛也收获了很多,从此从JavaWeb跳入了Android的坑,但是我一直很庆幸自己在从事Android开发。当时印象最深的是玩FragmentNFC,网上没有任何文章,就只好从官网看文档,后来也养成了自己喜欢看官网的文档的习惯。

回到正题,不少被Fragment的生命周期、相互跳转、参数传递、stack回退等问题困扰。如果是结合ViewPager还好说。然而很多同学的项目结构是一个Activity + 多个Fragment的形式,这样会加快页面之间的跳转,但是也带来不少问题,请往下看。

因此顺理成章的安利一个开源项目NoFragment,代码开源在Github:https://github.com/yanzhenjie/NoFragment

NoFragment 特点

  1. 支持传统Fragment的所有用法。
  2. 支持startFragmentForResult(Fragment)onFragmentResult(int, int, Bundle),原生只有Activity。
  3. 支持同一个Fragment启动多个实例。
  4. 支持自动维护Back Stack,不会错乱。
  5. 支持在Fragment中直接setToolbar()setTitle()displayHomeButton()
  6. 返回键和homeButton自动处理,支持开发者拦截处理。
  7. 支持ActionBar Menu、溢出Menu等。

当然最基本的是一个Activity + 多个Fragment。

依赖方法

  • Gradle一句话远程依赖
compile 'com.yanzhenjie:fragment:1.0.0'
  • Maven:
<dependency>
  <groupId>com.yanzhenjie</groupId>
  <artifactId>fragment</artifactId>
  <version>1.0.0</version>
  <type>pom</type>
</dependency>
  • Eclipse ADT
    请放弃治疗。

通常使用Fragment 4个姿势

一般我们使用Fragment大概有四种方法,第一种:ViewPager + Fragment,第二种:layout.xml{framgent},第三种:FragmetManager#replace(),第四种:FragmentManager#show()/hide(),下面我们一个个来看看用法和他们的优缺点。

一、ViewPager + Fragment

先来看一张图:

ViewPager + Fragment

当然了,代码也是超级无敌的简单(纯手写,小错请忽略):

ViewPager viewPager = findViewById(R.id.view_pager);
List<Fragment> fragmentList = new ArrayList<>();
fragmentList.add(new MyFragment());
...
FragmentManager fManager = getSupportFragmentManager();
FragmentAdapter adapter = new FragmentAdapter(fManager, fragmentList);
viewPager.setAdapter(adapter);

------------------------------------------------------------------------------

public class FragmentAdapter extends FragmentPagerAdapter {

    private List<Fragment> mFragmentList;

    public FragmentAdapter(FragmentManager fm, List<Fragment> fragmentList) {
        super(fm);
        mFragmentList = fragmentList;
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }

    @Override
    public int getCount() {
        return mFragmentList.size();
    }
}

这种玩法应该是最简单的一种了,ViewPager的Adapter会自动为我们管理show()hide(),生命周期的调用我们也完全不用担心,由于这种方法太简单了,就不说了,对此用法遇到问题的同学也可以在文章下留言,我会一一解答。

二、layout.xml {framgent}

这种玩法也很简单,在布局中写一个framgent,name填我们fragment完整包名就可以了,这种用法我们可以简单粗暴的把fragment理解为一个view:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <fragment
        android:id="@+id/fragment_sign_in"
        android:name="com.yanzhenjie.fragment.SignInFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

上述代码中的framgent完整包名:android:name="com.yanzhenjie.fragment.SignInFragment",这样布局会自动加载fragment进来,我们在java代码中可以通过如下方法拿到这个fragment

FragmetnManager fManager = getSupportFragmentManager();
SignInFragment signInFragment = (SignInFragment)fManager.findFragmentById(R.id.fragment_sign_in);
// TODO Then, do anything.

这种玩法常用的是大屏手机上,左边一个MenuFragment,右边一个ContentFragment,左边控制右边的内容。当然也有一个Fragment占满全屏的用法,比如高德地图MapView提供的MapFragment

三、FragmetManager#replace()

我最开始玩Fragment就是这么玩的,一般是在布局中提供一个空的FrameLayout,再用FragmetManager#replace()替换要显示的Fragment

// 调用
replaceFragment(new SignInFragment());

--------------------------------------------------------------

public <T extends Fragment> void replaceFragment(T fragment) {
    FragmentManager fManager = getSupportFragmentManager();
    fManager
        .beginTransaction()
        .replace(R.id.fragment_root, fragment)
        .commitAllowingStateLoss();
}

这种用法可以做到项目只有一个Activity,然后多个Fragment做为切换的页面。比较省内存,页面之间的跳转速度比较快,由于使用replace()方法它的缺点是:

  • 所以无法保存fragment的状态。
  • Back Stack错乱,没有ActivityBack Stack的好用。
  • 没有startFragmetnForResult()用法。

四、FragmentManager#show()/hide()

我们先来看看这种方法怎么玩:

/**
 * 显示一个新的fragment,并且隐藏旧的fragment。
 *
 * @param outFragment display fragment。
 * @param inFragment  hide fragment。
 * @param <T>         {@link Fragment}.
 */
public <T extends Fragment> void showHideFragment(T outFragment, T inFragment) {
    FragmentManager fManager = getSupportFragmentManager();
    // 如果要隐藏的fragment非空,隐藏。
    if (outFragment != null) {
        fManager
            .beginTransaction()
            .hide(outFragment)
            .commit();
    }
    // 先从栈中看是否存在要显示的fagment。
    String tag = inFragment.getClass().getClass().getSimpleName();
    Fragment tempFragment = fManager.findFragmentByTag(tag);
    if(tempFragment != null) { // 存在则直接显示。
        fManager
            .beginTransaction()
            .show(inFragment)
            .commitAllowingStateLoss();
    } else { // 不存在就添加并显示。
        fManager
            .beginTransaction()
            .add(R.id.fragment_root, inFragment, tag)
            .addToBackStack(tag)
            .commitAllowingStateLoss();
    }
}

/**
 * Destroy self.
 */
public <T extends Fragment> void finish(T fragment) {
    FragmentManager fManager = getSupportFragmentManager();
    fManager
        .beginTransaction()
        .remove(fragment)
        .commitAllowingStateLoss();
    }

经过上面的封装,其实已经变的很好用了:
Activity中:

showHideFragment(null, new SignInFragment());

Fragment中:

showHideFragment(this, new RegisterFragment());

当需要退出的时候:

finish(this);

结合注释看应该是看得懂的,调用show()hide()方法可以保存fragment的状态,但是逻辑复杂,同样存在以下缺点:

  • 没有startFragmetnForResult()用法。
  • 没有Activitystandard模式,也就是同一个Fragment启动多个实例。

因此我结合support包中的Fragment开发了NoFragment,没错它和NoHttp是同一个系列,就是为了让你忘记Fragment

NoFragment新姿势新玩法

其实Fragment的用法上图是没法很直观的了解的,所以建议大家去下载Demo看看,如果觉得好用的话,顺便点个Star:https://github.com/yanzhenjie/NoFragment

特点

  1. 支持传统Fragment的所有用法。
  2. 支持startFragmentForResult(Fragment)onFragmentResult(int, int, Bundle),原生只有Activity。
  3. 支持同一个Fragment启动多个实例。
  4. 支持自动维护Back Stack,不会错乱。
  5. 支持在Fragment中直接setToolbar()setTitle()displayHomeButton()
  6. 返回键和homeButton自动处理,支持开发者拦截处理。
  7. 支持ActionBar Menu、溢出Menu等。
  8. 开发者不用管跳转逻辑、back键处理、Toolbar加载菜单等。

图示

第一种,结合ToolBar、Menu的演示:

这里写图片描述

第二种,结合Toolbar、Menu + OverFlower的演示:

这里写图片描述

第三种,startFragmentForResult()onFragmentResult()演示:

这里写图片描述

第四种,不保存的在回退栈的演示:

这里写图片描述

用法

你的宿主Activity需要继承CompatActivity,然后启动一个Fragment

public class MainActivity extends CompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /*
         * 一句话即可,不要怀疑自己的眼睛,这是真的。
         */
        startFragment(MainFragment.class);
    }

    @Override
    protected int fragmentLayoutId() {
        return R.id.fragment_root;
    }

}

之后在Fragment中互相跳转,你可以不同管物理Back键被按下之类的:

一、以standard模式启动一个Fragment

startFragment(MoreMenuFragment.class);

二、以startActivityForResult()方式启动一个Fragment

// 启动,等待回调结果。
startFragmentForResquest(StartResultFragment.class, 100);

// 不论怎样回来都会回调onFragmentResult()。
@Override
public void onFragmentResult(int requestCode, int resultCode, @Nullable Bundle result) {
    switch (requestCode) {
        case 100: {
            if (resultCode == RESULT_OK) {
                // 操作成功:result就是调用的Fragment返回的结果。
            } else if (resultCode == RESULT_CANCELED) {
                // 操作取消。
            }
            break;
        }
    }
}

StartResultFragment中如果要返回结果,那么:

Bundle bundle = new Bundle();
bundle.putString("message", result);
setResult(RESULT_OK, bundle);
finish();

当然你也不设置,那么resultCode的默认值是RESULT_CANCELED

三、跳转时带参数

// 封装参数:
Bundle bundle = new Bundle();
bundle.putString("hehe", "呵呵哒");
bundle.putString("meng", "萌萌哒");
bundle.putString("bang", "棒棒哒");
bundle.putString("meme", "么么哒");

// 第一种:
NoFragment fragment = NoFragment.instantiate(getContext(), ArgumentFragment.class, bundle);

// 第二种:
ArgumentFragment fragment = new ArgumentFragment();
fragment.setArgument(bundle);

// 最后启动:
startFragment(fragment);

四、跳转的Fragment不保存在Back Stack

这种方式显示的fragment中如果调用了其它fragment,从其它fragment中回来时,这个fragment将会跳过,不会显示,也就是说:A-B-C-[back]-A,从A到B,B不加入回退栈,B再到C,C按下返回键,或者调用finish()方法,将会直接回到A。

startFragment(StackFragment.class, false);

五、同一个Fragment,启动多个实例

startFragment(MoreMenuFragment.class);
startFragment(MoreMenuFragment.class);
startFragment(MoreMenuFragment.class);
startFragment(MoreMenuFragment.class);

比如我们这里调用四次,那么回退栈中有四个MoreMenuFragment,按下返回键时将一个个退出。

六、Toolbar菜单的加载和处理

我们知道MD设计中,Toolbar的菜单很好看,而且利用Toolbar也很好加载,那么NoFragment也是完美支持的,当重写了onCreateOptionsMenu()方法后,调用setToolbar(Toolbar)方法时,将会调用onCreateOptionsMenu()方法,此时你就该加载菜单了,当然也只需要一句话。

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    // Load your menu.
    inflater.inflate(R.menu.menu_fragment_main, menu);
}

当用户点击meun的item时将会回调这个方法,和原生Activity是一样的。

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle menu item click.
    int id = item.getItemId();
    switch (id) {
        case R.id.action_settings: {
            Snackbar.make(mToolbar, R.string.action_settings, Snackbar.LENGTH_SHORT).show();
            break;
        }
        case R.id.action_exit: {
            Snackbar.make(mToolbar, R.string.action_exit, Snackbar.LENGTH_SHORT).show();
            break;
        }
    }
    return true;
}

七、Toolbar的返回按钮的处理

在正常开发中给Toolbar设置返回按钮也要好几行代码的,如果使用了NoFragment,那么:

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    // 首先设置Toolbar:
    setToolbar(mToolbar);

    // 设置标题:
    setTitle(R.string.title_fragment_main);

    // 显示返回按钮,图标开发者指定:
    displayHomeAsUpEnabled(R.drawable.ic_close_white);
}

设置了返回按钮后,用户点击返回按钮将自动杀死当前Fragment,当然你也可以拦截用户的返回行为:

@Override
    public boolean onInterceptToolbarBack() {
        // 返回true将拦截,返回false将不拦截。
        return true;
    }

混淆

-keep public class * extends android.support.v4.app.Fragment

好啦,到这里就完了,希望这个项目可以帮到需要的同学。


版权声明:转载必须注明本文转自严振杰的博客:http://blog.yanzhenjie.com

阅读更多

扫码向博主提问

严振杰

博客专家

学而不思则罔,思而不学则殆。
  • 擅长领域:
  • Android
  • JavaEE
去开通我的Chat快问
换一批

没有更多推荐了,返回首页