出处

自从Fragment出现,曾经有段时间,感觉大家谈什么都能跟Fragment谈上关系,做什么都要问下Fragment能实现不~~~哈哈,是不是有点过~~~

本篇博客力求为大家说明Fragment如何产生,什么是Fragment,Fragment生命周期,如何静态和动态的使用Fragment,Fragment回退栈,Fragment事务;以及Fragment的一些特殊用途,例如:没有布局的Fragment有何用处?Fragment如何与Activity交互?Fragment如何创建对话框?Fragment如何与ActionBar集成等等。

1、Fragment的产生与介绍

Android运行在各种各样的设备中,有小屏幕的手机,超大屏的平板甚至电视。针对屏幕尺寸的差距,很多情况下,都是先针对手机开发一套App,然后拷贝一份,修改布局以适应平板神马超级大屏的。难道无法做到一个App可以同时适应手机和平板么,当然了,必须有啊。Fragment的出现就是为了解决这样的问题。你可以把Fragment当成Activity的一个界面的一个组成部分,甚至Activity的界面可以完全有不同的Fragment组成,更帅气的是Fragment拥有自己的生命周期和接收、处理用户的事件,这样就不必在Activity写一堆控件的事件处理的代码了。更为重要的是,你可以动态的添加、替换和移除某个Fragment。

2、Fragment的生命周期

Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。官网这张图很好的说明了两者生命周期的关系:

可以看到Fragment比Activity多了几个额外的生命周期回调方法:
onAttach(Activity)
当Fragment与Activity发生关联时调用。
onCreateView(LayoutInflater, ViewGroup,Bundle)
创建该Fragment的视图
onActivityCreated(Bundle)
当Activity的onCreate方法返回时调用
onDestoryView()
与onCreateView想对应,当该Fragment的视图被移除时调用
onDetach()
与onAttach相对应,当Fragment与Activity关联被取消时调用
注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现,

3、静态的使用Fragment

嘿嘿,终于到使用的时刻了~~

这是使用Fragment最简单的一种方式,把Fragment当成普通的控件,直接写在Activity的布局文件中。步骤:

1、继承Fragment,重写onCreateView决定Fragemnt的布局

2、在Activity中声明此Fragment,就当和普通的View一样

下面展示一个例子(我使用2个Fragment作为Activity的布局,一个Fragment用于标题布局,一个Fragment用于内容布局):

TitleFragment的布局文件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="45dp"
  5. android:background="@drawable/title_bar" >
  6. <ImageButton
  7. android:id="@+id/id_title_left_btn"
  8. android:layout_width="wrap_content"
  9. android:layout_height="wrap_content"
  10. android:layout_centerVertical="true"
  11. android:layout_marginLeft="3dp"
  12. android:background="@drawable/showleft_selector" />
  13. <TextView
  14. android:layout_width="fill_parent"
  15. android:layout_height="fill_parent"
  16. android:gravity="center"
  17. android:text="我不是微信"
  18. android:textColor="#fff"
  19. android:textSize="20sp"
  20. android:textStyle="bold" />
  21. </RelativeLayout>

TitleFragment

  1. package com.zhy.zhy_fragments;
  2. import android.app.Fragment;
  3. import android.os.Bundle;
  4. import android.view.LayoutInflater;
  5. import android.view.View;
  6. import android.view.View.OnClickListener;
  7. import android.view.ViewGroup;
  8. import android.widget.ImageButton;
  9. import android.widget.Toast;
  10. public class TitleFragment extends Fragment
  11. {
  12. private ImageButton mLeftMenu;
  13. @Override
  14. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  15. Bundle savedInstanceState)
  16. {
  17. View view = inflater.inflate(R.layout.fragment_title, container, false);
  18. mLeftMenu = (ImageButton) view.findViewById(R.id.id_title_left_btn);
  19. mLeftMenu.setOnClickListener(new OnClickListener()
  20. {
  21. @Override
  22. public void onClick(View v)
  23. {
  24. Toast.makeText(getActivity(),
  25. "i am an ImageButton in TitleFragment ! ",
  26. Toast.LENGTH_SHORT).show();
  27. }
  28. });
  29. return view;
  30. }
  31. }

同理还有ContentFragment的其布局文件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical" >
  6. <TextView
  7. android:layout_width="fill_parent"
  8. android:layout_height="fill_parent"
  9. android:gravity="center"
  10. android:text="使用Fragment做主面板"
  11. android:textSize="20sp"
  12. android:textStyle="bold" />
  13. </LinearLayout>
  1. package com.zhy.zhy_fragments;
  2. import android.app.Fragment;
  3. import android.os.Bundle;
  4. import android.view.LayoutInflater;
  5. import android.view.View;
  6. import android.view.ViewGroup;
  7. public class ContentFragment extends Fragment
  8. {
  9. @Override
  10. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  11. Bundle savedInstanceState)
  12. {
  13. return inflater.inflate(R.layout.fragment_content, container, false);
  14. }
  15. }

MainActivity

  1. package com.zhy.zhy_fragments;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.Window;
  5. public class MainActivity extends Activity
  6. {
  7. @Override
  8. protected void onCreate(Bundle savedInstanceState)
  9. {
  10. super.onCreate(savedInstanceState);
  11. requestWindowFeature(Window.FEATURE_NO_TITLE);
  12. setContentView(R.layout.activity_main);
  13. }
  14. }

Activity的布局文件:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent" >
  5. <fragment
  6. android:id="@+id/id_fragment_title"
  7. android:name="com.zhy.zhy_fragments.TitleFragment"
  8. android:layout_width="fill_parent"
  9. android:layout_height="45dp" />
  10. <fragment
  11. android:layout_below="@id/id_fragment_title"
  12. android:id="@+id/id_fragment_content"
  13. android:name="com.zhy.zhy_fragments.ContentFragment"
  14. android:layout_width="fill_parent"
  15. android:layout_height="fill_parent" />
  16. </RelativeLayout>

是不是把Fragment当成普通的View一样声明在Activity的布局文件中,然后所有控件的事件处理等代码都由各自的Fragment去处理,瞬间觉得Activity好干净有木有~~代码的可读性、复用性以及可维护性是不是瞬间提升了~~~下面看下效果图:

4、动态的使用Fragment

上面已经演示了,最简单的使用Fragment的方式~下面介绍如何动态的添加、更新、以及删除Fragment

为了动态使用Fragment,我们修改一下Actvity的布局文件,中间使用一个FrameLayout,下面添加四个按钮~~~嘿嘿~~不是微信的按钮- -!

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent" >
  5. <fragment
  6. android:id="@+id/id_fragment_title"
  7. android:name="com.zhy.zhy_fragments.TitleFragment"
  8. android:layout_width="fill_parent"
  9. android:layout_height="45dp" />
  10. <include
  11. android:id="@+id/id_ly_bottombar"
  12. android:layout_width="fill_parent"
  13. android:layout_height="55dp"
  14. android:layout_alignParentBottom="true"
  15. layout="@layout/bottombar" />
  16. <FrameLayout
  17. android:id="@+id/id_content"
  18. android:layout_width="fill_parent"
  19. android:layout_height="fill_parent"
  20. android:layout_above="@id/id_ly_bottombar"
  21. android:layout_below="@id/id_fragment_title" />
  22. </RelativeLayout>

