Fragment的了解与学习

Fragment的用途:

1、为平板做适配、正常手机是宽度小于长度,平板则相反,当然这个判断方法并不保险,因为当手机翻转的时候,手机配置发生变化,导致的结果就是长宽大小比改变,这时候如果手机当平板用,感人感觉应该体验不佳;

2、首页多BottomTab的,这种软件布局比较常见,像贴吧、QQ、微信、微博、支付宝等都是这样的开头布局(里面的不讲吼),一个主Activity,然后动态切换四个左右的fragment,达到一种快速的感觉,毕竟Activity的创建还是很沉重的;

那么话说回来,这种效果到底是如何的呢?熟悉Android的朋友一定都会知道,很简单嘛,使用TabHost就OK了!但是殊不知,TabHost并非是那么的简单,它的可扩展性非常的差,不能随意地定制Tab项显示的内容,而且运行还要依赖于ActivityGroup。ActivityGroup原本主要是用于为每一个TabHost的子项管理一个单独的Activity,但目前已经被废弃了。

3、为了丝滑体验,用单Activity+多Fragment的组合,这种时候就要深入的理解Fragment了,不然出了问题就不太好挽回,同时这也是我所向往的;

目前我们想到的主要是这3个。

Fragment的生命周期

这个是始终逃不掉的,正是有了和Activity差不多的生命周期才显得如此精彩

此处输入图片的描述

说实话,我是很讨厌别人一上来就讲生命周期的,当我刚开始接触Android的时候,买了一本书来学习,结果第一章节就给我讲生命周期,但是当时不懂,听作者说这个生命周期太重要了,唬的我不得不看,但是我一个菜鸟看这个干嘛,我当时迫不及待的想搞个登录界面出来,找点兴趣和成就感。后来,我也学了很长时间的android之后(基础阶段),我用到的生命周期很少,只是普通的自动创建onCreate,然后普通的使用一个Activity,因为我才弄懂TextView、EditText等,我还用不到生命周期。有一天当我终于被一个demo给迷住的时候,我才发现生命周期的厉害,然后我才真正的去看Activity的生命周期,去看他们的先后顺序,作用,以及能利用生命周期函数做什么,所以我是很反感的,所以当我第一次接触Fragment的时候,发现竟然也有生命周期,而且比Activity还多,我就恼怒了,然后直接弃之生命周期去看别的,以至于在我人生第一次面试中,要默写生命周期函数的名称。。。

我还是这个态度,当你真正想了解Fragment的生命周期函数的时候再来关注,不然你看了没什么用,还一时半会记不住,自寻烦恼,就算你记住了,用不到精妙住处也是徒劳。

但我接触了fragment很久以后,也是在体会到生命周期的重要性之后,我就一直想搞清楚这个生命周期函数分别是在什么时候调用的,于是,我就测试了一下:

12-27 03:03:04.889 5393-5393/com.xiey94.fragment E/ccer: Activity----onCreate
12-27 03:03:04.891 5393-5393/com.xiey94.fragment E/ccer: Fragemnt----onAttach
12-27 03:03:04.891 5393-5393/com.xiey94.fragment E/ccer: Fragemnt----onCreate
12-27 03:03:04.891 5393-5393/com.xiey94.fragment E/ccer: Fragemnt----onCreateView
12-27 03:03:04.894 5393-5393/com.xiey94.fragment E/ccer: Fragemnt----onViewCreated
12-27 03:03:04.894 5393-5393/com.xiey94.fragment E/ccer: Fragemnt----onActivityCreated
12-27 03:03:04.894 5393-5393/com.xiey94.fragment E/ccer: Fragemnt----onViewStateRestored
12-27 03:03:04.895 5393-5393/com.xiey94.fragment E/ccer: Fragemnt----onStart
12-27 03:03:04.895 5393-5393/com.xiey94.fragment E/ccer: Activity----onStart
12-27 03:03:04.895 5393-5393/com.xiey94.fragment E/ccer: Activity----onResume
12-27 03:03:04.895 5393-5393/com.xiey94.fragment E/ccer: Fragemnt----onResume
12-27 03:03:16.422 5393-5393/com.xiey94.fragment E/ccer: Fragemnt----onPause
12-27 03:03:16.422 5393-5393/com.xiey94.fragment E/ccer: Activity----onPause
12-27 03:03:17.169 5393-5393/com.xiey94.fragment E/ccer: Fragemnt----onStop
12-27 03:03:17.170 5393-5393/com.xiey94.fragment E/ccer: Activity----onStop
12-27 03:03:17.170 5393-5393/com.xiey94.fragment E/ccer: Fragemnt----onDestroyView
12-27 03:03:17.170 5393-5393/com.xiey94.fragment E/ccer: Fragemnt----onDestroy
12-27 03:03:17.170 5393-5393/com.xiey94.fragment E/ccer: Fragemnt----onDetach
12-27 03:03:17.170 5393-5393/com.xiey94.fragment E/ccer: Activity----onDestroy

