Fragment

作者: cxm11 | 来源:发表于2016-04-16 00:50 被阅读829次

    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解析

    相关文章

      网友评论

      本文标题:Fragment

      本文链接:https://www.haomeiwen.com/subject/dqunlttx.html