底部四个按钮的布局就不贴了,到时看效果图就明白了~~

下面主Activity

  1. package com.zhy.zhy_fragments;
  2. import android.app.Activity;
  3. import android.app.FragmentManager;
  4. import android.app.FragmentTransaction;
  5. import android.os.Bundle;
  6. import android.view.View;
  7. import android.view.View.OnClickListener;
  8. import android.view.Window;
  9. import android.widget.LinearLayout;
  10. public class MainActivity extends Activity implements OnClickListener
  11. {
  12. private LinearLayout mTabWeixin;
  13. private LinearLayout mTabFriend;
  14. private ContentFragment mWeixin;
  15. private FriendFragment mFriend;
  16. @Override
  17. protected void onCreate(Bundle savedInstanceState)
  18. {
  19. super.onCreate(savedInstanceState);
  20. requestWindowFeature(Window.FEATURE_NO_TITLE);
  21. setContentView(R.layout.activity_main);
  22. // 初始化控件和声明事件
  23. mTabWeixin = (LinearLayout) findViewById(R.id.tab_bottom_weixin);
  24. mTabFriend = (LinearLayout) findViewById(R.id.tab_bottom_friend);
  25. mTabWeixin.setOnClickListener(this);
  26. mTabFriend.setOnClickListener(this);
  27. // 设置默认的Fragment
  28. setDefaultFragment();
  29. }
  30. private void setDefaultFragment()
  31. {
  32. FragmentManager fm = getFragmentManager();
  33. FragmentTransaction transaction = fm.beginTransaction();
  34. mWeixin = new ContentFragment();
  35. transaction.replace(R.id.id_content, mWeixin);
  36. transaction.commit();
  37. }
  38. @Override
  39. public void onClick(View v)
  40. {
  41. FragmentManager fm = getFragmentManager();
  42. // 开启Fragment事务
  43. FragmentTransaction transaction = fm.beginTransaction();
  44. switch (v.getId())
  45. {
  46. case R.id.tab_bottom_weixin:
  47. if (mWeixin == null)
  48. {
  49. mWeixin = new ContentFragment();
  50. }
  51. // 使用当前Fragment的布局替代id_content的控件
  52. transaction.replace(R.id.id_content, mWeixin);
  53. break;
  54. case R.id.tab_bottom_friend:
  55. if (mFriend == null)
  56. {
  57. mFriend = new FriendFragment();
  58. }
  59. transaction.replace(R.id.id_content, mFriend);
  60. break;
  61. }
  62. // transaction.addToBackStack();
  63. // 事务提交
  64. transaction.commit();
  65. }
  66. }

可以看到我们使用FragmentManager对Fragment进行了动态的加载,这里使用的是replace方法~~下一节我会详细介绍FragmentManager的常用API。

注:如果使用Android3.0以下的版本,需要引入v4的包,然后Activity继承FragmentActivity,然后通过getSupportFragmentManager获得FragmentManager。不过还是建议版Menifest文件的uses-sdk的minSdkVersion和targetSdkVersion都改为11以上,这样就不必引入v4包了。

代码中间还有两个Fragment的子类,ContentFragment上面已经见过,FriendFragment其实类似:

  1. package com.zhy.zhy_fragments;
  2. import android.app.Fragment;
  3. import android.os.Bundle;
  4. import android.view.LayoutInflater;
  5. import android.view.View;
  6. import android.view.ViewGroup;
  7. public class FriendFragment extends Fragment
  8. {
  9. @Override
  10. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  11. Bundle savedInstanceState)
  12. {
  13. return inflater.inflate(R.layout.fragment_friend, container, false);
  14. }
  15. }

效果图:

可以看到很好的实现了效果,其实这个效果以前的博客中也出现过,在博客:Android项目Tab类型主界面大总结 Fragment+TabPageIndicator+ViewPager,有兴趣可以看看。ps:为了代码的简洁,就不添加按钮的点击变化什么的了,主要讲解功能了~~~

5、Fragment家族常用的API

Fragment常用的三个类:

android.app.Fragment 主要用于定义Fragment

android.app.FragmentManager 主要用于在Activity中操作Fragment

android.app.FragmentTransaction 保证一些列Fragment操作的原子性,熟悉事务这个词,一定能明白~

a、获取FragmentManage的方式:

getFragmentManager() // v4中,getSupportFragmentManager

b、主要的操作都是FragmentTransaction的方法

FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务

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。

上述已经介绍完成了Fragment常用的一些方法,相信看完,大家一定清楚了Fragment的产生理由,以及如何使用Fragment,再根据API的讲解,也能明白,曾经为何觉得Fragment会出现一些列乱七八槽的问题,终究是因为没有弄清楚其生命周期。

由于篇幅原因,剩下的内容留到下一篇了。在下一篇,会介绍:

1、如何管理Fragment回退栈

2、Fragment如何与Activity交互

3、Fragment与Activity交互的最佳实践

4、没有视图的Fragment的用处

5、使用Fragment创建对话框

6、如何与ActionBar,MenuItem集成等~~

会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护。
 

本篇将介绍上篇博客提到的:如何管理Fragment回退栈,Fragment如何与Activity交互,Fragment与Activity交互的最佳实践,没有视图的Fragment的用处,使用Fragment创建对话框,如何与ActionBar,MenuItem集成等~~

1、管理Fragment回退栈

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

看这样一个效果图:

点击第一个按钮,切换到第二个界面,点击第二个按钮,切换到第三个界面,然后点击Back键依次回退。这像不像初学Android时的Activity跳转,当然了,这里肯定不是,不然我就跪了。这里是Fragment实现的,用户点击Back,实际是Fragment回退栈不断的弹栈。

如何添加一个Fragment事务到回退栈:

FragmentTransaction.addToBackStack(String)

下面讲解代码:很明显一共是3个Fragment和一个Activity.

先看Activity的布局文件:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent" >
  5. <FrameLayout
  6. android:id="@+id/id_content"
  7. android:layout_width="fill_parent"
  8. android:layout_height="fill_parent" >
  9. </FrameLayout>
  10. </RelativeLayout>

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

MainActivity.java

  1. package com.zhy.zhy_fragments;
  2. import android.app.Activity;
  3. import android.app.FragmentManager;
  4. import android.app.FragmentTransaction;
  5. import android.os.Bundle;
  6. import android.view.Window;
  7. public class MainActivity extends Activity
  8. {
  9. @Override
  10. protected void onCreate(Bundle savedInstanceState)
  11. {
  12. super.onCreate(savedInstanceState);
  13. requestWindowFeature(Window.FEATURE_NO_TITLE);
  14. setContentView(R.layout.activity_main);
  15. FragmentManager fm = getFragmentManager();
  16. FragmentTransaction tx = fm.beginTransaction();
  17. tx.add(R.id.id_content, new FragmentOne(),"ONE");
  18. tx.commit();
  19. }
  20. }