可以从测试中看到先后顺序,先后顺序很重要,可以帮助我们准确的在哪个阶段该干什么;

知道了调用顺序后就紧接着应该知道,都是在什么时候回调的,这个了解了才算是真正的知道在哪个阶段该干什么;

* onAttach:*
当Fragment依附到Activity的时候调用
可以获取到依附的Activity的实例;当api-23及以后,会显示onAttach(Activity activity)是过时的,要使用onAttach(Context context);

* onCreate:*
在Fragment初始化的时候调用,可以再该方法中使用getArgument获取activity出过来的fragment的参数,这个时候还不能获取activity中的控件,因为Activity还没又onCreate完成。

* onCreateView:*
可以通过布局填充器来获取fragment的布局,同时可初始化fragment中的控件。(我之前都是不关注fragment的生命周期的,关于初始化操作,我都是全部扔到这里面操作的)

* onViewCreate:*
当onCreateView调用完成之后立即执行,可以在这里面赋值;onViewCreated(View view,Bundle savedInstanceState),这个里面的View就是onCreateView中最后返回的View,可以在这里面进行初始化fragment的控件并赋值;个人感觉相对普通功能来说是和onCreateView重了,但是在特殊场景下肯定有意外收获,当然 我还没有遇到。

* onActivityCreate:*
这个就是Activity的onCreate方法执行完了,此时就可以获取Activity中的控件资源了。

* onViewStateRestored:*

* onStart:*
我在网上看到有的博客说这个方法是在Activity的onStart执行之后立刻执行的,但是根据我打印的结果来看,好像他要比Activity的onStart要先执行;这个方法执行完fragment界面就显示出来了,但是现在还没有获取到焦点,用户是还不能进行操作体验的。

* onResume:*
用户可操作了

* onPause:*
可见,失去焦点,(上面弹出一个Dialog)

* onStop:*
不可见,上面覆盖了一个fragment或者退出前夕

* onDestroyView:*
fragment视图被销毁,fragment视图被回收

* onDestroy:*
这个fragment不再使用了,但资源没有回收,还是可以在Activity中找到的

* onDetach:*
与Activity接触依附关系,fragment资源被回收。

额外的回调方法:
* onHiddenChanged*
当Fragment调用hide、show时回调

* setUserVisibleHint*
当Fragment与ViewPager结合使用时,切换Pager时回调

特别说明:
1、onAttach和onCreate只在Fragment与Activity第一次关联时调用;
2、onDestroy和onDetach只在Fragment与Activity销毁时才会被调用;
3、addToBackStack只涉及onCreateView和onDestroyView这之间的生命周期;add和replace不会对Fragment的生命周期产生影响,但add会造成Fragment叠加显示;
4、Fragment和ViewPager结合时使用时的生命周期与4相似;
5、通过hide、show来显示和隐藏Fragment,此时Fragment只改变了可见性,并不涉及生命周期的改变;

简单使用:

1、创建一个普通的xml文件;
2、创建一个Class继承自Fragment,并填充加载创建的xml文件;
3、在Activity中显示;

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#7722aa"
    android:gravity="center"
    android:clickable="true"
    android:orientation="vertical">

    <TextView
        android:id="@+id/testView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="fragment1"
        android:textColor="#ff4466"
        android:textSize="30sp" />

</LinearLayout>
public class Fragment2 extends Fragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment2, container, false);
    }
}
FragmentManager fm=getSupportFragmentManager();
FragmentTransaction ft=fm.beginTransaction();
Fragment2 fragment2=new Fragment2();
ft.replace(R.id.rootContent,fragment2);
ft.commit();
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff33aa"
    android:orientation="horizontal">

    <FrameLayout
        android:id="@+id/rootContent"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />


</LinearLayout>

以上这一段代码应该不用解释,他是最普通的demo案例。

Fragment的堆栈管理器FragemntManager:用于对Fragment的堆栈管理;getFragmentManager() // v4中,getSupportFragmentManager
FragmentTransaction事务,学过数据库就应该知道(程序员都学过吧),在事务中进行操作,最后提交事务。

因为版本的问题,Android3.0以下的版本,需要引入v4的包;一旦决定用了v4的就不能用app,反之亦然,不然改死你。

主要的操作是事务的操作:

* ft.add*
往Activity中添加一个fragment

* ft.remove*
从Activity中移除一个Fragment,如果被移除的这个Fragment没有添加到回退栈中,它的实例将会被销毁;

* ft.replace*
使用另一个fragment替换当前的fragment;实际上就是remove之后add的合体

* ft.hide*
隐藏当前的fragment,晶晶是不可见,并不会销毁;

* ft.show*
显示之前隐藏的fragment

额外的API:

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

