zoukankan      html  css  js  c++  java
  • Fragment的详细使用

    一直在用Fragment,但是没有系统的整理过,Google了一下相关文章,看到了几篇,将几篇还不错的文章重点整理了下,很多是直接Copy的,只为做个笔记,以后翻来看比较方便,建议大家看一下下面几篇,相信会有一些收获的。

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

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

    3、Android Fragment 你应该知道的一切

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

    Fragment出现的缘由

    不同大小屏幕的手机、平板、以及TV要展示统一的界面,但是由于屏幕大小不同,布局展示上就会有差异,但是又不想写多套布局,Fragment便诞生了。比如在手机上,先展示列表内容,点击后,再进入详情,而在平板或者电视上,因为屏幕足够大,那么就要充分利用屏幕,可以左侧展示列表,右侧实时的展示详情。比如Activity上面提到的列表和详情,手机和平板上的详情直接加载一个Fragment就可以了,不要再为平板单独写一套代码,达到了代码复用的效果。

    生命周期

    关于Activity与Fragment的完整声明周期以及交互,你可以在android-lifecycle 中看到,由于图片太大,这里就不展示了,建议大家都看一下。

    生命周期方法介绍

    因为Fragment是依赖与Activity的。所以会有onAttach,和onDetach,onActivityCreate.这样的和Activity相关的方法。另外Fragment也有自己的创建和销毁视图的方法:onCreateView和onDestoryView。

    • Create阶段 
      onAttach,onCreate,onCreateView,onActivityCreated

    • Destory阶段 
      onDestroyView,onDestory,onDettach.

    Create的时候多了一个onActivityCreated的回调,Activity创建完成的回调。

    生命周期方法说明
    onAttached() 当fragment被加入到activity时调用(在这个方法中可以获得所在的activity)
    onCreateView() 当activity要得到fragment的layout时,调用此方法,fragment在其中创建自己的layout(界面)
    onActivityCreated() 当activity的onCreated()方法返回后调用此方法
    onDestroyView() 当fragment中的视图被移除的时候,调用这个方法
    onDetach() 当fragment和activity分离的时候,调用这个方法

      
    一旦activity进入resumed状态(也就是running状态),fragment的生命周期才能独立的运转,才可以自由地添加和删除fragment了。其它时候是依赖于activity的生命周期变化的

    创建Fragment的两种方式

    1、XML布局中使用标签

    <?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>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    不能移除,而且这种方式在Activity创建的时候,就被创建出来了,灵活性差,仅仅作为简单的视图展示可以。

    2、代码动态添加

    FragmentManager manager = getFragmentManager()
    FragmentTransaction transaction = manager.beginTransaction();
    ExampleFragment fragment = new ExampleFragment();
    transaction.add(R.id.fragment_container, fragment);
    transaction.commit();
    • 1
    • 2
    • 3
    • 4
    • 5

    可以实现不同Fragment之间的切换:FragmentManager实现activity运行时对fragment进行添加,移除,和替换。推荐的方式,可控性高,方便处理不同屏幕的适配。但布局中必须有一个视图容器去存放fragment。

    FragmentTransaction的方法

    FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务
    transaction.add();//往Activity中添加一个Fragment
    transaction.remove()
    • 1
    • 2
    • 3

    从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁。

    方法说明
    replace() 使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体
    add(Fragment,String) 添加一个没有ui的fragment,该fragment只能通过FragmentManager. findFragmentByTag()获取
    add(int,Fragment) 添加一个fragment,参数1:fragment的id
    add(int,Fragment,String) 添加一个fragment,参数1:fragment的id,参数3:fragment的tag
    remove() 从Activity中移除一个Fragment
    hide() 隐藏当前的Fragment,仅仅是设为不可见,并不会销毁
    show() 显示之前隐藏的Fragment
    detach() 将此Fragment从Activity中分离,会销毁其布局,但不会销毁该实例
    attach() 将从Activity中分离的Fragment,重新关联到该Activity,重新创建其视图层次
    addToBackStack() 添加事务到回退栈
    commit() 提交一个事务,在一个事务开启到提交可以进行多个的添加、移除、替换等操作

    正确使用API

    • 当切换到Fragment时,如果希望切回来后数据和面板状态仍然存在,可以使用hide和show,当然了不要使劲在那new实例,进行下非null判断。
    • 不希望保留用户操作,你可以使用remove(),然后add();或者使用replace()这个和remove,add是相同的效果
    • remove和detach有一点细微的区别,在不考虑回退栈的情况下,remove会销毁整个Fragment实例,而detach则只是销毁其视图结构,实例并不会被销毁。那么二者怎么取舍使用呢?如果你的当前Activity一直存在,那么在不希望保留用户操作的时候,你可以优先使用detach

    管理Fragment回退栈

    通过FragmentManager对象和FragmentTransaction来进行事物管理。你也可以将Fragment压入堆栈,这样按返回键的时候可以回退到上一个Fragment。

    调用add()方法即可添加一个fragment。 你可以对activity使用同一个FragmentTransaction对象去执行多个fragment事务,当你确定要做这些操作时,你必须调用commint()方法。

    通过replace方法来替换一个Fragment。如果你希望让用户可以通过fragment事务“后退”,需要在提交fragment事务之前调用 addToBackStack()方法。

    注意:

    如果当你移除或者替换fragment时将事务添加到堆栈中(允许用户撤销或后退),那么被移除的fragment就被停止了(没有消亡,但视图已经销毁了),如果用户导航回来重新加载这个fragment,它将会重新启动,视图也会重新创建,如果你没有把事务加入到堆栈中,当fragment被删除或者替换时,这个fragment也就消亡了。

    调用tx.addToBackStack(null);将当前的事务添加到了回退栈,所以Fragment实例不会被销毁,但是视图层次依然会被销毁,即会调用onDestoryView和onCreateView.

    replace是remove和add的合体,如果使用了replace并且没有将之前的Fragment添加到回退栈,那么该Fragment实例会被销毁。

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

    Fragment与Activity的交互

    Activity直接影响它所包含的fragment的生命周期,所以对activity的某个生命周期方法的调用也会产生对fragment相同方法的调用。

    例如:当activity的onPause()方法被调用时,它所包含的所有的fragment们的onPause()方法都会被调用

    为了重用Fragment UI组件,你应该将Fragment建立成完全独立。一旦你定义了这些可重用的Fragment, 你可以通过activity,使它们关联以及交互,Fragment之间的交互也应该通过Activity统一管理,各个Fragment之间应该保持独立。

    Activity 与 Fragment交互的实现方式

    • 如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法

    • 如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作

    • 在Fragment中定义回调接口listener,由Activity实现,然后在Fragment的onAttach获取到该Activity强转为listener,之后进行一些操作。这种方式是一种比较官方的做法,Android官方文档的举例就是这样的方式,而且在AndroidStudio中创建一个FragmentActivity的时候,会为你写好这些回调,也说明官方推荐这种方式

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

    当屏幕旋转或者当你的应用被至于后台(例如用户点击了home),长时间没有返回的时候,你的应用也会被重新启动。这样会导致Fragment的重复创建,导致Fragment重叠的问题。

    解决Fragment的重复创建

    在Activity的onCreate方法中对saveInstanceState进行判断,如果saveInstaceState==null时,再进行Fragment创建。现在无论进行多次旋转都只会有一个Fragment实例在Activity中。

    if(savedInstanceState == null)
        {
            mFOne = new FragmentOne();
            FragmentManager fm = getFragmentManager();
            FragmentTransaction tx = fm.beginTransaction();
            tx.add(R.id.id_content, mFOne, “ONE”);
            tx.commit();
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    解决Fragment重叠的问题

    https://typeblog.net/tech/2014/08/22/fix-duplicate-menu.html 
    在Activity中复写onSaveIndstanceState方法然后注释掉super.onSaveInstanceState(),这种方式不保存之前的Fragment状态,因此不会重叠。

    @Override
    protected void onSaveInstanceState(Bundle bundle) {
        // do not call super.onSaveInstanceState()
    }
    • 1
    • 2
    • 3
    • 4

    如果Fragment发生重建,如何保持原本的数据?

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

    Fragment状态的恢复

    • onSaveInstanceState()和onRestoreInstanceState() 
      少量数据,可以通过onSaveInstanceState()和onRestoreInstanceState()进行保存与恢复。Android会在销毁你的Activity之前调用onSaveInstanceState()方法,于是,你可以在此方法中存储关于应用状态的数据。然后你可以在onCreate()或onRestoreInstanceState()方法中恢复

    • Fragment 
      大量数据,使用Fragment保持需要恢复的对象

    • 自已处理配置变化 
      注:getLastNonConfigurationInstance()已经被弃用,被上述方法二替代。

    使用Fragment来保存对象、恢复数据

    如果重新启动你的Activity需要恢复大量的数据,重新建立网络连接,或者执行其他的密集型操作,这样完全重新启动可能会是一个慢的用户体验。并且,使用系统提供的onSaveIntanceState()的回调中,使用Bundle来完全恢复你Activity的状态是可能是不现实的(Bundle不是设计用来携带大量数据的(例如bitmap),并且Bundle中的数据必须能够被序列化和反序列化),这样会消耗大量的内存和导致配置变化缓慢。

    在这样的情况下,当你的Activity因为配置发生改变而重启,你可以通过保持一个Fragment来缓解重新启动带来的负担。这个Fragment可以包含你想要保持的有状态的对象的引用。

    在屏幕旋转后,可以使用Lru缓存来存储Fragment中的图片,然后可以快速的从内存中读取。当Android系统因为配置变化关闭你的Activity的时候,你的Activity中被标识保持的fragments不会被销毁。你可以在你的Activity中添加这样的fragements来保存有状态的对象。

    在运行时配置发生变化时,在Fragment中保存有状态的对象

    • 继承Fragment,声明引用指向你的有状态的对象
    • 当Fragment创建时调用setRetainInstance(boolean)
    • 把Fragment实例添加到Activity中
    • 当Activity重新启动后,使用FragmentManager对Fragment进行恢复
    public class RetainedFragment extends Fragment {
    
        private MyDataObject data;//保存的数据
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRetainInstance(true);// retain this fragment
    
        }
    
        public void setData(MyDataObject data) {
            this.data = data;
        }
    
        public MyDataObject getData() {
            return data;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在Fragment的onDestroy方法中为Fragment调用setData设置数据。然后在Activity重新创建时获取到该Fragment实例,调用getData()方法,获取到保存的数据。进行视图填充。

    public class FragmentRetainDataActivity extends Activity{  
    
        private static final String TAG = "FragmentRetainDataActivity";  
        private RetainedFragment dataFragment;  
        private DialogFragment mLoadingDialog;  
        private ImageView mImageView;  
        private Bitmap mBitmap;  
    
        @Override  
        public void onCreate(Bundle savedInstanceState){  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
            Log.e(TAG, "onCreate");  
    
            // find the retained fragment on activity restarts  
            FragmentManager fm = getFragmentManager();  
            dataFragment = (RetainedFragment) fm.findFragmentByTag("data");  
            // create the fragment and data the first time  
            if (dataFragment == null)  
            {  
                // add the fragment  
                dataFragment = new RetainedFragment();  
                fm.beginTransaction().add(dataFragment, "data").commit();  
            }  
            mBitmap = getData();  
            initData();  
    
            // the data is available in dataFragment.getData()  
        }  
      }
     private Object getData(){  
            return dataFragment.getData();  
     }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    配置configChanges,自己对屏幕旋转的变化进行处理

    此时,无论用户何时旋转屏幕都不会重新启动Activity,并且onConfigurationChanged中的代码可以得到调用。

    <activity
            android:name=".ConfigChangesTestActivity"
            android:configChanges="screenSize|orientation" >
    </activity>
    • 1
    • 2
    • 3
    • 4
    /** 
      * 当配置发生变化时,不会重新启动Activity。但是会回调此方法,用户自行进行对屏幕旋转后进行处理 
      */  
     @Override  
     public void onConfigurationChanged(Configuration newConfig)  
     {  
         super.onConfigurationChanged(newConfig);  
    
         if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)  
         {  
             Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();  
         } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)  
         {  
             Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();  
         }  
    
     }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    http://developer.android.com/guide/topics/resources/runtime-changes.html

    http://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/

    Fragment的典型应用场景

    Fragment的应用场景最多的便是ViewPager+Fragment的实现,现在主流的APP几乎都能看到它们的身影,那么这一部分我就主要针对该应用场景进行分析。
    ViewPager+Fragment结构
    相信绝大多数的人都用ViewPager+Fragment的形式实现过界面,同时目前市面上主流的APP也都是采用这种结构来进行UI架构的,所以我们有必要单独对这种情况拿出来做一下分析。
    首先我们来看一段代码:

    public class MainActivity extends FragmentActivity
            implements
                View.OnClickListener {
    
        private ViewPager mViewPager;
    
        private List<Fragment> mList;
        private Fragment mOne;
        private Fragment mTwo;
        private Fragment mThree;
        private Fragment mFour;
    
        private Button mOneButton;
        private Button mTwoButton;
        private Button mThreeButton;
        private Button mFourButton;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mViewPager = (ViewPager) findViewById(R.id.content_pager);
            //加载Fragment
            mList = new ArrayList<>();
            mOne = new OneFragment();
            mTwo = new TwoFragment();
            mThree = new ThreeFragment();
            mFour = new FourFragment();
            mList.add(mOne);
            mList.add(mTwo);
            mList.add(mThree);
            mList.add(mFour);
    
            mOneButton = (Button) findViewById(R.id.one);
            mTwoButton = (Button) findViewById(R.id.two);
            mThreeButton = (Button) findViewById(R.id.three);
            mFourButton = (Button) findViewById(R.id.four);
    
            mOneButton.setOnClickListener(this);
            mTwoButton.setOnClickListener(this);
            mThreeButton.setOnClickListener(this);
            mFourButton.setOnClickListener(this);
    
            //设置到ViewPager中
            mViewPager.setAdapter(new ContentsPagerAdapter(
                    getSupportFragmentManager()));
    
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.one :
                    mViewPager.setCurrentItem(0);
                    break;
                case R.id.two :
                    mViewPager.setCurrentItem(1);
                    break;
                case R.id.three :
                    mViewPager.setCurrentItem(2);
                    break;
                case R.id.four :
                    mViewPager.setCurrentItem(3);
                    break;
            }
        }
    
        
        class ContentsPagerAdapter extends FragmentStatePagerAdapter {
    
            public ContentsPagerAdapter(FragmentManager fm) {
                super(fm);
            }
    
            @Override
            public Fragment getItem(int position) {
                return mList.get(position);
            }
    
            @Override
            public int getCount() {
                return mList.size();
            }
        }
    }
    

    在这里我们加载了4个Fragment到ViewPager中,同时我在这里使用的是
    FragmentStatePagerAdapter。这里需要提到的是FragmentStatePagerAdapter与FragmentPagerAdapter的区别。
    FragmentPagerAdapter:对于不再需要的fragment,仅仅只会调用到onDestroyView方法,也就是仅仅销毁视图而并没有完全销毁Fragment。
    FragmentStatePagerAdapter:会销毁不再需要的fragment,一直调用到onDetach方法失去与Activity的绑定。销毁时,会调用onSaveInstanceState(Bundle outState)方法通过bundle将信息保存下来,当用户切换回来,可以通过该bundle恢复生成新的fragment,也就是说,我们可以在onSaveInstanceState(Bundle outState)方法中保存一些数据,在onCreate中进行恢复创建。下面我们来看一下日志就清楚了:
    首先在使用FragmentPagerAdapter中的时候我们观察日志:

     
    FragmentPagerAdapter.png

    首先进入的时候ViewPager处于第一个Fragment上,此时由于ViewPager的预加载功能TwoFragment也被加载了,通过日志我们就能看到。当我们此时切换到了第四个Fragment中去的时候,我们就会发现OneFragment仅仅只是调用了onDestroyView方法而已,后面的onDestroy方法很onDetach方法都没被调用到。


     
    FragmentStatePagerAdapter.png

    同样的操作再在FragmentStatePagerAdapter里来一遍,我们会发现当我们切换的时候One和Two的Fragment的onDestroyView,onDestroy,onDetach全部都调用到了。同时我在OnewFragment通过onSaveInstanceState方法存起来的值在下一次的onCreate的时候也能读取到。

    通过上面的代码和例子我们基本搞清楚了ViewPager与Fragment如何结合起来使用,以及他们的生命周期调用我们也弄清楚了。那么我们到底什么时候用FragmentStatePagerAdapter,什么时候用FragmentPagerAdapter呢?根据我的经验来看,当页面较少的情况下可以考虑使用FragmentPagerAdapter,通过空间来换取时间上的效率。但当页面多了的时候我们就更需要使用FragmentStatePagerAdapter来做了,因为没有哪个用户希望某个应用会占爆它内存。



    作者:Dracula716
    链接:https://www.jianshu.com/p/c289b26c602e
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    FragmentPagerAdapter与FragmentStatePagerAdapter

    主要区别在与对于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

    其它Fragment类型

      • DialogFragment 
        显示一个浮动的对话框. 
        用这个类来创建一个对话框,是使用在Activity类的对话框工具方法之外的一个好的选择,因为你可以将一个fragment对话框合并到activity管理的fragment back stack中,允许用户返回到一个之前曾被摒弃的fragment

      • ListFragment 
        显示一个由一个adapter(例如 SimpleCursorAdapter)管理的项目的列表, 类似于ListActivity。它提供一些方法来管理一个list view, 例如 onListItemClick()回调来处理点击事件

      • PreferenceFragment 
        显示一个Preference对象的层次结构的列表, 类似于PreferenceActivity。这在为你的应用创建一个”设置”activity时有用处。

  • 相关阅读:
    ASP+Access UTF-8 网页乱码问题解决办法
    使用PowerDesigner生成Access数据库
    crontab详解
    Pending Statistics
    Recovery with Incremental Backups
    ASP的Global.asa使用说明
    cocos基础教程(5)数据结构介绍之cocos2d::Map<K,V>
    cocos基础教程(5)数据结构介绍之cocos2d::Vector
    cocos基础教程(4)基础概念介绍
    cocos进阶教程(1)Lua调用自定义C++类和函数的最佳实践
  • 原文地址:https://www.cnblogs.com/Im-Victor/p/9557652.html
Copyright © 2011-2022 走看看