Fragment

Android Fragment完全解析,关于碎片所需知道的一切

我们都知道,Android上的界面展示都是通过Activity实现的,
但是Activity也有它的局限性,同样的界面在手机上显示可能很好看,在平板上就未必了,因为平板的屏幕非常大,手机的界面放在平板上可能会有过分被拉大、控件间距过大等情况。这个时候更好的体验效果是在Activity中嵌入“小Activity”,然后每个“小Activity”又可以拥有自己的布局。因此有了Fragment。

Fragment初探

为了让界面在平板上更好地展示,Android在3.0版本引入了Fragment(碎片)功能的,它非常类似于Activity,可以像Activity一样包含布局。Fragment通常是嵌套在Activity中使用的,现在想象这种场景:有两个Fragment,Fragment1包含了一个ListView,每行显示一本书的标题。Fragment2包含了TextView和ImageView,来显示书的详细内容和图片。

如果现在程序运行竖屏模式的平板或手机上,Fragment 1可能嵌入在一个Activity中,而Fragment 2可能嵌入在另一个Activity中,如下图所示:

Fragment.jpg

而如果现在程序运行在横屏模式的平板上,两个Fragment就可以嵌入在同一个Activity中了,如下图所示:

由此可以看出,使用Fragment可以让我们更加充分地利用平板的屏幕空间。

需要注意,Fragment是在3.0版本引入的,如果使用的是3.0之前的系统,需要先导入android-support-v4的jar包才能使用Fragment功能。

静态使用Fragment

Fragment当成普通的控件,直接写在Activity的布局文件中。步骤:
1、继承Fragment,重写onCreateView决定Fragemnt的布局
2、在Activity中声明此Fragment,就当和普通的View一样

新建两个分别名为fragment1和fragment2的xml布局文件

然后新建一个类Fragment1,这个类是继承自Fragment的:
同样建立Fragment2.

public class Fragment1 extends Fragment {       
@Override   
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {     
return inflater.inflate(R.layout.fragment1, container, false);  
}
}

然后打开或新建activity_main.xml作为主Activity的布局文件,在里面加入两个Fragment的引用,使用android:name前缀来引用具体的Fragment。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
android:layout_width="match_parent" 
android:layout_height="match_parent" 
android:baselineAligned="false" > 
<fragment 
android:id="@+id/fragment1" 
android:name="com.example.fragmentdemo.Fragment1" 
android:layout_width="0dip" 
android:layout_height="match_parent" 
android:layout_weight="1" /> 
<fragment 
android:id="@+id/fragment2"     
android:name="com.example.fragmentdemo.Fragment2" 
android:layout_width="0dip" 
android:layout_height="match_parent" 
android:layout_weight="1" />
</LinearLayout>
动态添加Fragment

在XML中使用Fragment,这仅仅是Fragment最简单的功能而已。Fragment的真正强大之处在于可以动态地添加到Activity当中。

在上面的基础上修改。打开activity_main.xml,将其中对Fragment的引用都删除,只保最外层的LinearLayout,并给它添加一个id.因为我们要动态添加Fragment,不用在XML里添加了。

在MainActivity,修改其中代码

public class MainActivity extends Activity {    

@Override
protected void onCreate(Bundle savedInstanceState) {        
super.onCreate(savedInstanceState);     
setContentView(R.layout.activity_main);     
Display display = getWindowManager().getDefaultDisplay();       
if (display.getWidth() > display.getHeight()) { 
Fragment1 fragment1 = new Fragment1();          
getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment1).commit();      
} else { 
Fragment2 fragment2 = new Fragment2();          
getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment2).commit();      
}   
}
}

首先,我们要获取屏幕的宽度和高度,然后进行判断,如果屏幕宽度大于高度就添加fragment1,如果高度大于宽度就添加fragment2.动态添加fragment主要分为4步。

  1. 获取到FragmentManager,在Activity中可以直接通过getFragmentManager得到。
  2. 开启一个事务,通过调用beginTransaction方法开启。
  3. 向容器内加入Fragment,一般使用replace方法实现,需要传入容器的id和Fragment的实例。
  4. 提交事务。
Fragment的生命周期

因为Fragment必须嵌入在Activity中使用,所以Fragment的生命周期和它所处的Activity是密切相关的。