* attach*
重建view视图,附加到UI上并显示。

测试:
为了多了解一点事务操作,写了一个不成熟的demo用来测试add、show、hide

package com.xiey94.fragment.test3;

import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.xiey94.fragment.R;

public class Test3Activity extends AppCompatActivity {

    private FragmentManager fm;
    private Fragment31 fragment31;
    private Fragment32 fragment32;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test3);

        fm = getSupportFragmentManager();

        FragmentTransaction ft = fm.beginTransaction();
        fragment31 = new Fragment31();
        ft.add(R.id.rootViewGroup, fragment31);
        ft.commit();

        Button btn1 = (Button) findViewById(R.id.btn1);
        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentTransaction ft = fm.beginTransaction();
                if (fragment31 == null) {
                    fragment31 = new Fragment31();
                }

                if (fragment31.isAdded()) {
                    if (fragment32 != null) {
                        ft.hide(fragment32).show(fragment31);
                    } else {
                        ft.show(fragment31);
                    }
                } else {
                    ft.add(R.id.rootViewGroup, fragment31);
                }

                ft.commit();
            }
        });

        Button btn2 = (Button) findViewById(R.id.btn2);
        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentTransaction ft = fm.beginTransaction();
                if (fragment32 == null) {
                    fragment32 = new Fragment32();
                }

                if (fragment32.isAdded()) {
                    if (fragment31 != null) {
                        ft.hide(fragment31).show(fragment32);
                    } else {
                        ft.show(fragment32);
                    }
                } else {
                    ft.add(R.id.rootViewGroup, fragment32);
                }

                ft.commit();
            }
        });
    }
}

为什么有这么多的if-else,开始我写的也是很少的,然后测试过来一路修改的:
1、当fragment被add之后就不能再add了;
2、第一个判断null,是避免创建过多实例,第二个判断null,是避免为null;
3、当我不停的切换fragment1和fragment2时,被回调的是他们俩的onHiddenChanged,所以他们的用户操作行为还是保存的;
4、如果不希望保存用户操作,那就用remove-add或replace来操作;
5、remove和detach有一点细微的区别,在不考虑回退栈的情况下,remove会销毁整个Fragment实例,而detach则只是销毁其视图结构,实例并不会被销毁。那么二者怎么取舍使用呢?如果你的当前Activity一直存在,那么在不希望保留用户操作的时候,你可以优先使用detach。

关于attach()和detach()我不是很懂,从别人那找了找:

使用add()加入fragment时将触发onAttach(),使用attach()不会触发onAttach()

使用replace()替换后会将之前的fragment的view从viewtree中删除

触发顺序:

detach()->onPause()->onStop()->onDestroyView()

attach()->onCreateView()->onActivityCreated()->onStart()->onResume()

使用hide()方法只是隐藏了fragment的view并没有将view从viewtree中删除,随后可用show()方法将view设置为显示

而使用detach()会将view从viewtree中删除,和remove()不同,此时fragment的状态依然保持着,在使用attach()时会再次调用onCreateView()来重绘视图,注意使用detach()后fragment.isAdded()方法将返回false,在使用attach()还原fragment后isAdded()会依然返回false(需要再次确认)

执行detach()和replace()后要还原视图的话, 可以在相应的fragment中保持相应的view,并在onCreateView()方法中通过view的parent的removeView()方法将view和parent的关联删除后返回

6、我的感觉:对比来看;attach和add对比:一个重建视图,一个重建实例 ;detach和remove的区别:一个销毁视图,一个直接销毁实例;但是我还没想到他们的使用场景。

add或者replace只是添加到任务,如果立即执行得加上executePendingTransactions。

回退栈

要想达到一个Activity+多个Fragment的效果,回退栈肯定是少不了的

FragmentTransaction.addToBackStack(String)

1、添加回退栈后,remove这个操作就不会直接销毁实例,而是会添加到回退栈中,再次回退的时候,会重新创建视图;

疑问:hide和show与回退栈有关系吗?

个人感觉好像没关系啊!add、remove、replace等操作都是针对实例的操作,也就是在add/remove/replace操作后要是添加了回退栈,就将他们的实例添加到管理栈中,前进或后退分别进栈出栈;而hide-show只是给(假设都在栈里)那些视图披上/脱下一层隐身衣。

跟踪回退栈的状态
我们通过实现OnBackStackChangedListener接口来实现回退栈状态跟踪,具体代码如下:

//implements接口
public class XXX implements FragmentManager.OnBackStackChangedListener 
//实现接口所要实现的方法
@Override
public void onBackStackChanged() {
  //do whatevery you want
}
//设置回退栈监听接口
getSupportFragmentManager().addOnBackStackChangedListener(this);

管理回退栈

(1).FragmentTransaction.addToBackStack(String)
将一个刚刚添加的Fragment加入到回退栈中