很简单,直接将FragmentOne添加到布局文件中的FrameLayout中,注意这里并没有调用FragmentTransaction.addToBackStack(String),因为我不喜欢在当前显示时,点击Back键出现白板。而是正确的相应Back键,即退出我们的Activity.

下面是FragmentOne

  1. package com.zhy.zhy_fragments;
  2. import android.app.Fragment;
  3. import android.app.FragmentManager;
  4. import android.app.FragmentTransaction;
  5. import android.os.Bundle;
  6. import android.view.LayoutInflater;
  7. import android.view.View;
  8. import android.view.View.OnClickListener;
  9. import android.view.ViewGroup;
  10. import android.widget.Button;
  11. public class FragmentOne extends Fragment implements OnClickListener
  12. {
  13. private Button mBtn;
  14. @Override
  15. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  16. Bundle savedInstanceState)
  17. {
  18. View view = inflater.inflate(R.layout.fragment_one, container, false);
  19. mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);
  20. mBtn.setOnClickListener(this);
  21. return view;
  22. }
  23. @Override
  24. public void onClick(View v)
  25. {
  26. FragmentTwo fTwo = new FragmentTwo();
  27. FragmentManager fm = getFragmentManager();
  28. FragmentTransaction tx = fm.beginTransaction();
  29. tx.replace(R.id.id_content, fTwo, "TWO");
  30. tx.addToBackStack(null);
  31. tx.commit();
  32. }
  33. }

我们在点击FragmentOne中的按钮时,使用了replace方法,如果你看了前一篇博客,一定记得replace是remove和add的合体,并且如果不添加事务到回退栈,前一个Fragment实例会被销毁。这里很明显,我们调用tx.addToBackStack(null);将当前的事务添加到了回退栈,所以FragmentOne实例不会被销毁,但是视图层次依然会被销毁,即会调用onDestoryView和onCreateView,证据就是:仔细看上面的效果图,我们在跳转前在文本框输入的内容,在用户Back得到第一个界面的时候不见了。

接下来FragmentTwo

  1. package com.zhy.zhy_fragments;
  2. import android.app.Fragment;
  3. import android.app.FragmentManager;
  4. import android.app.FragmentTransaction;
  5. import android.os.Bundle;
  6. import android.view.LayoutInflater;
  7. import android.view.View;
  8. import android.view.View.OnClickListener;
  9. import android.view.ViewGroup;
  10. import android.widget.Button;
  11. public class FragmentTwo extends Fragment implements OnClickListener
  12. {
  13. private Button mBtn ;
  14. @Override
  15. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  16. Bundle savedInstanceState)
  17. {
  18. View view = inflater.inflate(R.layout.fragment_two, container, false);
  19. mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);
  20. mBtn.setOnClickListener(this);
  21. return view ;
  22. }
  23. @Override
  24. public void onClick(View v)
  25. {
  26. FragmentThree fThree = new FragmentThree();
  27. FragmentManager fm = getFragmentManager();
  28. FragmentTransaction tx = fm.beginTransaction();
  29. tx.hide(this);
  30. tx.add(R.id.id_content , fThree, "THREE");
  31. //      tx.replace(R.id.id_content, fThree, "THREE");
  32. tx.addToBackStack(null);
  33. tx.commit();
  34. }
  35. }

这里点击时,我们没有使用replace,而是先隐藏了当前的Fragment,然后添加了FragmentThree的实例,最后将事务添加到回退栈。这样做的目的是为了给大家提供一种方案:如果不希望视图重绘该怎么做,请再次仔细看效果图,我们在FragmentTwo的EditText填写的内容,用户Back回来时,数据还在~~~

最后FragmentThree就是简单的Toast了:

  1. package com.zhy.zhy_fragments;
  2. import android.app.Fragment;
  3. import android.os.Bundle;
  4. import android.view.LayoutInflater;
  5. import android.view.View;
  6. import android.view.View.OnClickListener;
  7. import android.view.ViewGroup;
  8. import android.widget.Button;
  9. import android.widget.Toast;
  10. public class FragmentThree extends Fragment implements OnClickListener
  11. {
  12. private Button mBtn;
  13. @Override
  14. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  15. Bundle savedInstanceState)
  16. {
  17. View view = inflater.inflate(R.layout.fragment_three, container, false);
  18. mBtn = (Button) view.findViewById(R.id.id_fragment_three_btn);
  19. mBtn.setOnClickListener(this);
  20. return view;
  21. }
  22. @Override
  23. public void onClick(View v)
  24. {
  25. Toast.makeText(getActivity(), " i am a btn in Fragment three",
  26. Toast.LENGTH_SHORT).show();
  27. }
  28. }

好了,经过上面的介绍,应该已经知道Fragment回退栈是怎么一回事了,以及hide,replace等各自的应用的场景。

这里极其注意一点:上面的整体代码不具有任何参考价值,纯粹为了显示回退栈,在后面讲解了Fragment与Activity通信以后,会重构上面的代码!

2、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()。

3、Fragment与Activity通信的最佳实践

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

下面我通过两种方式的代码,分别重构,FragmentOne和FragmentTwo的点击事件,以及Activity对点击事件的响应:

首先看FragmentOne

  1. package com.zhy.zhy_fragments;
  2. import android.app.Fragment;
  3. import android.os.Bundle;
  4. import android.view.LayoutInflater;
  5. import android.view.View;
  6. import android.view.View.OnClickListener;
  7. import android.view.ViewGroup;
  8. import android.widget.Button;
  9. public class FragmentOne extends Fragment implements OnClickListener
  10. {
  11. private Button mBtn;
  12. /**
  13. * 设置按钮点击的回调
  14. * @author zhy
  15. *
  16. */
  17. public interface FOneBtnClickListener
  18. {
  19. void onFOneBtnClick();
  20. }
  21. @Override
  22. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  23. Bundle savedInstanceState)
  24. {
  25. View view = inflater.inflate(R.layout.fragment_one, container, false);
  26. mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);
  27. mBtn.setOnClickListener(this);
  28. return view;
  29. }
  30. /**
  31. * 交给宿主Activity处理,如果它希望处理
  32. */
  33. @Override
  34. public void onClick(View v)
  35. {
  36. if (getActivity() instanceof FOneBtnClickListener)
  37. {
  38. ((FOneBtnClickListener) getActivity()).onFOneBtnClick();
  39. }
  40. }
  41. }

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

