转载请注意:http://blog.youkuaiyun.com/wjzj000/article/details/51879798
本菜GitHub上开源了一个小的Android项目,感兴趣的看官大大们可以star下:
https://github.com/zhiaixinyang/MyFirstApp
进入暑假,啥也不想搞。所以就抱着几本书敲敲东西。
首先记录一下Fragment的一些用法和问题:
Fragment的生命周期:
正常的周期顺序是:
onAttach -> onCreate -> onCreateView -> onActivityCreated -> onStart -> onResume -> onPause -> onStop -> onDistroyView -> onDistroy -> onDetach
特别来看一下Fragment比Activity多的几个方法的用处。
onAttach(Activity):当Fragment与Activity发生关联时调用。
onCreateView(LayoutInflater, ViewGroup,Bundle):创建该Fragment的视图
onActivityCreated(Bundle):当Activity的onCreate方法返回时调用
onDestoryView():与onCreateView想对应,当该Fragment的视图被移除时调用
onDetach():与onAttach相对应,当Fragment与Activity关联被取消时调用
经测试,除了onCreateView方法,其他的所有方法如果被重写了,我们都得调用它的父类此方法。
1. 当一个fragment被创建的时候,它会经历以下状态.
- onAttach()
- onCreate()
- onCreateView()
- onActivityCreated()
2. 当这个fragment对用户可见的时候,它会经历以下状态。
- onStart()
- onResume()
3. 当这个fragment进入“后台模式”的时候,它会经历以下状态。
- onPause()
- onStop()
4. 当这个fragment被销毁了(或者持有它的activity被销毁了),它会经历以下状态。
- onPause()
- onStop()
- onDestroyView()
- onDestroy()
- onDetach()
静态使用
静态的使用还是比较简单的,只需要在activity的布局中写几个需要用到的<fragment>标签,然后在标签中android:name=""写上自己的Fragment类的全称即可。
而具体的代码在Fragment中写就好。简易的流程就是:继承Fragment,重写onCreateView方法,通过LayoutInflater加载fragment的布局,拿到返回值的View然后return。那么这个Fragment就算写好了。因为我们在activity布局中声明了这个Fragment类,所以在对应的Activity类中我们并不需要做些什么。只需要在onCreate方法中通过setContent把activity的布局文件加载进去就好。其他的业务逻辑在Fragment类中处理。那么这个我们的Fragment其实就可以当做比较复杂的控件来使用了。
(动态使用方式下文有补充)
不过,今天在敲Fragment的时候,遇到了尴尬的小情况,是关于v4包和app包下的fragment的区别,实际上之前从来没深究过这个问题,今天特地记下来。
首先我是习惯性在fragment类中继承v4下的Fragment类,然后在布局中添加相关fragment标签,可是在运行时报出了这样的错误Binary XML file line #8: Error inflating class fragment并提示说that is not a Fragment。
瞎捣鼓了很久才发现是v4和app捣的鬼,当把fragment中的继承换为app包下的Framgent后,程序可以正常运行。
这就促使了我去了解一下这俩个包下的Fragment的区别。
关于这两个Fragment使用<fragment>标签的问题
(1)app.fragment和v4.fragment都是可以使用<fragment>标签的:
如果是app.fragment,那么加载fragment的activity则没有什么特殊的地方继承Activity即可。
(2)当v4.fragment使用<fragment>标签的时候就需要注意:
那么加载fragment的activity必须继承FragmentActivity,否则就会报错。
其他的不同点也有,比如最常见的就是兼容问题.....(本彩笔觉得,兼容这个年代不是问题了吧...)
app包中的fragment,是在3.0之后才有的。
android.support.v4.app.Fragment:可以兼容到1.6的版本。
(2016年8月11补充基础使用)
不得不承认自己真的很菜。关于Fragment的动态加载,一上午都在纠结一个问题。实际上问题出在自身基础不熟练。
在Activity中的layout里动态添加一个Fragment时,layout的里的空间一直写的事<fragment>所以一直报错(这个标签用于静态加载时)。换成<xxxxxLayout>就行了(一般使用FrameLayout),也就是用正常的布局控件,而不是fragment控件。
虽然浪费了很多时间,但是有一个关setArguments(bundle)的收获。
关于动态加载的用法:
FragmentManager fm = getFragmentManager();
fm.beginTransaction().add(R.id.test_frag, new FragmentTest()).commit();
这里的R.id.test_frag 其实就是activity布局中的一个布局控件的id。也就是你想让Fragment出现的地方。
一般使用FrameLayout。
更多的时候我们使用的是替换方法:fm.beginTransaction().replace的方式替换Fragment。用法依然同上,第一个参数是id,第二个是Fragment
类。然后就是调用commit提交方法。一个简单的应用,我们可以通过这种方式制作底部导航栏...当然现阶段有很多
解决方案可以做底部导航栏。这里只是一个思路...
(后文有补充,关于使用官方API做底部导航)
关于setArguments(bundle):
这个newInstance是系统提供的静态工厂模式方法。这里的效果是在使用Fragment时通过静态的newInstance方法初始化Fragment,并且传入自己想要传递的值。那么为什么不适用new Fragment的方法在构造方法中传值呢?
public static Fragment newInstance(String text){
Bundle bundle=new Bundle();
bundle.putString(ARGUMENT,text);
FragmentTest fragmentTest=new FragmentTest();
fragmentTest.setArguments(bundle);
return fragmentTest;
}
在动态加载的时候通过newInstance(String text)加载Fragment并且传入想更新UI的数据,在onCreateView内可以随意取出。如果通过构造方法传,是拿不到值的,因为源码中Fragment的启动方法是使用的无参构造方法,所以我们在构造方法中传值其实并没有被调用。
提供静态工厂而不是使用默认构造函数的原因:
fragmnet经常会被销毁重新实例化,Android framework只会调用fragment无参的构造函数。在系统自动实例化fragment的过程中,你没有办法干预。一些需要外部传入的参数来决定的初始化就没有办法完成。使用静态工厂方法,将外部传入的参数可以通过Fragment.setArgument保存在它自己身上,这样我们可以在Fragment.onCreate(...)调用的时候将这些参数取出来。
2016年12月6日补充-官方API实现底部导航:
首先是activity的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/frg_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<!--必须写,官方要求,而且id写法必须是@android:id/tabcontent,写法很死,id名都得固定-->
<android.support.v4.app.FragmentTabHost
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:background="@color/white"
android:layout_height="wrap_content">
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
/>
</android.support.v4.app.FragmentTabHost>
</LinearLayout>
此处写法比较蛋疼,就得这么写,而且最下面的那个<FrameLayout>没什么卵用,但是不写还不行,而且id还必须那么写...而且宽和高不整成0,它还会把真正的<FrameLayout>给挤掉...
回到Actvity之中:
private FragmentTabHost mTabHost;
private LayoutInflater layoutInflater;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layoutInflater=LayoutInflater.from(this);
/**
* 这里可以这么理解:
* TabHost就是底部导航栏的长条集合,而它的内部类TabSpec就是具体的按钮
*/
mTabHost = (FragmentTabHost)findViewById(android.R.id.tabhost);
mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);
//这里构造方法传递的值并不会对实际效果产生影响,其实和下文的TabHost.TabSpec重名
TabHost.TabSpec one=mTabHost.newTabSpec("one");
/**
* R.layout.fth_tabspec_item
* 就是底部按钮的布局文件,自己想要啥样的就整成啥样的。
* 我这里是一个ImageView和一个TextView竖直排列
*/
View oneView= layoutInflater.inflate(R.layout.fth_tabspec_item,null);
ImageView iv= (ImageView) oneView.findViewById(R.id.iv_fth);
//这里是一个图片选择selector
iv.setBackgroundResource(R.drawable.tab_icon_select);
TextView tv= (TextView) oneView.findViewById(R.id.tv_fth);
tv.setText("One!");
one.setIndicator(oneView);
TabHost.TabSpec two=mTabHost.newTabSpec("one");
View twoView= layoutInflater.inflate(R.layout.fth_tabspec_item,null);
ImageView iv_= (ImageView) twoView.findViewById(R.id.iv_fth);
iv_.setBackgroundResource(R.drawable.tab_icon_select);
TextView tv_= (TextView) twoView.findViewById(R.id.tv_fth);
tv_.setText("Two!");
two.setIndicator(twoView);
//给导航按钮绑定你所要跳转的Fragment,具体有啥实现效果直接在Fragment中写就好
mTabHost.addTab(one,OneFragment.class, null);
mTabHost.addTab(two,ThreeFragment.class, null);
}
这里边涉及到的相关文件源码:
R.drawable.tab_icon_select:
(相关用法,参看http://blog.youkuaiyun.com/wjzj000/article/details/50937031)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- btn_ok就是一个图片 -->
<item android:state_focused="true" android:drawable="@drawable/btn_ok"/>
<item android:state_selected="true" android:drawable="@drawable/btn_ok"/>
<item android:drawable="@drawable/ic_launcher"/>
</selector>
R.layout.fth_tabspec_item:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="wrap_content"
android:gravity="center"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_fth"
android:layout_width="40dp"
android:layout_height="40dp" />
<TextView
android:id="@+id/tv_fth"
android:layout_width="wrap_content"
android:textColor="@color/text_color_select"
android:layout_height="wrap_content" />
</LinearLayout>
结合布局文件和最终效果比较好理解。