(2).getSupportFragmentManager().getBackStackEntryCount()
获取回退栈中的实体数量

(3).getSupportFragmentManager().popBackStack(String name, int flags)
根据name立刻弹出栈顶的fragment

(4).getSupportFragmentManager().popBackStack(int id, int flags)
根据id立刻弹出栈顶的fragment

还有出栈多个的问题:
Fragment 出栈的方法popBackStack需要特别注意的一点

popBackStack(String tag,int flags)

tag可以为null或者相对应的tag,flags只有0和1(POP_BACK_STACK_INCLUSIVE)两种情况

如果tag为null,flags为0时,弹出回退栈中最上层的那个fragment。

如果tag为null ,flags为1时,弹出回退栈中所有fragment。

如果tag不为null,那就会找到这个tag所对应的fragment,flags为0时,弹出该fragment以上的Fragment,如果是1,弹出该fragment(包括该fragment)以上的fragment。

popBackStack(int id,int flags)

与popBackStack(String tag,int flags)类似,找到id代表的fragment,然后执行一样的操作

popBackStackImmediate(int id, int flags)

popBackStackImmediate(String name, int flags)

popBackStackImmediate()

这几个方法类似以上的方法,只不过这几个在内部调用时会立即弹出

Activity异常销毁

配置发生改变,View重新绘制,导致Fragment也跟着重新绘制,onSaveInstanceState可以保存数据,然后在onCreate或者onCreateView或者onActivityCreated进行恢复都可以。

我尝试了一下销毁重建的demo,经过不断的调试后有一个可以正常销毁重建的:

fragment中我就放一个EditText

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>
public class Fragment41 extends Fragment {
    private EditText editText;
    private View rootView;
    public static final String TAG = "Fragment41";

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        if (rootView == null) {
            rootView = inflater.inflate(R.layout.fragment41, container, false);
        }

        ViewGroup parent = (ViewGroup) rootView.getParent();
        if (parent != null) {
            parent.removeView(rootView);
        }
        return rootView;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        editText = view.findViewById(R.id.editText);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (null != savedInstanceState) {
            editText.setText(savedInstanceState.getString("editText"));
        }

    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("editText", editText.getText().toString().trim());
    }
}

createView中创建视图,在ViewCreate中获取控件实例,在onSaveInstanceState中保存输入的数据,在onActivityCreated中获取数据,

activity的布局文件中就放了一个FrameLayout

Activity中:
一开始我就只是在onCreate中创建Fragment并提交后显示,但是翻转屏幕后保存的数据丢失了,然后我去打印Fragment的生命周期函数,发现开始的那几个Create、CreateView等前期函数调用了两次,百思不得其解,然后猜测就是创建了两个Fragment的实例,然后我在创建Fragment实例的时候加了一个==null的判断,但是依旧如此;也就是说,它依旧为null,这时我就不懂了,既然第一个已经是null了,那为什么还会执行两次呢?还没找到原因。

然后按照建议在Activity中去保存Fragment,然后重建之后获取Fragment

public class SaveStateActivity extends AppCompatActivity {

    private Fragment41 fragment41;
    private FragmentManager fm;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_save_state);
        fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();

        if (savedInstanceState != null) {
            fragment41 = (Fragment41) fm.getFragment(savedInstanceState, Fragment41.TAG);
        } else {
            fragment41 = new Fragment41();
            ft.add(R.id.root, fragment41, Fragment41.TAG);
            ft.commit();
        }

    }

    @Override
    public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager.putFragment(outState, Fragment41.TAG, fragment41);
    }

}

这样就OK了;

这里写图片描述

Fragment和Activity中通信

都推荐对外开放接口的方式,将Fragment 的一些对外操作传递给宿主 Activity。

public class OneFragment extends Fragment implements View.OnClickListener{

    public interface IOneFragmentClickListener{
        void onOneFragmentClick();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View contentView = inflater.inflate(R.layout.fragment_one, null);
        contentView.findViewById(R.id.edt_one).setOnClickListener(this);
        return contentView;
    }

    @Override
    public void onClick(View v) {
        if (getActivity() instanceof IOneFragmentClickListener){
             ((IOneFragmentClickListener) getActivity()).onOneFragmentClick();
        }
    }

}

这个就可以看 Android Fragment 真正的完全解析(下)

Fragment是否很完美

对于Fragment的一些理解

因为Fragment是由FragmentManager来管理,每一个Activity有一个FragmentManager,管理着一个Fragment的栈,Activity是系统级别的,由系统来管理ActivityManager,栈也是系统范围的。而Fragment则是每个Activity范围内的,所以在使用Fragment的时候也有几点要注意。