再看FragmentTwo

  1. package com.zhy.zhy_fragments;
  2. import android.app.Fragment;
  3. import android.os.Bundle;
  4. import android.view.LayoutInflater;
  5. import android.view.View;
  6. import android.view.View.OnClickListener;
  7. import android.view.ViewGroup;
  8. import android.widget.Button;
  9. public class FragmentTwo extends Fragment implements OnClickListener
  10. {
  11. private Button mBtn ;
  12. private FTwoBtnClickListener fTwoBtnClickListener ;
  13. public interface FTwoBtnClickListener
  14. {
  15. void onFTwoBtnClick();
  16. }
  17. //设置回调接口
  18. public void setfTwoBtnClickListener(FTwoBtnClickListener fTwoBtnClickListener)
  19. {
  20. this.fTwoBtnClickListener = fTwoBtnClickListener;
  21. }
  22. @Override
  23. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  24. Bundle savedInstanceState)
  25. {
  26. View view = inflater.inflate(R.layout.fragment_two, container, false);
  27. mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);
  28. mBtn.setOnClickListener(this);
  29. return view ;
  30. }
  31. @Override
  32. public void onClick(View v)
  33. {
  34. if(fTwoBtnClickListener != null)
  35. {
  36. fTwoBtnClickListener.onFTwoBtnClick();
  37. }
  38. }
  39. }

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

最后看Activity :

  1. package com.zhy.zhy_fragments;
  2. import android.app.Activity;
  3. import android.app.FragmentManager;
  4. import android.app.FragmentTransaction;
  5. import android.os.Bundle;
  6. import android.view.Window;
  7. import com.zhy.zhy_fragments.FragmentOne.FOneBtnClickListener;
  8. import com.zhy.zhy_fragments.FragmentTwo.FTwoBtnClickListener;
  9. public class MainActivity extends Activity implements FOneBtnClickListener,
  10. FTwoBtnClickListener
  11. {
  12. private FragmentOne mFOne;
  13. private FragmentTwo mFTwo;
  14. private FragmentThree mFThree;
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState)
  17. {
  18. super.onCreate(savedInstanceState);
  19. requestWindowFeature(Window.FEATURE_NO_TITLE);
  20. setContentView(R.layout.activity_main);
  21. mFOne = new FragmentOne();
  22. FragmentManager fm = getFragmentManager();
  23. FragmentTransaction tx = fm.beginTransaction();
  24. tx.add(R.id.id_content, mFOne, "ONE");
  25. tx.commit();
  26. }
  27. /**
  28. * FragmentOne 按钮点击时的回调
  29. */
  30. @Override
  31. public void onFOneBtnClick()
  32. {
  33. if (mFTwo == null)
  34. {
  35. mFTwo = new FragmentTwo();
  36. mFTwo.setfTwoBtnClickListener(this);
  37. }
  38. FragmentManager fm = getFragmentManager();
  39. FragmentTransaction tx = fm.beginTransaction();
  40. tx.replace(R.id.id_content, mFTwo, "TWO");
  41. tx.addToBackStack(null);
  42. tx.commit();
  43. }
  44. /**
  45. * FragmentTwo 按钮点击时的回调
  46. */
  47. @Override
  48. public void onFTwoBtnClick()
  49. {
  50. if (mFThree == null)
  51. {
  52. mFThree = new FragmentThree();
  53. }
  54. FragmentManager fm = getFragmentManager();
  55. FragmentTransaction tx = fm.beginTransaction();
  56. tx.hide(mFTwo);
  57. tx.add(R.id.id_content, mFThree, "THREE");
  58. // tx.replace(R.id.id_content, fThree, "THREE");
  59. tx.addToBackStack(null);
  60. tx.commit();
  61. }
  62. }

代码重构结束,与开始的效果一模一样。上面两种通信方式都是值得推荐的,随便选择一种自己喜欢的。这里再提一下:虽然Fragment和Activity可以通过getActivity与findFragmentByTag或者findFragmentById,进行任何操作,甚至在Fragment里面操作另外的Fragment,但是没有特殊理由是绝对不提倡的。Activity担任的是Fragment间类似总线一样的角色,应当由它决定Fragment如何操作。另外虽然Fragment不能响应Intent打开,但是Activity可以,Activity可以接收Intent,然后根据参数判断显示哪个Fragment。

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

运行时配置发生变化,最常见的就是屏幕发生旋转,如果你不知道如何处理屏幕变化可以参考:Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的最佳方案

这里提一下:很多人觉得强制设置屏幕的方向就可以了,但是有一点,当你的应用被至于后台(例如用户点击了home),长时间没有返回的时候,你的应用也会被重新启动。比如上例:如果你把上面的例子你至于FragmentThree界面,然后处于后台状态,长时间后你会发现当你再次通过home打开时,上面FragmentThree与FragmentOne叠加在一起,这就是因为你的Activity重新启动,在原来的FragmentThree上又绘制了一个FragmentOne。

好了,下面看一段代码:

Activity:

  1. package com.zhy.zhy_fragments;
  2. import android.app.Activity;
  3. import android.app.FragmentManager;
  4. import android.app.FragmentTransaction;
  5. import android.os.Bundle;
  6. import android.view.Window;
  7. public class MainActivity extends Activity
  8. {
  9. private FragmentOne mFOne;
  10. @Override
  11. protected void onCreate(Bundle savedInstanceState)
  12. {
  13. super.onCreate(savedInstanceState);
  14. requestWindowFeature(Window.FEATURE_NO_TITLE);
  15. setContentView(R.layout.activity_main);
  16. mFOne = new FragmentOne();
  17. FragmentManager fm = getFragmentManager();
  18. FragmentTransaction tx = fm.beginTransaction();
  19. tx.add(R.id.id_content, mFOne, "ONE");
  20. tx.commit();
  21. }
  22. }

Fragment

  1. package com.zhy.zhy_fragments;
  2. import android.app.Fragment;
  3. import android.os.Bundle;
  4. import android.util.Log;
  5. import android.view.LayoutInflater;
  6. import android.view.View;
  7. import android.view.ViewGroup;
  8. public class FragmentOne extends Fragment
  9. {
  10. private static final String TAG = "FragmentOne";
  11. @Override
  12. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  13. Bundle savedInstanceState)
  14. {
  15. Log.e(TAG, "onCreateView");
  16. View view = inflater.inflate(R.layout.fragment_one, container, false);
  17. return view;
  18. }
  19. @Override
  20. public void onCreate(Bundle savedInstanceState)
  21. {
  22. // TODO Auto-generated method stub
  23. super.onCreate(savedInstanceState);
  24. Log.e(TAG, "onCreate");
  25. }
  26. @Override
  27. public void onDestroyView()
  28. {
  29. // TODO Auto-generated method stub
  30. super.onDestroyView();
  31. Log.e(TAG, "onDestroyView");
  32. }
  33. @Override
  34. public void onDestroy()
  35. {
  36. // TODO Auto-generated method stub
  37. super.onDestroy();
  38. Log.e(TAG, "onDestroy");
  39. }
  40. }

很简单的代码,当你运行之后,不断的旋转屏幕,你会发现每旋转一次屏幕,屏幕上就多了一个FragmentOne的实例,并且后台log会打印出许多套生命周期的回调。

