网络电台研究(3)Fragment系统学习,解决各种问题

如果你已经非常熟练的,在工程内大量使用过自定义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);








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值