同一个Activity中,只能有一个ID或TAG标识的Fragment实例。
这很容易理解,同一个范围内,有标识的实例肯定是要唯一才行(否则还要标识干嘛)这个在布局中经常犯错,在布局中写Fragment最好不要加ID或者TAG,否则很容易出现不允许创建的错误。我的原则是如果放在布局中,就不要加ID和TAG,如果需要ID和TAG就全用代码控制。创建新实例前先到FragmentManager中查找一番,这也正是有标识的意义所在。

一个Activity中有一个Fragment池,实例不一定会被销毁,可能会保存在池中。
这个跟第一点差不多。就好比系统会缓存Activity的实例一样,FragmentManager也会缓存Fragment实例,以方便和加速再次显示。

FragmentManager的作用范围是整个Activity,所以,某一个布局ID,不能重复被Fragment替换。
通常显示Fragment有二种方式,一种是层叠到某个布局上,或者把某个布局上面的Fragment替换掉,但是这个布局不能出现二次,比如布局A中有ID为id的区域,要显示为Fragment,此布局A,只能在一个Activity中显示一个,否则第二个id区域不能被Fragment成功替换。因为虽有二个ID布局的实例,但ID是相同的,对FragmentManager来说是一样的,它会认为只有一个,因为它看的是布局的ID,而不是布局的实例。

Fragment的生命周期反应Activity的生命周期。
Fragment在显示和退出时会走一遍完整的生命周期。此外,正在显示时,就跟Activity的一样,Activity被onPause,里面的Fragment就onPause,以此类推,由此带来的问题就是,比如你在onStart()里面做了一些事情,那么,当宿主Activity被挡住,又出现时(比如接了个电话),Fragment的onStart也会被高到,所以你要想到,这些生命周期不单单在显示和退出时会走到。

Fragment的可见性。
这个问题出现在有Fragment栈的时候,也就是说每个Fragment不知道自己是否真的对用户可见。比如现在是Fragment A,又在其上面显示了FragmentB,当B显示后,A并不知道自己上面还有一个,也不知道自己对用户不可见了,同样再有一个C,B也不知。C退出后,B依然不知自己已在栈顶,对用户可见,B退后,A也不知。也就是说Fragment显示或者退出,栈里的其他Fragment无法感知。这点就不如Activity,a被b盖住后,a会走到onStop(),同样c显示后,b也能通过onStop()感知。Fragment可以从FragmentManager监听BackStackState的变化,但它只告诉你Stack变了,不告诉你是多了,还是少,还有你处的位置。有一个解决方案就是,记录页面的Path深度,再跟Fragment所在的Stack深度来比较,如果一致,那么这个Fragment就在栈顶。因为每个页面的Path深度是固定的,而Stack深度是不变化的,所以这个能准确的判断Fragment是否对用户可见,当然,这个仅针对整个页面有效,对于布局中的一个区域是无效的。

Fragment的事件传递。
对于层叠的Fragment,其实就相当于在一个FrameLayout里面加上一堆的View,所以,如果处于顶层的Fragment没处理点击事件,那么事件就会向下层传递,直到事件被处理。比如有二个Fragment A和B,B在A上面,B只有TextView且没处理事件,那么点击B时,会发现A里的View处理了事件。这个对于Activity也不会发生,因为事件不能跨窗体传播,上面的Activity没处理事件,也不会传给下面的Activity,即使它可见。解决之法,就是让上面的Fragment的根布局吃掉事件,为每个根ViewGroup添加onClick=“true”。

与第三方Activity交互。与第三方交互,仍要采用Android的标准startActivityForResult()和onActivityResult()这二个方法来进行。但对于Fragment有些事情需要注意,Fragment也有这二个方法,但是为了能正确的让Fragment收到onActivityResult(),需要:

1、宿主Activity要实现一个空的onActivityResult(),里面调用super.onActivityResult()

2、调用Fragment#startActivityForResult()而不是用Activity的 当然,也可以直接使用Activity的startActivityForResult(),那样的话,就只能在宿主Activity里处理返回的结果了。

Fragment Arguments

像普通的类一样,Fragment 拥有自己的构造函数,于是我们可以像下面这样在 Activity 中创建 Fragment 实例:

MainFragment mainFragment = new MainFragment();

如果需要在创建 Fragment 实例时传递参数进行初始化的话,可以创建一个带参数的构造函数,并初始化 Fragment 成员变量等。这样做,看似没有问题,但在一些特殊状况下还是有问题的。

我们知道,Activity 在一些特殊状况下会发生 destroy 并重新 create 的情形,比如屏幕旋转、内存吃紧时;对应的,依附于 Activity 存在的 Fragment 也会发生类似的状况。而一旦重新 create 时,Fragment 便会调用默认的无参构造函数,导致无法执行有参构造函数进行初始化工作。

创建通用实例

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的startActivityForResult

这个我是在鸿洋的博客里面看到用途的,但是我觉得那应该仅供学习参考,用途上我更倾向于用EventBus这一类的事件总线(原谅我不上进);