类似:

  1. 07-20 08:18:46.651: E/FragmentOne(1633): onCreate
  2. 07-20 08:18:46.651: E/FragmentOne(1633): onCreate
  3. 07-20 08:18:46.651: E/FragmentOne(1633): onCreate
  4. 07-20 08:18:46.681: E/FragmentOne(1633): onCreateView
  5. 07-20 08:18:46.831: E/FragmentOne(1633): onCreateView
  6. 07-20 08:18:46.891: E/FragmentOne(1633): onCreateView

这是为什么呢,因为当屏幕发生旋转,Activity发生重新启动,默认的Activity中的Fragment也会跟着Activity重新创建;这样造成当旋转的时候,本身存在的Fragment会重新启动,然后当执行Activity的onCreate时,又会再次实例化一个新的Fragment,这就是出现的原因。

那么如何解决呢:

其实通过检查onCreate的参数Bundle savedInstanceState就可以判断,当前是否发生Activity的重新创建:

默认的savedInstanceState会存储一些数据,包括Fragment的实例:通过打印可以看出:

  1. 07-20 08:23:12.952: E/FragmentOne(1782): Bundle[{android:fragments=android.app.FragmentManagerState@40d0b7b8, android:viewHierarchyState=Bundle[{android:focusedViewId=2131230721, android:views=android.util.SparseArray@40d0af68}]}]

所以,我们简单改一下代码,只有在savedInstanceState==null时,才进行创建Fragment实例:

  1. package com.zhy.zhy_fragments;
  2. import android.app.Activity;
  3. import android.app.FragmentManager;
  4. import android.app.FragmentTransaction;
  5. import android.os.Bundle;
  6. import android.util.Log;
  7. import android.view.Window;
  8. public class MainActivity extends Activity
  9. {
  10. private static final String TAG = "FragmentOne";
  11. private FragmentOne mFOne;
  12. @Override
  13. protected void onCreate(Bundle savedInstanceState)
  14. {
  15. super.onCreate(savedInstanceState);
  16. requestWindowFeature(Window.FEATURE_NO_TITLE);
  17. setContentView(R.layout.activity_main);
  18. Log.e(TAG, savedInstanceState+"");
  19. if(savedInstanceState == null)
  20. {
  21. mFOne = new FragmentOne();
  22. FragmentManager fm = getFragmentManager();
  23. FragmentTransaction tx = fm.beginTransaction();
  24. tx.add(R.id.id_content, mFOne, "ONE");
  25. tx.commit();
  26. }
  27. }
  28. }

现在无论进行多次旋转都只会有一个Fragment实例在Activity中。

现在还存在一个问题,就是重新绘制时,Fragment发生重建,原本的数据如何保持?

其实和Activity类似,Fragment也有onSaveInstanceState的方法,在此方法中进行保存数据,然后在onCreate或者onCreateView或者onActivityCreated进行恢复都可以。

由于篇幅原因,就不贴测试代码了。

5、Fragmeny与ActionBar和MenuItem集成

Fragment可以添加自己的MenuItem到Activity的ActionBar或者可选菜单中。

a、在Fragment的onCreate中调用 setHasOptionsMenu(true);

b、然后在Fragment子类中实现onCreateOptionsMenu

c、如果希望在Fragment中处理MenuItem的点击,也可以实现onOptionsItemSelected;当然了Activity也可以直接处理该MenuItem的点击事件。

代码:

Fragment

  1. package com.zhy.zhy_fragments;
  2. import android.app.Fragment;
  3. import android.os.Bundle;
  4. import android.view.LayoutInflater;
  5. import android.view.Menu;
  6. import android.view.MenuInflater;
  7. import android.view.MenuItem;
  8. import android.view.View;
  9. import android.view.ViewGroup;
  10. import android.widget.Toast;
  11. public class FragmentOne extends Fragment
  12. {
  13. @Override
  14. public void onCreate(Bundle savedInstanceState)
  15. {
  16. super.onCreate(savedInstanceState);
  17. setHasOptionsMenu(true);
  18. }
  19. @Override
  20. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  21. Bundle savedInstanceState)
  22. {
  23. View view = inflater.inflate(R.layout.fragment_one, container, false);
  24. return view;
  25. }
  26. @Override
  27. public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
  28. {
  29. inflater.inflate(R.menu.fragment_menu, menu);
  30. }
  31. @Override
  32. public boolean onOptionsItemSelected(MenuItem item)
  33. {
  34. switch (item.getItemId())
  35. {
  36. case R.id.id_menu_fra_test:
  37. Toast.makeText(getActivity(), "FragmentMenuItem1", Toast.LENGTH_SHORT).show();
  38. break;
  39. }
  40. return true;
  41. }
  42. }

Activity

  1. package com.zhy.zhy_fragments;
  2. import android.app.Activity;
  3. import android.app.FragmentManager;
  4. import android.app.FragmentTransaction;
  5. import android.os.Bundle;
  6. import android.util.Log;
  7. import android.view.Menu;
  8. import android.view.MenuItem;
  9. import android.view.Window;
  10. import android.widget.Toast;
  11. public class MainActivity extends Activity
  12. {
  13. private static final String TAG = "FragmentOne";
  14. private FragmentOne mFOne;
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState)
  17. {
  18. super.onCreate(savedInstanceState);
  19. requestWindowFeature(Window.FEATURE_NO_TITLE);
  20. setContentView(R.layout.activity_main);
  21. Log.e(TAG, savedInstanceState + "");
  22. if (savedInstanceState == null)
  23. {
  24. mFOne = new FragmentOne();
  25. FragmentManager fm = getFragmentManager();
  26. FragmentTransaction tx = fm.beginTransaction();
  27. tx.add(R.id.id_content, mFOne, "ONE");
  28. tx.commit();
  29. }
  30. }
  31. @Override
  32. public boolean onCreateOptionsMenu(Menu menu)
  33. {
  34. super.onCreateOptionsMenu(menu);
  35. getMenuInflater().inflate(R.menu.main, menu);
  36. return true;
  37. }
  38. @Override
  39. public boolean onOptionsItemSelected(MenuItem item)
  40. {
  41. switch (item.getItemId())
  42. {
  43. case R.id.action_settings:
  44. Toast.makeText(this, "setting", Toast.LENGTH_SHORT).show();
  45. return true;
  46. default:
  47. //如果希望Fragment自己处理MenuItem点击事件,一定不要忘了调用super.xxx
  48. return super.onOptionsItemSelected(item);
  49. }
  50. }
  51. }

效果图:

好了,可以很好的看到,Fragment可以添加MenuItem,也可以自己处理点击~~~

6、没有布局的Fragment的作用

没有布局文件Fragment实际上是为了保存,当Activity重启时,保存大量数据准备的

请参考博客:Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的最佳方案

7、使用Fragment创建对话框

这是Google推荐的方式,我也单独写过博客介绍,请参考:Android 官方推荐 : DialogFragment 创建对话框

好了,终于把Fragment相关的联系到一起了,上述基本包含了Fragment所有的用法~~~相信大家如果能够看完,一定有不少的收获~~~

两篇结束,相信你对Fragment已经有了一定的了解,那么在项目中的最佳实践是什么呢?