如果Activity是暂停状态,其中所有的Fragment都是暂停状态;如果Activity是stopped状态,这个Activity中所有的Fragment都不能被启动;如果Activity被销毁,那么它其中的所有Fragment都会被销毁。
  但是,当Activity在活动状态,可以独立控制Fragment的状态,比如加上或者移除Fragment。

FragmentRecycle.JPG

使用Fragment时,需要继承Fragment或者Fragment的子类(DialogFragment, ListFragment, PreferenceFragment, WebViewFragment),所以Fragment的代码看起来和Activity的类似。

必须实现的三个回调函数:
onCreate()
  系统在创建Fragment的时候调用这个方法,这里应该初始化相关的组件,一些即便是被暂停或者被停止时依然需要保留的东西。
  onCreateView()
  当第一次绘制Fragment的UI时系统调用这个方法,必须返回一个View,如果Fragment不提供UI也可以返回null。
  注意,如果继承自ListFragment,onCreateView()默认的实现会返回一个ListView,所以不用自己实现。
  onPause()
  当用户离开Fragment时第一个调用这个方法,需要提交一些变化,因为用户很可能不再返回来。

FragmentLife.JPG

Fragment和Activity的生命周期非常相似,只有几个Activity中没有的新方法,如下:

onAttach方法:Fragment和Activity建立关联的时候调用。
onCreateView方法:为Fragment加载布局时调用。
onActivityCreated方法:当Activity中的onCreate方法执行完后调用。
onDestroyView方法:Fragment中的布局被移除时调用。
onDetach方法:Fragment和Activity解除关联的时候调用。

注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现,

Fragment之间进行通信

通常情况下,Activity中都会包含多个Fragment,这时多个Fragment之间如何进行通信就是个非常重要的问题了。

主要都是通过getActivity这个方法实现的。getActivity方法可以让Fragment获取到关联的Activity,然后再调用Activity的findViewById方法,就可以获取到和这个Activity关联的其它Fragment的视图了。


Fragment是什么,生命周期、静态和动态使用,
Fragment如何与Activity交互?Fragment如何创建对话框?Fragment如何与ActionBar集成等等。

Fragment家族常用的API

android.app.Fragment主要用于定义Fragment
android.app.FragmentManager 主要用于在Activity中操作Fragment
android.app.FragmentTransaction 保证一系列Fragment操作的原子性


a.获取FragmentManager的方式
getFragmentManager();  //v4中,getSupportFragmentManager
b. 主要的操作都是FragmentTransaction
FragmentTransaction transaction = fm.beginTransaction();//开启一个事务
  • transaction.add()
    往Activity中添加一个Fragment
  • transaction.remove()
    从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈,这个Fragment实例将会被销毁。
  • transaction.replace()
    使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体
  • transaction.hide()
    隐藏当前的Fragment(),仅仅是设为不可见,并不会销毁。
  • transaction.show()
    显示之前隐藏的Fragment
  • detach()
    会将view从UI中移除,和remove()不同,此时的Fragment的状态依然由FragmentManager维护
  • attach()
    重建view视图,附加到UI上并显示。
  • transatcion.commit()//提交一个事务

注意:常用Fragment的哥们,可能会经常遇到这样Activity状态不一致:State loss这样的错误。主要是因为:commit方法一定要在Activity.onSaveInstance()之前调用。
上述,基本是操作Fragment的所有的方式了,在一个事务开启到提交可以进行多个的添加、移除、替换等操作。

值得注意的是:如果你喜欢使用Fragment,一定要清楚这些方法,哪个会销毁视图,哪个会销毁实例,哪个仅仅只是隐藏,这样才能更好的使用它们。

a、比如:我在FragmentA中的EditText填了一些数据,当切换到FragmentB时,如果希望会到A还能看到数据,则适合你的就是hide和show;也就是说,希望保留用户操作的面板,你可以使用hide和show,当然了不要使劲在那new实例,进行下非null判断。
b、再比如:我不希望保留用户操作,你可以使用remove(),然后add();或者使用replace()这个和remove,add是相同的效果。
c、remove和detach有一点细微的区别,在不考虑回退栈的情况下,remove会销毁整个Fragment实例,而detach则只是销毁其视图结构,实例并不会被销毁。那么二者怎么取舍使用呢?如果你的当前Activity一直存在,那么在不希望保留用户操作的时候,你可以优先使用detach。


Android Fragment 真正的完全解析(下)

管理Fragment回退栈

类似于Android系统为Activity维护一个任务栈,我们也可以通过Activity维护一个回退栈来保存每次Fragment事务发生的变化。如果你将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的Fragment。一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前Activity。