FragmentPagerAdapter与FragmentStatePagerAdapter

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。

getChildFragmentManager()

在 Activity 嵌入 Fragment 时,需要使用 FragmentManager,通过 Activity 提供的 getFragmentManager() 方法即可获取,用于管理 Activity 里面嵌入的所有一级 Fragment。

然而有时候,我们会在 Fragment 里面继续嵌套二级甚至三级 Fragment,即 Activity 嵌套多级 Fragment。此时在 Fragment 里管理子 Fragment 时,也需要使用到 FragmentManager。但是一定要使用 getChildFragmentManager() 方法获取 FragmentManager 对象!

管理Fragment

可以通过FragmentManager做一些事情, 包括: 使用findFragmentById()(用于在activity layout中提供一个UI的fragment)或findFragmentByTag()(适用于有或没有UI的fragment)获取activity中存在的fragment。

将fragment从后台堆栈中弹出, 使用 popBackStack() (模拟用户按下BACK 命令)。

使用addOnBackStackChangeListener()注册一个监听后台堆栈变化的listener。

派生类

DialogFragment
显示一个浮动的对话框。使用这个类创建对话框是替代activity创建对话框的最佳选择。因为可以把fragmentdialog
放入到activity的返回栈中,使用户能再返回到这个对话框。

ListFragment
显示一个列表控件,就像ListActivity类,它提供了很多管理列表的方法,比如onListItemClick()方法响应click事件。

PreferenceFragment
显示一个由Preference对象组成的列表,与PreferenceActivity相同。它用于为程序创建“设置”activity。

WebViewFragement
WebView 界面的 Fragement;

commit()

commit()方法并不立即执行transaction中包含的动作,而是把它加入到UI线程队列中.
如果想要立即执行,可以在commit之后立即调用FragmentManager的executePendingTransactions()方法.

commit()方法必须在状态存储之前调用,否则会抛出异常,如果觉得状态丢失没关系,可以调用commitAllowingStateLoss(). 但是除非万不得已, 一般不推荐用这个方法, 会掩盖很多错误.

DialogFragment—setTargetFragment

这个方法,一般就是用于当前fragment由别的fragment启动,在完成操作后返回数据的

从鸿洋大神博客摘抄的一个说明demo

public class ContentFragment extends Fragment  {  

    private String mArgument;  
    public static final String ARGUMENT = "argument";  
    public static final String RESPONSE = "response";  
    public static final String EVALUATE_DIALOG = "evaluate_dialog";  
    public static final int REQUEST_EVALUATE = 0X110;  

    //...  

    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState)  {  
        Random random = new Random();  
        TextView tv = new TextView(getActivity());  
        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(  
                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);  
        tv.setLayoutParams(params);  
        tv.setText(mArgument);  
        tv.setGravity(Gravity.CENTER);  
        tv.setBackgroundColor(Color.argb(random.nextInt(100),  
                random.nextInt(255), random.nextInt(255), random.nextInt(255)));  
        // set click  
        tv.setOnClickListener(new OnClickListener()  {  

            @Override  
            public void onClick(View v)  {  
                EvaluateDialog dialog = new EvaluateDialog();  
                //注意setTargetFragment  
                dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE);  
                dialog.show(getFragmentManager(), EVALUATE_DIALOG);  
            }  
        });  
        return tv;  
    }  

    //接收返回回来的数据  
    @Override  
    public void onActivityResult(int requestCode, int resultCode, Intent data)  {  
        super.onActivityResult(requestCode, resultCode, data);  

        if (requestCode == REQUEST_EVALUATE)  {  
            String evaluate = data  
                    .getStringExtra(EvaluateDialog.RESPONSE_EVALUATE);  
            Toast.makeText(getActivity(), evaluate, Toast.LENGTH_SHORT).show();  
            Intent intent = new Intent();  
            intent.putExtra(RESPONSE, evaluate);  
            getActivity().setResult(Activity.REQUEST_OK, intent);  
        }  

    }  
}  
public class EvaluateDialog extends DialogFragment  {  
    private String[] mEvaluteVals = new String[] { "GOOD", "BAD", "NORMAL" };  
    public static final String RESPONSE_EVALUATE = "response_evaluate";  

    @Override  
    public Dialog onCreateDialog(Bundle savedInstanceState)  {  
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());  

        builder.setTitle("Evaluate :").setItems(mEvaluteVals,  
                new OnClickListener()  
                {  
                    @Override  
                    public void onClick(DialogInterface dialog, int which)  
                    {  
                        setResult(which);  
                    }  
                });  
        return builder.create();  
    }  

    // 设置返回数据  
    protected void setResult(int which)  {  
        // 判断是否设置了targetFragment  
        if (getTargetFragment() == null)  
            return;  

        Intent intent = new Intent();  
        intent.putExtra(RESPONSE_EVALUATE, mEvaluteVals[which]);  
        getTargetFragment().onActivityResult(ContentFragment.REQUEST_EVALUATE,  
                Activity.RESULT_OK, intent);  

    }  
}  