之前的博客属于怎么使用Fragment,本文目标教你如何用好Fragment,即Fragment的一些使用的建议,(多数内容来自:Android programming the big nerd ranch guide 一书,直接百度,你懂的,虽然是基础书籍,还是很值得一看的)。

1、概述

首先我们简单回顾一下,相信大家对Fragment的都不陌生,对于Fragment的使用,一方面Activity需要在布局中为Fragment安排位置,另一方面需要管理好Fragment的生命周期。Activity中有个FragmentManager,其内部维护fragment队列,以及fragment事务的回退栈。

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

  1. public class MainActivity extends FragmentActivity
  2. {
  3. private ContentFragment mContentFragment  ;
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState)
  6. {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_main);
  9. FragmentManager fm = getSupportFragmentManager();
  10. mContentFragment = (ContentFragment) fm.findFragmentById(R.id.id_fragment_container);
  11. if(mContentFragment == null )
  12. {
  13. mContentFragment = new ContentFragment();
  14. fm.beginTransaction().add(R.id.id_fragment_container,mContentFragment).commit();
  15. }
  16. }
  17. }

针对上面代码,问两个问题:

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

下面描述一个简单的场景,比如我们某个按钮触发Activity跳转,需要通过Intent传递参数到目标Activity的Fragment中,那么此Fragment如何获取当前的Intent的值呢?

有哥们会说,这个简单?看我的代码(问题代码):

  1. public class ContentFragment extends Fragment
  2. {
  3. private String mArgument ;
  4. public static final String ARGUMENT ="argument";
  5. @Override
  6. public void onCreate(Bundle savedInstanceState)
  7. {
  8. super.onCreate(savedInstanceState);
  9. mArgument = getActivity().getIntent().getStringExtra(ARGUMENT);
  10. }

我们直接在Fragment的onCreate中,拿到宿主Activty,宿主Activity中肯定能通过getIntent拿到Intent,然后通过get方法,随意拿参数~~

这么写,功能上是实现了,但是呢?存在一个大问题:我们用Fragment的一个很大的原因,就是为了复用。你这么写,相当于这个Fragment已经完全和当前这个宿主Activity绑定了,复用直接废了~~~所以呢?我们换种方式,推荐使用arguments来创建Fragment。

  1. public class ContentFragment extends Fragment
  2. {
  3. private String mArgument;
  4. public static final String ARGUMENT = "argument";
  5. @Override
  6. public void onCreate(Bundle savedInstanceState)
  7. {
  8. super.onCreate(savedInstanceState);
  9. // mArgument = getActivity().getIntent().getStringExtra(ARGUMENT);
  10. Bundle bundle = getArguments();
  11. if (bundle != null)
  12. mArgument = bundle.getString(ARGUMENT);
  13. }
  14. /**
  15. * 传入需要的参数,设置给arguments
  16. * @param argument
  17. * @return
  18. */
  19. public static ContentFragment newInstance(String argument)
  20. {
  21. Bundle bundle = new Bundle();
  22. bundle.putString(ARGUMENT, argument);
  23. ContentFragment contentFragment = new ContentFragment();
  24. contentFragment.setArguments(bundle);
  25. return contentFragment;
  26. }

给Fragment添加newInstance方法,将需要的参数传入,设置到bundle中,然后setArguments(bundle),最后在onCreate中进行获取;

这样就完成了Fragment和Activity间的解耦。当然了这里需要注意:

setArguments方法必须在fragment创建以后,添加给Activity前完成。千万不要,首先调用了add,然后设置arguments。

3、Fragment的startActivityForResult

依旧是一个简单的场景:两个Fragment,一个展示文章列表的Fragment(叫做ListTitleFragment),一个显示详细信息的Fragment(叫做:ContentFragment),当然了,这两个Fragment都有其宿主Activity。

现在,我们点击列表Fragment中的列表项,传入相应的参数,去详细信息的Fragment展示详细的信息,在详细信息页面,用户可以进行点评,当用户点击back以后,我们以往点评结果显示在列表的Fragment对于的列表项中;

也就是说,我们点击跳转到对应Activity的Fragment中,并且希望它能够返回参数,那么我们肯定是使用Fragment.startActivityForResult ;

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

详细代码:

ListTitleFragment

  1. public class ListTitleFragment extends ListFragment
  2. {
  3. public static final int REQUEST_DETAIL = 0x110;
  4. private List<String> mTitles = Arrays.asList("Hello", "World", "Android");
  5. private int mCurrentPos ;
  6. private ArrayAdapter<String> mAdapter ;
  7. @Override
  8. public void onActivityCreated(Bundle savedInstanceState)
  9. {
  10. super.onActivityCreated(savedInstanceState);
  11. setListAdapter(mAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mTitles));
  12. }
  13. @Override
  14. public void onListItemClick(ListView l, View v, int position, long id)
  15. {
  16. mCurrentPos = position ;
  17. Intent intent = new Intent(getActivity(),ContentActivity.class);
  18. intent.putExtra(ContentFragment.ARGUMENT, mTitles.get(position));
  19. startActivityForResult(intent, REQUEST_DETAIL);
  20. }
  21. @Override
  22. public void onActivityResult(int requestCode, int resultCode, Intent data)
  23. {
  24. Log.e("TAG", "onActivityResult");
  25. super.onActivityResult(requestCode, resultCode, data);
  26. if(requestCode == REQUEST_DETAIL)
  27. {
  28. mTitles.set(mCurrentPos, mTitles.get(mCurrentPos)+" -- "+data.getStringExtra(ContentFragment.RESPONSE));
  29. mAdapter.notifyDataSetChanged();
  30. }
  31. }
  32. }

ContentFragment

  1. public class ContentFragment extends Fragment
  2. {
  3. private String mArgument;
  4. public static final String ARGUMENT = "argument";
  5. public static final String RESPONSE = "response";
  6. @Override
  7. public void onCreate(Bundle savedInstanceState)
  8. {
  9. super.onCreate(savedInstanceState);
  10. Bundle bundle = getArguments();
  11. if (bundle != null)
  12. {
  13. mArgument = bundle.getString(ARGUMENT);
  14. Intent intent = new Intent();
  15. intent.putExtra(RESPONSE, "good");
  16. getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);
  17. }
  18. }
  19. public static ContentFragment newInstance(String argument)
  20. {
  21. Bundle bundle = new Bundle();
  22. bundle.putString(ARGUMENT, argument);
  23. ContentFragment contentFragment = new ContentFragment();
  24. contentFragment.setArguments(bundle);
  25. return contentFragment;
  26. }
  27. @Override
  28. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  29. Bundle savedInstanceState)
  30. {
  31. Random random = new Random();
  32. TextView tv = new TextView(getActivity());
  33. tv.setText(mArgument);
  34. tv.setGravity(Gravity.CENTER);
  35. tv.setBackgroundColor(Color.argb(random.nextInt(100),
  36. random.nextInt(255), random.nextInt(255), random.nextInt(255)));
  37. return tv;
  38. }
  39. }

