Fragment使用姿势之NoFragment
Posted 严振杰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Fragment使用姿势之NoFragment相关的知识,希望对你有一定的参考价值。
版权声明:转载必须注明本文转自严振杰的博客:http://blog.yanzhenjie.com
我还算是个比较传统的人,所以我一直觉得明年是从春节开始,所以这是我今年最后一骗博客了,当然也是我阳历2017年第一骗博客,希望大家可以吸收到对自己有帮助的东西,因此这是一骗承上启下的文章。对了,你没看错,是骗(反正你也不能顺着网线来打我)。
今天会先介绍Fragment
通常的四种用法,然后再介绍一个开源项目NoFragment
的特点和用法。
对于Fragment大家一定不陌生,记得第一次接触时我还是个大三的学生。是在2013年底参加Google全国大学生android
应用设计大赛时,初赛获得华中五省第二名,有资格参加全国总决赛。总决赛是现场编码,需求大概是这样的:要求使用的技术Fragment
、Socket
、NFC
、二维码识别
,远程控制一个装有摄像头的小车沿着规定的路线做task,并且一路拍照并上传到Android
端,Android
端分析图片颜色,根据指定颜色再进行下一个随机任务,重点是,Android端手动发送一个start命令后,人就不能触碰手机了,剩下的事情全部由我们开发的Android端智能完成。项目和任务都完成了,遗憾的是由于其它人耗时更短,所以只获得了15/34。不过从那个比赛也收获了很多,从此从JavaWeb跳入了Android的坑,但是我一直很庆幸自己在从事Android
开发。当时印象最深的是玩Fragment
和NFC
,网上没有任何文章,就只好从官网看文档,后来也养成了自己喜欢看官网的文档的习惯。
回到正题,不少被Fragment
的生命周期、相互跳转、参数传递、stack回退等问题困扰。如果是结合ViewPager
还好说。然而很多同学的项目结构是一个Activity + 多个Fragment的形式,这样会加快页面之间的跳转,但是也带来不少问题,请往下看。
因此顺理成章的安利一个开源项目NoFragment
,代码开源在Github:https://github.com/yanzhenjie/NoFragment
NoFragment 特点
- 支持传统
Fragment
的所有用法。 - 支持
startFragmentForResult(Fragment)
、onFragmentResult(int, int, Bundle)
,原生只有Activity。 - 支持同一个
Fragment
启动多个实例。 - 支持自动维护
Back Stack
,不会错乱。 - 支持在
Fragment
中直接setToolbar()
、setTitle()
、displayHomeButton()
。 - 返回键和
homeButton
自动处理,支持开发者拦截处理。 - 支持
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.xmlframgent
,第三种:FragmetManager#replace()
,第四种:FragmentManager#show()/hide()
,下面我们一个个来看看用法和他们的优缺点。
一、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
错乱,没有Activity
的Back 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()
用法。 - 没有
Activity
的standard
模式,也就是同一个Fragment启动多个实例。
因此我结合support
包中的Fragment
开发了NoFragment
,没错它和NoHttp是同一个系列,就是为了让你忘记Fragment
。
NoFragment新姿势新玩法
其实Fragment的用法上图是没法很直观的了解的,所以建议大家去下载Demo看看,如果觉得好用的话,顺便点个Star:https://github.com/yanzhenjie/NoFragment
特点
- 支持传统
Fragment
的所有用法。 - 支持
startFragmentForResult(Fragment)
、onFragmentResult(int, int, Bundle)
,原生只有Activity。 - 支持同一个
Fragment
启动多个实例。 - 支持自动维护
Back Stack
,不会错乱。 - 支持在
Fragment
中直接setToolbar()
、setTitle()
、displayHomeButton()
。 - 返回键和
homeButton
自动处理,支持开发者拦截处理。 - 支持
ActionBar Menu
、溢出Menu
等。 - 开发者不用管跳转逻辑、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
以上是关于Fragment使用姿势之NoFragment的主要内容,如果未能解决你的问题,请参考以下文章
你真的会用Fragment吗?Fragment常见问题以及androidx下Fragment的使用新姿势