为fragment提供了ID有三种方法

1、用android:id属性提供一个唯一的标识
2、用android:tag属性提供一个唯一的字符串。
3、如果上述两种属性都没有,系统会使用其容器视图(view)的ID。

inflate()

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) { 
          // Inflate the layout for this fragment
         return inflater.inflate(R.layout.example_fragment, container, false);
}

inflate()函数需要以下三个参数:
(1).要inflate的布局的资源Id
(2).被inflate的布局的父ViewGroup
(3).一个布尔值,表明在inflate期间被inflate的布局是否应该附上ViewGroup(第二个参数container)。(在这个例子中传入的是false,因为系统已经将被inflate的布局插入到容器中(container)——传入true会在最终的布局里创建一个多余的ViewGroup)

Fragment状态的持久化:

由于Activity会经常性的发生配置变化,所以依附它的Fragment就有需要将其状态保存起来问题。下面有两种常用的方法去将Fragment的状态持久化。

方法一
可以通过protected void onSaveInstanceState(Bundle outState),protected void onRestoreInstanceState(Bundle savedInstanceState)状态保存和恢复的方法将状态持久化。

方法二(更为方便,让Android自动帮我们保存Fragment状态)
<1>.我们只需要将Fragment在Activity中作为一个变量整个保存,只要保存了Fragment,那么Fragment的状态就得到保存了,所以我们就可以通过下面方法,进行获取Fragment数据。

FragmentManager.putFragment(Bundle bundle, String key, Fragment
fragment) 是在Activity中保存Fragment的方法。
FragmentManager.getFragment(Bundle bundle, String key)
是在Activity中获取所保存的Frament的方法。

<2>.很显然,上述<1>中的key就传入Fragment的id,fragment就是你要保存状态的fragment,但,我们注意到上面的两个方法,第一个参数都是Bundle,这就意味着FragmentManager是通过Bundle去保存Fragment的。但是,这个方法仅仅能够保存Fragment中的控件状态,比如说:EditText中用户已经输入的文字(注意:在这里,控件需要设置一个id值,否则Android将不会为我们保存该控件的状态),而Fragment中需要持久化的变量依然会丢失,但依然有解决方法,就是利用方法一!

<3>.下面给出状态的持久化实例代码:

 /** Activity中的代码 **/
   FragmentB fragmentB;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.fragment_activity);
      if( savedInstanceState != null ){
          fragmentB = (FragmentB) getSupportFragmentManager()
                      .getFragment(savedInstanceState,"fragmentB");
      }
      init();
  }

  @Override
  protected void onSaveInstanceState(Bundle outState) {
      if( fragmentB != null ){
         getSupportFragmentManager()
           .putFragment(outState,"fragmentB",fragmentB);
      }
      super.onSaveInstanceState(outState);
  }

  /** Fragment中保存变量的代码 **/
  @Nullable
  @Override
  public View onCreateView(LayoutInflater inflater, @Nullable 
      ViewGroup container, @Nullable Bundle savedInstanceState) {
      AppLog.e("onCreateView");
      if ( null != savedInstanceState ){
          String savedString = savedInstanceState
                               .getString("string");
          //得到保存下来的string
      }
      View root = inflater.inflate(R.layout.fragment_a,null);
      return root;
  }

  @Override
  public void onSaveInstanceState(Bundle outState) {
      outState.putString("string","anAngryAnt");
      super.onSaveInstanceState(outState);
  }

commitAllowingStateLoss()和commit()

源码分析commitAllowingStateLoss() 和commit()的区别
Fragment提交transaction导致state loss异常

使用Fragment的时候偶尔会有这么一个报错,Can not perform this action after onSaveInstanceState,意思为无法再onSaveInstanceState之后执行该操作,这个操作就是指commit()

为什么我们会有这种报错呢,因为我们在使用add(),remove(),replace()等方法将Fragment的变化添加进去,然后在通过commit去提交这些变化(另外,在commit之前可以去调用addToBackState()方法,将这些变化加入到activity管理的back stack中去,这样用户调用返回键就可以回退这些变化了),提交完成之后这些变化就会应用到我们的Fragment中去。但是,这个commit()方法,你只能在avtivity存储他的状态之前调用,也就是onSaveInstanceState(),我们都知道activity有一个保存状态的方法和恢复状态的方法,这个就不详细解释了,在onSaveInstanceState()方法之后去调用commit(),就会抛出我们遇到的这个异常,这是因为在onSaveInstanceState()之后调用commit()方法,这些变化就不会被activity存储,即这些状态会被丢失,但我们可以去用commitAllowingStateLoss()这个方法去代替commit()来解决这个为题

stackoverflows上的一个回答:

Try moving your transactions into onPostResume() instead (note that onPostResume() is always called after onResume() and onResume() is always called after onActivityResult())

private boolean mReturningWithResult = false;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    mReturningWithResult = true;
}

@Override
protected void onPostResume() {
    super.onPostResume();
    if (mReturningWithResult) {
        // Commit your transactions here.
    }
    // Reset the boolean flag back to false for next time.
    mReturningWithResult = false;
}

findFragmentById/findFragmentByTag

manager.findFragmentById(); //根据ID来找到对应的Fragment实例,主要用在静态添加fragment的布局中,因为静态添加的fragment才会有ID

manager.findFragmentByTag();//根据TAG找到对应的Fragment实例,主要用于在动态添加的fragment中,根据TAG来找到fragment实例

manager.getFragments();//获取所有被ADD进Activity中的Fragment

设置TAG:

FragmentTransaction    add(int containerViewId, Fragment fragment, String tag)

android.app.FragmentTransaction.replace(int containerViewId, Fragment fragment, String tag)

此时用户指定tag,

然后根据tag获取fragment对象
getActivity().getFragmentManager().findFragmentByTag(getFragmentTag(position));

避免同一activity下 多个fragment 切换时重复执行onCreateView方法

Fragment之间切换时每次都会调用onCreateView方法,导致每次Fragment的布局都重绘,无法保持Fragment原有状态。解决办法:在Fragment onCreateView方法中缓存View

private View rootView;//缓存Fragment view  
   @Override  
   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
       if(rootView==null){  
           rootView=inflater.inflate(R.layout.tab_fragment, null);  
       }  
//缓存的rootView需要判断是否已经被加过parent, 如果有parent需要从parent删除,要不然会发生这个rootview已经有parent的错误。  
       ViewGroup parent = (ViewGroup) rootView.getParent();  
       if (parent != null) {  
           parent.removeView(rootView);  
       }   
       return rootView;  
   }  

转场动画 setCustomAnimations

Fragment的设置需要在transaction.add 或transaction.remove之前。
Fragment有两种方式,一种android提供了默认方法,一种自定义动画

//淡入淡出的默认动画
transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);

//设置自定义过场动画
transaction.setCustomAnimations(            
            R.anim.push_left_in,
            R.anim.push_left_out,
            R.anim.push_left_in,
            R.anim.push_left_out);

动画文件放置位置: res/anim: 这是兼容API-11以下的,只能有四种补间动画方式

//push_left_in_no_alpha,acitivity转场的时候用alpha会不好看
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="200"
        android:fromXDelta="100%p"
        android:toXDelta="0" />
</set>

//push_left_out_no_alpha
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="200"
        android:fromXDelta="0"
        android:toXDelta="-100%p" />
</set>

//push_right_in_no_alpha
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="200"
        android:fromXDelta="-100%p"
        android:toXDelta="0" />
</set>

//push_right_out_no_alpha
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="200"
        android:fromXDelta="0"
        android:toXDelta="100%p" />
</set>

用set标签的意思是可以集合多个动画一起执行,也可以自行选择单个动画,如alpha等。

Activity和Fragment的转场动画

遗留问题:
1、单Activity+多Fragment






参考博客:

浅谈 Fragment 生命周期
fragment中的attach/detach方法说明(网上拷贝,只为作笔记)
Android Fragment 真正的完全解析(上)
Android Fragment 真正的完全解析(下)
Android开发之Fragment回退栈
对于Fragment的一些理解
Android Fragment 你应该知道的一切
Android Fragment 的使用,一些你不可不知的注意事项
Android基础之使用Fragment控制切换多个页面
Android中Fragment的详解和使用。
Android Fragment使用(一) 基础篇 温故知新
Android–Fragment 你应该明白的一些疑难点
Android Fragment可见性的判断与监听完全实现
Square:从今天开始抛弃Fragment吧!
Fragment全解析系列(一):那些年踩过的坑
Fragment全解析系列(二):正确的使用姿势
Fragment之我的解决方案:Fragmentation
Fragmentation
基于AOP设计的Fragment框架
FragmentRigger
从源码角度分析,为什么会发生Fragment重叠?
9行代码让你App内的Fragment对重叠说再见
Android Fragment 和 FragmentManager 的代码分析
FragmentTransaction的commit和commitAllowingStateLoss的区别
commitallowingstateloss 和commit的区别
源码分析commitAllowingStateLoss() 和commit()的区别
Fragment提交transaction导致state loss异常
stackoverflow-“Failure Delivering Result ” - onActivityForResult
Android中保存和恢复Fragment状态的最好方法
Fragment 出栈的方法popBackStack需要特别注意的一点
避免同一activity下 多个fragment 切换时重复执行onCreateView方法
Activity和Fragment的转场动画
关于Fragment与Fragment、Activity通信的四种方式


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值