贴出了两个Fragment的代码,可以看到我们在ListTitleFragment.onListItemClick,使用startActivityForResult()跳转到目标Activity,在目标Activity的Fragment(ContentFragment)中获取参数,然后调用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);进行设置返回的数据;最后在ListTitleFragment.onActivityResult()拿到返回的数据进行回显;

为大家以后在遇到类似问题时,提供了解决方案;也说明了一个问题:fragment能够从Activity中接收返回结果,但是其自设无法产生返回结果,只有Activity拥有返回结果。

接下来我要贴一下,这两个Fragment的宿主Activity:

ListTitleActivity

  1. public class ListTitleActivity extends FragmentActivity
  2. {
  3. private ListTitleFragment mListFragment;
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState)
  6. {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_single_fragment);
  9. FragmentManager fm = getSupportFragmentManager();
  10. mListFragment = (ListTitleFragment) fm.findFragmentById(R.id.id_fragment_container);
  11. if(mListFragment == null )
  12. {
  13. mListFragment = new ListTitleFragment();
  14. fm.beginTransaction().add(R.id.id_fragment_container,mListFragment).commit();
  15. }
  16. }
  17. }

ContentActivity:

  1. public class ContentActivity extends FragmentActivity
  2. {
  3. private ContentFragment mContentFragment;
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState)
  6. {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_single_fragment);
  9. FragmentManager fm = getSupportFragmentManager();
  10. mContentFragment = (ContentFragment) fm.findFragmentById(R.id.id_fragment_container);
  11. if(mContentFragment == null )
  12. {
  13. String title = getIntent().getStringExtra(ContentFragment.ARGUMENT);
  14. mContentFragment = ContentFragment.newInstance(title);
  15. fm.beginTransaction().add(R.id.id_fragment_container,mContentFragment).commit();
  16. }
  17. }
  18. }

有没有发现两个Activity中的代码极其的类似,且使用了同一个布局文件:

activity_single_fragment.xml

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:id="@+id/id_fragment_container"
  6. >
  7. </RelativeLayout>

为什么要贴这Acticity的代码呢?因为我们项目中,如果原则上使用Fragment,会发现大量类似的代码,那么我们就可以抽象一个Activity出来,托管我们的Single Fragment。

详细见下一节。

4、SingleFragmentActivity

于是抽象出来的Activity的代码为:

  1. package com.example.demo_zhy_23_fragments;
  2. import android.os.Bundle;
  3. import android.support.v4.app.Fragment;
  4. import android.support.v4.app.FragmentActivity;
  5. import android.support.v4.app.FragmentManager;
  6. public abstract class SingleFragmentActivity extends FragmentActivity
  7. {
  8. protected abstract Fragment createFragment();
  9. @Override
  10. protected void onCreate(Bundle savedInstanceState)
  11. {
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.activity_single_fragment);
  14. FragmentManager fm = getSupportFragmentManager();
  15. Fragment fragment =fm.findFragmentById(R.id.id_fragment_container);
  16. if(fragment == null )
  17. {
  18. fragment = createFragment() ;
  19. fm.beginTransaction().add(R.id.id_fragment_container,fragment).commit();
  20. }
  21. }
  22. }

那么,有了这个SingleFragmentActivity,我们的ContentActivity和ListTitleActivity也能大变身了~

  1. package com.example.demo_zhy_23_fragments;
  2. import android.support.v4.app.Fragment;
  3. public class ContentActivity extends SingleFragmentActivity
  4. {
  5. private ContentFragment mContentFragment;
  6. @Override
  7. protected Fragment createFragment()
  8. {
  9. String title = getIntent().getStringExtra(ContentFragment.ARGUMENT);
  10. mContentFragment = ContentFragment.newInstance(title);
  11. return mContentFragment;
  12. }
  13. }
  1. package com.example.demo_zhy_23_fragments;
  2. import android.support.v4.app.Fragment;
  3. public class ListTitleActivity extends SingleFragmentActivity
  4. {
  5. private ListTitleFragment mListFragment;
  6. @Override
  7. protected Fragment createFragment()
  8. {
  9. mListFragment = new ListTitleFragment();
  10. return mListFragment;
  11. }
  12. }

是不是简洁很多,相信优先使用Fragment的项目,类似的Activity非常多,使用SingleFragmentActivity来简化你的代码吧~~

好了,此代码是来自文章开始推荐的书中的,再次推荐一下~~。

5、FragmentPagerAdapter与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。

篇幅原因,具体的案例就不写了,大家自行测试

6、Fragment间的数据传递

上面3中,我们展示了,一般的两个Fragment间的数据传递。

那么还有一种比较特殊的情况,就是两个Fragment在同一个Activity中:例如,点击当前Fragment中按钮,弹出一个对话框(DialogFragment),在对话框中的操作需要返回给触发的Fragment中,那么如何数据传递呢?对于对话框的使用推荐:Android 官方推荐 : DialogFragment 创建对话框

我们继续修改我们的代码:现在是ListTitleFragment , ContentFragment , 添加一个对话框:EvaluateDialog,用户点击ContentFragment 内容时弹出一个评价列表,用户选择评价。

现在我们的关注点在于:ContentFragment中如何优雅的拿到EvaluateDialog中返回的评价:

记住我们在一个Activity中,那么肯定不是使用startActivityForResult;但是我们返回的数据,依然在onActivityResult中进行接收。

好了看代码:

ContentFragment

  1. public class ContentFragment extends Fragment
  2. {
  3. private String mArgument;
  4. public static final String ARGUMENT = "argument";
  5. public static final String RESPONSE = "response";
  6. public static final String EVALUATE_DIALOG = "evaluate_dialog";
  7. public static final int REQUEST_EVALUATE = 0X110;
  8. //...
  9. @Override
  10. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  11. Bundle savedInstanceState)
  12. {
  13. Random random = new Random();
  14. TextView tv = new TextView(getActivity());
  15. ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
  16. LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
  17. tv.setLayoutParams(params);
  18. tv.setText(mArgument);
  19. tv.setGravity(Gravity.CENTER);
  20. tv.setBackgroundColor(Color.argb(random.nextInt(100),
  21. random.nextInt(255), random.nextInt(255), random.nextInt(255)));
  22. // set click
  23. tv.setOnClickListener(new OnClickListener()
  24. {
  25. @Override
  26. public void onClick(View v)
  27. {
  28. EvaluateDialog dialog = new EvaluateDialog();
  29. //注意setTargetFragment
  30. dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE);
  31. dialog.show(getFragmentManager(), EVALUATE_DIALOG);
  32. }
  33. });
  34. return tv;
  35. }
  36. //接收返回回来的数据
  37. @Override
  38. public void onActivityResult(int requestCode, int resultCode, Intent data)
  39. {
  40. super.onActivityResult(requestCode, resultCode, data);
  41. if (requestCode == REQUEST_EVALUATE)
  42. {
  43. String evaluate = data
  44. .getStringExtra(EvaluateDialog.RESPONSE_EVALUATE);
  45. Toast.makeText(getActivity(), evaluate, Toast.LENGTH_SHORT).show();
  46. Intent intent = new Intent();
  47. intent.putExtra(RESPONSE, evaluate);
  48. getActivity().setResult(Activity.REQUEST_OK, intent);
  49. }
  50. }
  51. }