添加一个Fragment事务到回退栈

FragmentTransaction.addToBackStack(String)

eg. Activity的布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    
xmlns:tools="http://schemas.android.com/tools" 
android:layout_width="match_parent" 
android:layout_height="match_parent" > 
<FrameLayout 
android:id="@+id/id_content" 
android:layout_width="fill_parent" 
android:layout_height="fill_parent" > 
</FrameLayout>
</RelativeLayout>

不同的Fragment就在这个FrameLayout中显示。

MainActivity

public class MainActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_main);

    FragmentManager fm = getFragmentManager();
    FragmentTransaction tx = fm.beginTransaction();
    tx.add(R.id.id_content, new FragmentOne(),"ONE");
    tx.commit();
}
}
Fragment与Activity通信

因为所有的Fragment都是依附于Activity的,所以通信起来并不复杂,大概归纳为:

a、如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
b、如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作。
c、在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作。
注:如果在Fragment中需要Context,可以通过调用getActivity(),如果该Context需要在Activity被销毁后还存在,则使用getActivity().getApplicationContext()。


Fragment与Activity通信的最佳实践

因为要考虑Fragment的重复使用,所以必须降低Fragment与Activity的耦合,而且Fragment更不应该直接操作别的Fragment,毕竟Fragment操作应该由它的管理者Activity来决定

首先看FragmentOne

public class FragmentOne extends Fragment implements OnClickListener{   
private Button mBtn;    
/**  
* 设置按钮点击的回调  
* @author zhy    
*    
*/  
public interface FOneBtnClickListener   {       
void onFOneBtnClick();  
}   
@Override   
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)    
{       
View view =inflater.inflate(R.layout.fragment_one, container, false);       
mBtn = (Button)view.findViewById(R.id.id_**fragment**_one_btn);     
mBtn.setOnClickListener(this);      
return view;    
}   
/**  
* 交给宿主Activity处理,如果它希望处理     
*/  
@Override
public void onClick(View v) 
{       
if (getActivity() instanceof FOneBtnClickListener)      
{           
  ((FOneBtnClickListener)getActivity()).onFOneBtnClick();       
}   
}
}

FragmentTwo代码

public class FragmentTwo extends Fragment implements OnClickListener{       
private Button mBtn ;       
private FTwoBtnClickListener fTwoBtnClickListener ;     
public interface FTwoBtnClickListener   
{       
void onFTwoBtnClick();  
}   
//设置回调接口    
public void setfTwoBtnClickListener(FTwoBtnClickListener fTwoBtnClickListener)  
{       
this.fTwoBtnClickListener = fTwoBtnClickListener;   
}   
@Override   
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)    
{       
View view = inflater.inflate(R.layout.**fragment**_two, container, false);      
mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);        
mBtn.setOnClickListener(this);      
return view ;   
}   
@Override   
public void onClick(View v) 
{       
if(fTwoBtnClickListener != null)        
{               
fTwoBtnClickListener.onFTwoBtnClick();      
}   
}
}

与FragmentOne极其类似,但是我们提供了setListener这样的方法,意味着Activity不仅需要实现该接口,还必须显示调用mFTwo.setfTwoBtnClickListener(this)。

可以看到现在的FragmentOne不和任何Activity耦合,任何Activity都可以使用;并且我们声明了一个接口,来回调其点击事件,想要管理其点击事件的Activity实现此接口就即可。可以看到我们在onClick中首先判断了当前绑定的Activity是否实现了该接口,如果实现了则调用

如何处理运行时配置发生变化

Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的最佳方案

当屏幕发生旋转,Activity发生重新启动,默认的Activity中的Fragment也会跟着Activity重新创建;这样造成当旋转的时候,本身存在的Fragment会重新启动,然后当执行Activity的onCreate时,又会再次实例化一个新的Fragment。
如何解决呢?

其实通过检查onCreate的参数Bundle savedInstanceState就可以判断,当前是否发生Activity的重新创建:
默认的savedInstanceState会存储一些数据,包括Fragment的实例。所以,我们简单改一下代码,只有在savedInstanceState==null时,才进行创建Fragment实例:

使用Fragment创建对话框

Android 官方推荐 : DialogFragment 创建对话框


Android Fragment 你应该知道的一切

1. 概述

一般情况下,我们在Activity里面会这么添加Fragment:

public class MainActivity extends FragmentActivity{     
private ContentFragment mContentFragment ;  
@Override   
protected void onCreate(Bundle savedInstanceState)  {       
super.onCreate(savedInstanceState);     
setContentView(R.layout.activity_main);         
FragmentManager fm = getSupportFragmentManager();       
mContentFragment = (ContentFragment) 
fm.findFragmentById(R.id.id_fragment_container);                
if(mContentFragment == null )       
{           
mContentFragment = new ContentFragment();           
fm.beginTransaction().add(R.id.id_fragment_container,mContentFragment).commit();        
}   
}
}

针对上面代码,问两个问题:
1、为什么需要判null呢?
主要是因为,当Activity因为配置发生改变(屏幕旋转)或者内存不足被系统杀死,造成重新创建时,我们的fragment会被保存下来,但是会创建新的FragmentManager,新的FragmentManager会首先会去获取保存下来的fragment队列,重建fragment队列,从而恢复之前的状态。
2、add(R.id.id_fragment_container,mContentFragment)中的布局的id有何作用?
一方面呢,是告知FragmentManager,此fragment的位置;另一方面是此fragment的唯一标识;就像我们上面通过fm.findFragmentById(R.id.id_fragment_container)查找

2. Fragment Arguments

需要通过Intent传递参数到目标Activity的Fragment中,那么此Fragment如何获取当前的Intent的值呢(考虑解耦的情况下)?

public class ContentFragment extends Fragment{  
private String mArgument;   
public static final String ARGUMENT = "argument";   
@Override   
public void onCreate(Bundle savedInstanceState) 
{       
super.onCreate(savedInstanceState);     // mArgument =   getActivity().getIntent().getStringExtra(ARGUMENT);        
Bundle bundle = getArguments();     
if (bundle != null)         
mArgument = bundle.getString(ARGUMENT); 
}   
/**  
* 传入需要的参数,设置给arguments   
* @param argument    
* @return    
*/  
public static ContentFragment newInstance(String argument)  
{       
Bundle bundle = new Bundle();       
bundle.putString(ARGUMENT, argument);       
ContentFragment contentFragment = new     ContentFragment();        
contentFragment.setArguments(bundle);       
return contentFragment; 
}

Fragment添加newInstance方法,将需要的参数传入,设置到bundle中,然后setArguments(bundle),最后在onCreate中进行获取;
这样就完成了Fragment和Activity间的解耦。当然了这里需要注意:
setArguments方法必须在fragment创建以后,添加给Activity前完成。千万不要,首先调用了add,然后设置arguments。

3. Fragment的startActivityForResult

我们点击跳转到对应Activity的Fragment中,并且希望它能够返回参数。

在Fragment中存在startActivityForResult()以及onActivityResult()方法,但是,没有setResult()方法,用于设置返回的intent,这样我们就需要通过调用getActivity.setResult(ListTitleFrament.REQUEST_DETAIL, intent);

fragment能够从Activity中接收返回结果,但是其自设无法产生返回结果,只有Activity拥有返回结果。

5. FragmentPaperAdapter与FragmentStatePagerAdapter

相信这两个PagerAdapter的子类,大家都不陌生吧~~自从Fragment问世,使用ViewPager再结合上面任何一个实例的制作APP主页的案例特别多
那么这两个类有何区别呢?

主要区别就在与对于fragment是否销毁,下面细
说:

FragmentPagerAdapter:对于不再需要的fragment,选择调用detach方法,仅销毁视图,并不会销毁fragment实例。

FragmentStatePagerAdapter:会销毁不再需要的fragment,当当前事务提交以后,会彻底的将fragmeng从当前Activity的FragmentManager中移除,state标明,销毁时,会将其onSaveInstanceState(Bundle outState)中的bundle信息保存下来,当用户切换回来,可以通过该bundle恢复生成新的fragment,也就是说,你可以在onSaveInstanceState(Bundle outState)方法中保存一些数据,在onCreate中进行恢复创建。

如上所说,使用FragmentStatePagerAdapter当然更省内存,但是销毁新建也是需要时间的。一般情况下,如果你是制作主页面,就3、4个Tab,那么可以选择使用FragmentPagerAdapter,如果你是用于ViewPager展示数量特别多的条目时,那么建议使用FragmentStatePagerAdapter。


参考:

Android Fragment 真正的完全解析(上)

Android Fragment 真正的完全解析(下)

Android Fragment 你应该知道的一切

Android Fragment实现TabHost解析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容