如果你已经非常熟练的,在工程内大量使用过自定义View,那么学习使用和理解Fragment将变得很容易。
Fragment的到来更加体现OOP思想,独立View模块,有自己的生命周期。可随时随地复用,在Activity里可以添加、移除、替换。功能强大。在Android开发过程中,自己经常性自定义View,其实和Fragment想实现的一样,在我看来最大区别在于Fragment具有生命周期。下面来看图解(图片摘抄于https://github.com/xxv/android-lifecycle)
这张图完整的描述了Fragment和Activity的生命周期。
Fragment相关主要类:
1.android.app.Fragment Or android.support.v4.app.Fragment
主要是定义Fragment
2.android.app.FragmentManager Or android.support.v4.app.FragmentManager
getFragmentManager() // v4中 在FragmentActivity中getSupportFragmentManager
其中最终要的是 Back Stack 的掌握
3. android.app.FragmentTransaction Or android.support.v4.app.FragmentTransaction
实现原子操作必须的事务
FragmentTransaction transaction = fm.benginTransatcion();// 开启一个事务
transaction.add()
向当前Activity添加Fragment
transaction.remove()
从当前Activity中移除Fragment,如果被移除的Fragment没有执行addToBackStack(),则该Fragment实例将会销毁。
transaction.replace()
使用另一个Fragment替换当前的,实际是先remove()然后add()
transaction.hide()
隐藏当前的Fragment,仅仅是设为不可见,并不会销毁
transaction.show()
显示之前隐藏的Fragment
detach()
将此Fragment从Activity中分离,会销毁其布局,但不会销毁该实例
attach()
将从Activity中分离的Fragment,重新关联到该Activity,重新创建其视图层次
transatcion.commit()//提交一个事务
API使用的建议:
a) 比如:我在FragmentA中有一个通过网络获得ListView数据,当切换到FragmentB时,我当然会希望返回A还能看到数据,则适合你的就是hide和show;也就是说,希望保留用户操作的面板,你可以使用hide和show。
b) 再比如:我不希望保留用户操作,你可以使用remove(),然后add();或者使用replace()这个和remove,add是相同的效果。
c) remove和detach有一点细微的区别,在不考虑回退栈的情况下,remove会销毁整个Fragment实例,而detach则只是销毁其视图结构,实例并不会被销毁。那么二者怎么取舍使用呢?如果你的当前Activity一直存在,那么在不希望保留用户操作的时候,你可以优先使用detach。
主要代码: @Override
public void onClick(View v)
{
FragmentThree fThree = new FragmentThree();
FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
//注意 hide + add 方式不会销毁视图
tx.hide(this);
tx.add(R.id.id_content , fThree, "THREE");
//replace 其实是 remove+add 方式 会销毁视图
// tx.replace(R.id.id_content, fThree, "THREE");
//我们可以用remove+add 方式来代替 replace方式
// tx.remove(getFragmentManager().getBackStackEntryAt(1));
// tx.add(R.id.id_content , fThree, "THREE");
//前一个Fragment被加入到栈中,不会被销毁实例
tx.addToBackStack(null);
tx.commit();
}
Fragment可以重复利用,就像自定义View,来看下布局文件中如何引用。注意android:name属性 填写对应的class
也可以使用class属性来关联Fragment
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.example.news.ArticleListFragment"
android:id="@+id/list"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.news.ArticleReaderFragment"
android:id="@+id/viewer"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
你可以提供 android:id 亦或是 android:tag 来唯一标识,两者都可以。
项目场景学习:
网易新闻APP 汽车之家APP我们上边说过,调用hide、replace 的区别在于是否销毁视图,而调用addToBackStatck会确定是否销毁前一个Fragment实例.若是使用ViewPager,就要涉及到2个Adapter ---- FragmentPagerAdapter 、 FragmentStatePagerAdapter 从类的名字上即可看出最大区别是 “状态”管理。详细区别如下:
1. 若ViewPager使用的是FragmentPagerAdapter.出于使用FragmentPagerAdapter 时,Fragment对象会一直存留在内存中,所以当有大量的显示页时,就不适合用 它仅适用于只有少数的page情况,比如2-3个页面。
2.使用FragmentStatePagerAdapter 时,如果Fragment不显示,那么Fragment对象会被销毁,但在回调onDestroy()方法之前会回调onSaveInstanceState(Bundle outState)方法来保存Fragment的状态,下次Fragment显示时通过onCreate(Bundle savedInstanceState)把存储的状态值取出来,FragmentStatePagerAdapter 比较适合页面比较多的情况 。我们来看下当自定义FragmentStatePagerAdapter时,我们需要重写的方法。
getCount() : 获得数量
getItem(): 获得当前要显示的Fragment
汽车之家APP应该只是重写了必要的getCount() 与 getItem() 实例的管理直接交给Adapter来自己处理。
那为什么我们的Fragment状态被保存了呢,请看这个方法。
/**
* 源码的该方法主要是帮助我们从缓存中拿,拿不到重新new
* 所以页面没有被销毁,因为状态是被保存的。这个类 Fragment.SavedState
*/
@Override
public Object [More ...] instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
fragment.setMenuVisibility(false);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
那么对应Fragment做了哪些东东呢,页面状态被保存,页面没有被销毁,我们知道页面被隐藏掉了,再show()的时候,会调用attach()。
看到了吧.这就是为何状态被保存了。记录了你之前浏览的位置,页面没有被销毁!状态被保留!
今天重新梳理了一下,针对几个业务问题分析并且解释一下
1. 我想实现“懒加载” ,就是当Fragment对用户可见时,请求服务器的数据,刷新列表操作等。
答: 这是一个长期的误区,比如Fragment+ViewPager做,会默认加载到下一个Fragment,但是,要理解,你别把自己请求服务器的方法绑在一起。系统默认只是要准备页面,状态等等,刷不刷数据跟这个没有关系,解决方法
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser){
//可见
}else{
//不可见
}
}
在你的fragment里面复写这个方法,实现去把。
2,. 我想知道,fragment是 new出来的,还是从缓存中取得。因为如果从缓存中取,某些操作我就不做了,我就在fragment新建的时候初始化一次。
答: 记得么,如果是FragmentPageAdapter 则会常驻内存中的,这种咱一般就认为它仅仅会调用一次onCreate(Bundle onInstanceState),当然被回收了另一说。
咱们说说被回收了如何判断。 拿FragmentStatePageAdapter,因为它的实例不是常驻内存的,会被销毁,所以每次onCreate()会调用。但是它的状态会被保留,在OnCreate(Bundle onInstanceState)里根据onInstanceState是否为null 来判断是否是new的Fragment。所以有下面的代码
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
// 状态未保留,Fragment被系统回收了
}else{
//状态被保留,Fragment实例被销毁,但是状态还在
}
}
另外重点理解下,如果使用的是 FragmentPageAdapter当不可见时,会调用onDestoryView() 这时候源码里
调用是detach() 就是View与Activity分离,销毁View。
若是使用的FragmentStatePageAdapter ,则会调用 onDestoryView() -> onDestory -> onDetach()
源码里调用是 onremove 销毁了view,并且销毁了fragment实例,状态保存。
若是被强制回收,则 销毁View,销毁fragment实例,状态销毁。
4. 如果我想保存自己的数据,等到Fragment再次显示的时候用到,如何呢?
答: 看代码
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
// 新new的Fragment ,实例被销毁l
}else{
int value = (Integer) savedInstanceState.get("value");
}
EventBus.getDefault().register(this);
}
@Override
public void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
outState.putInt("value", 1234);
super.onSaveInstanceState(outState);
}
另外说一点,在Fragment的onSaveInstanceState(Bundke outState)里这里面是空的,那到底谁最后执行了保存Bundle State的代码呢?
是 FragmentManager最后保存状态的。
但是因为FragmentActivity重写了Activity的onSaveInstanceState(Bundke outState)
里面都是让FragmentManager最后保存的。Activity的是保存在Application里面。
关于这个,我想说下我之前遇到困惑的问题。以前老项目大部分实现Tab都是TabActivity,但是因为Fragment出现了,所以以前涉及到Activity的都会有对应的Fragment来代替,就出现了这个类 FragmentTabHost ,这货不会自动的保存状态,导致修改完的项目不是原来的效果,页面没有被保存,重新调用接口! 这就火大了,网上有使用这货并且如何保存状态的解决方案。http://www.cnblogs.com/tiantianbyconan/p/3360938.html 这老兄的想法也没有错,使用hide和show,据我们所知这俩个方法不会销毁View,所以坏处是占用内存。这样你就相当于埋了一颗未知炸弹,不知道什么时候内存没了,奔溃了,或者其他异常的问题,最主要的应该是页面会卡吧。所以若是让我改,我会销毁页面,但是不选择销毁实例。使用detach()方法+缓存数据(不请求网络,让UI更快显示)+记录用户浏览位置。 我没有试验,但是应该是可行的。
我分享个例子程序,效果和上面这俩个APP是一样的。源码 http://download.csdn.net/detail/u013651247/8150105
我改了下源码,主要是添加一个setCutIndicatorWidth方法,减少左右下划线的距离,使得当一个平面是有2个或者3个时,UI上更加好看。
请看效果
修改源码后的效果。适应性更好了。
重要的属性:
// 可以设置它的各种属性比如
// 分割线
mTabStrip.setDividerColor(getResources().getColor(
android.R.color.transparent));
// 背景色,比如点击有背景
mTabStrip.setBackground(getResources().getDrawable(
R.drawable.background_tab));
// 底线设置
mTabStrip.setUnderlineColorResource(R.color.public_theme_color);
mTabStrip.setUnderlineHeight(0);
// 滑动的线设置
mTabStrip.setIndicatorHeight(8);
mTabStrip.setIndicatorColorResource(R.color.public_theme_color);
// 分类字体设置
mTabStrip.setTextColor(getResources().getColor(
R.color.public_text_color));
mTabStrip.setTextSize(28);
//当item较少时,用不用自动适配屏幕
mTabStrip.setShouldExpand(true);
//减掉左右划线,让UI看起来更舒服
mTabStrip.setCutIndicatorWidth(25);