删除了一些无关代码,注意看,我们在onCreateView中为textview添加了click事件,用于弹出我们的dialog,注意一行代码:

dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE);

我们调用了Fragment.setTargetFragment ,这个方法,一般就是用于当前fragment由别的fragment启动,在完成操作后返回数据的,符合我们的需求吧~~~注意,这句很重要。

接下来看EvaluateDialog代码:

  1. package com.example.demo_zhy_23_fragments;
  2. import android.app.Activity;
  3. import android.app.AlertDialog;
  4. import android.app.Dialog;
  5. import android.content.DialogInterface;
  6. import android.content.DialogInterface.OnClickListener;
  7. import android.content.Intent;
  8. import android.os.Bundle;
  9. import android.support.v4.app.DialogFragment;
  10. public class EvaluateDialog extends DialogFragment
  11. {
  12. private String[] mEvaluteVals = new String[] { "GOOD", "BAD", "NORMAL" };
  13. public static final String RESPONSE_EVALUATE = "response_evaluate";
  14. @Override
  15. public Dialog onCreateDialog(Bundle savedInstanceState)
  16. {
  17. AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
  18. builder.setTitle("Evaluate :").setItems(mEvaluteVals,
  19. new OnClickListener()
  20. {
  21. @Override
  22. public void onClick(DialogInterface dialog, int which)
  23. {
  24. setResult(which);
  25. }
  26. });
  27. return builder.create();
  28. }
  29. // 设置返回数据
  30. protected void setResult(int which)
  31. {
  32. // 判断是否设置了targetFragment
  33. if (getTargetFragment() == null)
  34. return;
  35. Intent intent = new Intent();
  36. intent.putExtra(RESPONSE_EVALUATE, mEvaluteVals[which]);
  37. getTargetFragment().onActivityResult(ContentFragment.REQUEST_EVALUATE,
  38. Activity.RESULT_OK, intent);
  39. }
  40. }

重点就是看点击后的setResult了,我们首先判断是否设置了targetFragment,如果设置了,意味我们要返回一些数据到targetFragment。

我们创建intent封装好需要传递数据,最后手动调用onActivityResult进行返回数据~~

最后我们在ContentFragment的onActivityResult接收即可。

ok,终于把这些tips贯穿到一起了,到此我们的Fragment的一些建议的用法就结束了~~~那么,最后提供下源码,也顺便贴个效果图:

源码点击下载

Android Fragment 真正的完全解析的更多相关文章

  1. 【Android自学日记】【转】Android Fragment 真正的完全解析(下)

    上篇博客中已经介绍了Fragment产生原因,以及一些基本的用法和各种API,如果你还不了解,请看:Android Fragment 真正的完全解析(上). 本篇将介绍上篇博客提到的:如何管理Frag ...

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

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37992017 上篇博客中已经介绍了Fragment产生原因,以及一些基本的用法和 ...

  3. Android Fragment 真正的完全解析(下)---转

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37992017 上篇博客中已经介绍了Fragment产生原因,以及一些基本的用法和 ...

  4. Android Fragment 真正的完全解析(上)--转

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37970961 自从Fragment出现,曾经有段时间,感觉大家谈什么都能跟Fra ...

  5. Android——Fragment 真正的完全解析(下)(转)

    原文地址:http://blog.csdn.net/lmj623565791/article/details/37992017 上篇博客中已经介绍了Fragment产生原因,以及一些基本的用法和各种A ...

  6. Android—— Fragment 真正的完全解析(上)(转)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37970961 自从Fragment出现,曾经有段时间,感觉大家谈什么都能跟Fra ...

  7. 【转】 Android Fragment 真正的完全解析(下)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37992017 上篇博客中已经介绍了Fragment产生原因,以及一些基本的用法和 ...

  8. 【转】Android Fragment 真正的完全解析(上)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37970961 自从Fragment出现,曾经有段时间,感觉大家谈什么都能跟Fra ...

  9. 【Android自学日记】【转】Android Fragment 真正的完全解析(上)

    自从Fragment出现,曾经有段时间,感觉大家谈什么都能跟Fragment谈上关系,做什么都要问下Fragment能实现不~~~哈哈,是不是有点过~~~ 本篇博客力求为大家说明Fragment如何产 ...

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

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37970961 自从Fragment出现,曾经有段时间,感觉大家谈什么都能跟Fra ...

随机推荐

  1. 用$("...").attr("checked", true)设置勾选无效的原因

    如下图所示,本来想要实现如下图所示的功能,于是我本来是使用$("...").attr("checked", true/false)来实现该功能,但是第一次点击时 ...

  2. 通过VS2010性能分析来查找代码中那些地方最损耗资源

    在编写完成一个程序后,大家都比较关心程序的性能如何,想把程序优化得更好.很多时候凭个人直觉来优化程序是件非常不靠普的事情,即使你是一个优秀的开人员也很难准确地判断程序中那些出现问题.VS2010提供了 ...

  3. What is a Microsoft Failover Cluster Virtual Adapter

    What is a Microsoft Failover Cluster Virtual Adapter anyway? A question often asked is, "What i ...

  4. git上导出的maven项目转成正常的Dynamic web project

    项目地址:http://git.oschina.net/jiafuwei0407/springmvc,springmvc maven项目 包含日志的测试 restful api 如何在eclipse ...

  5. JAVA下几个问题

    一.Server MyEclipse Tomcat v8.5 was unable to start within 45 seconds. If the server requires more ti ...

  6. 最全面的Android Webview详解

    转自:最全面的Android Webview详解 前言 现在很多App里都内置了Web网页(Hyprid App),比如说很多电商平台,淘宝.京东.聚划算等等,如下图  那么这种该如何实现呢?其实这是 ...

  7. Fiddler抓包4-工具介绍(request和response)【转载】

    本篇转自博客:上海-悠悠 原文地址:http://www.cnblogs.com/yoyoketang/p/6719717.html 前言 本篇简单的介绍下fiddler界面的几块区域,以及各自区域到 ...

  8. 基于django rest framework的mock server实践

    网上找了一下mock server的实现,发现python的基本都是基于flask来实现的,因最近在学django,就尝试用drf实现了下: A brief introduction of sui_m ...

  9. POJ 2549:Subsets(哈希表)

    [题目链接] http://poj.org/problem?id=2549 [题目大意] 给出一个数集,从中选择四个元素,使得a+b+c=d,最小化d [题解] 我们对a+b建立Hash_table, ...

  10. Visio中插入公式

    也可以直接插入MathType公式,我原来的word不显示MathType,现在显示了,不过插入后太小,那是因为字体小,拖动托大就行. 在word中插入-对象-公式3.0对象(安装MathType后, ...