关于android Fragment那些事

目录

关于android Fragment那些事_第1张图片
目录.png

起源

android 3.0(api11)引入了fragment(碎片)这个概念,起初主要是为了给大屏幕(如平板电脑)上更加动态和灵活的 UI 设计提供支持。由于平板电脑的屏幕比手机屏幕大得多,因此可用于组合和交换 UI 组件的空间更大(类似于微信横屏时,朋友圈与好友列表显示在同一页,并各占一半,如下图),但是实际上虽然因为需要适配平板我们也使用了fragment,但是更多的时候我们把fragment当做了可复用组件。


关于android Fragment那些事_第2张图片
fragments.png

不仅仅是平板,fragment在手机端的那些事

啪啦啪啦写了一大堆,发现还是解释不清fragment是啥,直接上图吧,look:
关于android Fragment那些事_第3张图片
--简单点.jpg

譬如的app,下面是5个导航栏,对于5个完全不同的页面。那这5个页面可咋整呢,我要把5个页面写到一个activity里面,然后通过点击下面的导航栏来隐藏其他不需要的布局吗?5个还好(其实也不好了),如果10个20个呢(譬如今日头条上方的导航栏),这activity里面的代码怎么顶得住,那么多业务逻辑需要处理,这个时候fragment就起到了至关重要的作用。

你可以把fragment当成activity里的一个ui控件,每次点击下方的导航栏都是切换到对应的fragment,即5个fragment对应着5个页面 。

fragment的一些信息

  • fragment只能嵌套在activity中,不能独立存在
  • fragment也有自己的生命周期,但也正因为fragment依存于activity,所以activity的生命周期可以直接影响到fragment

优点

  • fragment的可复用性,多个activity可以复用同一个fragment
  • fragment切换流畅,轻量切换,不像activity切换那么笨重
  • fragment在activity也可以动态的添加、切换、删除,机动性高
  • 前面也说了,fragment一开始是为了适配平板引入的,提供了更加动态和灵活的 UI
  • 什么?为什么这么吹fragment?没办法,毕竟这章讲的fragment,人家是主角(XD)

生命周期

是的你没有看错,又是生命周期,老规矩,上图,look:
关于android Fragment那些事_第4张图片
fragment_lifecycle.png

onAttach:activity与fragment绑定
onCreate:fragment被创建
onCreateView:fragment加载视图
(其实这里还有个生命周期是onViewCreate,在onCreateView执行后立刻执行,看了下源码,里面没干啥子东西,可以在这个方法里面进行一些初始化操作,初始化变量,绑定监听事件之类)
onActivityCreated:当Activity中的onCreate方法执行完后调用。
onStart:跟activity一样就不讲了,详情见上一章的关于android生命周期那点事
onDestroyView:销毁fragment中的视图
onDetach:activity与fragment解除绑定

activity与fragment生命周期对比

关于android Fragment那些事_第5张图片
activity_fragment_lifecycle.png

当activity启动时,执行顺序如下:

MainActivity: onCreate: 
MainActivity: onStart: 
Fragment: onAttach: 
Fragment: onCreate: 
Fragment: onCreateView: 
Fragment: onViewCreated: 
Fragment: onActivityCreated: 
Fragment: onStart: 
MainActivity: onResume: 
Fragment: onResume: 

home键按下时,执行顺序如下:

MainActivity: onPause: 
Fragment: onPause: 
MainActivity: onStop: 
Fragment: onStop: 

再重新返回到该activity页面时,执行顺序如下

MainActivity: onRestart: 
MainActivity: onStart: 
Fragment: onStart: 
MainActivity: onResume: 
Fragment: onResume: 

关闭该activity时,执行顺序如下:

MainActivity: onPause: 
Fragment: onPause: 
MainActivity: onStop: 
Fragment: onStop: 
MainActivity: onDestroy: 
Fragment: onDestroyView: 
Fragment: onDestroy: 
Fragment: onDetach: 

Fragment的创建

先写个简单的fragment吧,fragment的布局如下



    
    


Fragment代码如下:

//你可以这样写,写法1
public class MyFragment extends Fragment {

    private static final String TAG = "MyFragment";

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onCreateView: ");
        return inflater.inflate(R.layout.fragment,container,false);
    }
}

--------------------------------------------------------------

//当然你也可以这样写,写法2
public class MyFragment extends Fragment {

    private static final String TAG = "MyFragment";


    public static MyFragment newInstance(String value) {
        Bundle args = new Bundle();
        args.putString("key", value);
        MyFragment fragment = new MyFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container
              , @Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onCreateView: ");
        return inflater.inflate(R.layout.fragment_blank, container, false);
    }


    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onViewCreated: ");
        super.onViewCreated(view, savedInstanceState);
        Bundle bundle = getArguments();
        if (getArguments() != null) {
            String value = bundle.getString("key", "");
        }
    }
}

两种写法实质上没什么区别,就是传参的方式不一样
写法一你可以使用以下方式传参

MyFragment mFragment = new MyFragment ();
Bundle bundle = new Bundle();
bundle.putString("key", "value");
mFragment.setArguments(bundle);

写法二则可以用 MyFragment .newInstance("value")传参

  • 两种写法都可以用方法二里面onViewCreated()中的方法取值
  • 官方更推荐我们使用写法二,我个人的理解是写法二Activity对Fragment的传参更好管理了,统一了参数形式,因为项目更多时候不止一个在写

然后来看看onCreateView的里面的三个参数

inflater:你可以看做是用来加载布局的一个类,具体的还是挺繁琐的,后面某一章讲吧
container:用来指定该视图(Fragment)需要嵌在哪个view下(这里是绑的activity)
savedInstanceState:因为内存不足或者按下home键(非用户手动按返回键销毁页面)等系统主动销毁页面时,存储的一些数据,具体的后面某一章会讲XD

再看看inflate里面的三个参数,具体源码就不看了

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
         ······
}

resource:布局文件的id
root:这个倒是跟上面onCreateView里面的container差不多,就不重复了
attachToRoot:是否将载入的视图绑定到根视图中

现在的只是说了个大概,关于更加详细的内容,譬如inflater,attachToRoot,savedInstanceState这些都有深入的价值在里面,但是这篇是fragment,还是给主角一点面子吧XD


fragment的动态添加&静态添加

静态与动态添加,无论是哪种方式,都是需要配个fragment滴,只是引入的方式不一样,Fragment的创建上面有写,这里就不重复了

静态添加

activity中的布局 activity_main




    

name:就是fragment所在的路径,用来绑定fragment的
注意:id是必须要的,顺带一提ConstraintLayout使用不了标签

动态添加

activity中布局 activity_main




    


activity中代码 activity_main

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d(TAG, "onCreate: ");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //写法一
        MyFragment fragment = new MyFragment();
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.framelayout,fragment);
        fragmentTransaction.commit();

        //当然你也可以写成一步,例如这个写法二:
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.framelayout,new Fragment())
                .commit();
    }
}

这里只写了add方法,其实FragmentTransaction还提供了remove(),replace(),hide(),show()等方法,这里就先只说上面提到的。

remove():移除指定的fragment,并销毁(如果没有回退栈的话)
add():添加指定fragment
replace():移除当前fragment,并添加指定fragment,等同于remove()+add()
hide():隐藏指定的fragment
show():显示指定的fragment

    @NonNull
    public abstract FragmentTransaction add(@NonNull Fragment var1, @Nullable String var2);
    //var1:Fragment实例
    //var2:tag标签,可以根据这个标签获取到该Fragment实例
    //该方法一般可用于创建无UI的Fragment,用来存储数据之类的操作

    @NonNull
    public abstract FragmentTransaction add(@IdRes int var1, @NonNull Fragment var2);
    //var1:你可以看做是Activity布局中用来放置Fragment的标签

    @NonNull
    public abstract FragmentTransaction add(@IdRes int var1, @NonNull Fragment var2, @Nullable String var3);
   

    @NonNull
    public abstract FragmentTransaction replace(@IdRes int var1, @NonNull Fragment var2);

    @NonNull
    public abstract FragmentTransaction replace(@IdRes int var1, @NonNull Fragment var2, @Nullable String var3);

    @NonNull
    public abstract FragmentTransaction remove(@NonNull Fragment var1);

    @NonNull
    public abstract FragmentTransaction hide(@NonNull Fragment var1);

    @NonNull
    public abstract FragmentTransaction show(@NonNull Fragment var1);

我们可以看到这几个方所需要的参数基本都是大同小异
FragmentManager ,FragmentTransaction 详见后面几章


fragment回退栈

什么是回退栈

众所周知,我们从页面A进入到页面B都是通过控制activity的入栈与出栈实现的,启动页面B时候,将activity B压入栈,此时activity B处于栈顶,即用户此时看到的页面是activity B;返回上一个页面时候,B移出栈,此时处于栈顶的为A,即用户此时看到的页面是A(这里留个位置给activity的启动模式与任务栈,后面某一章会讲到XD)

什么时候用到回退栈 && 什么时候使用多个activity,什么时候使用单个activity+多个fragment

归根到底,都是需求的问题

诚然,以上我们讨论的都是类似于app那样,底部导航栏+fragment,这种时候确实不大需要到回退栈,因为直接使用hide(),show()隐藏显示需要的fragment就可以了,但是需求总是千变万化滴。

譬如一个购买商品的页面,需要填写收货人的基本信息=>选择商品=>付款成功,这样3步的流程,对应3个activity页面。3个页面都是上面是1/3的logo布局(logo都是一样的)加下面的2/3业务逻辑。这个时候我们可以使用以下两种方法

  • 3个Activity+xml布局复用
  • 1个Activity+3个fragment+fragment回退栈

我想了半天,没有想到好的应用场景,必须使用到Fragment回退栈而不使用多个Activity。嗯,这个问题其实也可以等同于什么时候使用多个activity,什么时候使用单个activity+多个fragment。关于这一点我个人的看法是,根据自己的习惯以及需求等因素来考虑。

上面这段话有点标题党的嫌疑XD

回退栈的使用
  • 篇幅的问题,就不贴布局文件的代码了
  • 以上讨论的都是不包括viewpager的,viewpager+fragment会在后面某一章讲
  • 以下代码只是用来讲解的测试代码

Activity代码:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d(TAG, "onCreate: ");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //首页直接默认添加了一个fragment
        //如果savedInstanceState为空说明是首次创建,则不需要重新添加fragment
        // 否则说明已经被创建过,但是Activity被销毁重建,例横竖屏切换
        if (savedInstanceState == null) {
            getSupportFragmentManager()
                    .beginTransaction()
                    .add(R.id.framelayout, new UserInfoFragment())
                    .commit();
        }
    }
}

页面1 UserInfoFragment代码:

public class UserInfoFragment extends Fragment {
    private static final String TAG = "UserInfoFragment";

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Log.d(TAG, "onCreateView: ");
        return inflater.inflate(R.layout.fragment_user_info, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onViewCreated: ");
        super.onViewCreated(view, savedInstanceState);
        TextView tv122 = view.findViewById(R.id.tv_122);
        //lambda表达式
        tv122.setOnClickListener(v -> getActivity().getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.framelayout, new ShopQueryFragment())
                .addToBackStack("1to2")
                .commit());
    }
}

页面2 ShopQueryFragment的代码:

public class ShopQueryFragment extends Fragment {


    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_shop_query, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        TextView tv221 = view.findViewById(R.id.tv_221);
        TextView tv223 = view.findViewById(R.id.tv_223);
        tv221.setOnClickListener(v -> getActivity().getSupportFragmentManager().popBackStack());

        tv223.setOnClickListener(v -> getActivity().getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.framelayout, new Pay4ShopFragment())
                .addToBackStack("2to3")
                .commit());
    }
}

页面3 Pay4ShopFragment的代码:

public class Pay4ShopFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_pay4_shop, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        TextView tv321 = view.findViewById(R.id.tv_321);
        TextView tv320 = view.findViewById(R.id.tv_320);

        tv321.setOnClickListener(v -> getActivity().getSupportFragmentManager()
                .popBackStack("2to3", FragmentManager.POP_BACK_STACK_INCLUSIVE));

        tv320.setOnClickListener(v -> getActivity().finish());

    }

}
关于android Fragment那些事_第6张图片
demo.gif

画质有点渣,大家将就着看下吧,大致还是说下上面代码的功能

Fragment 1 ⇌ Fragment 2 两个Fragment可以相互跳转
Fragment 2 => Fragment 3
Fragment 3 => Fragment 1

跳转就不说了replace,回退都是使用的回退栈,主要涉及到addToBackStackpopBackStack这两个方法

addToBackStack

public FragmentTransaction addToBackStack(@Nullable String name) {
       ······
}

//官网是这么介绍的
Add this transaction to the back stack. This means that the transaction will be remembered after
it is committed, and will reverse its operation when later popped off the stack.

 name :String: An optional name for this back stack state, or null. This value may be null.

大致意思是:将该事务添加到返回栈中(入栈),将在commit操作后生效,出栈后会回退到该操作之前。
参数name:回退栈状态的名称,或者为null 。这个值可能为null

也就是说Fragment的回退栈不像Activity那样,入栈的是Activity,而是transaction操作

popBackStack

再来看看官网给的popBackStack()方法

public abstract void popBackStack ()

Pop the top state off the back stack. This function is asynchronous -- it enqueues 
the request to pop, but the action will not be performed until the application returns to its event loop.

大致意思是:返回栈中移除栈顶元素。该方法是异步的--它将以队列的形式请求出栈,直到循环到该操作才会被执行。
有点拗口,意思就是将最近那一次transaction干的事情回退回去,即移除栈顶的元素。但是这个方法又是异步的,类似于handler,looper那样存于消息队列中,需要等到looper拿完了前面的才能轮到你(handler,looper会在后面某一章讲)

下面看看带参数的那几个

public abstract void popBackStack(@Nullable String var1, int var2);
Pop the last fragment transition from the manager's fragment back stack. If there is nothing to pop
, false is returned. This function is asynchronous -- it enqueues the request to pop, 
but the action will not be performed until the application returns to its event loop.
//参数
var1:String: If non-null, this is the name of a previous back state to look for; if found, 
all states up to that state will be popped. The POP_BACK_STACK_INCLUSIVE flag can be used t
o control whether the named state itself is popped. If null, only the top state is popped. 
var2:int: Either 0 or POP_BACK_STACK_INCLUSIVE 
//结合参数+自身实践来看,大致意思是,如果第2个参数var2为POP_BACK_STACK_INCLUSIVE ,则移除该var1上方(包括var1)的所有栈元素。

----------------------------------------------------------------------------------------------------------------

public abstract void popBackStack(int var1, int var2);
Pop all back stack states up to the one with the given identifier. This function is asynchronous 
-- it enqueues the request to pop, but the action will not be performed until the application returns to its event loop.
//弹出指定id上所有的栈元素
//int var1 :指的是transaction.commit()返回的id值
//int var2 :默认值为0或FragmentManager.POP_BACK_STACK_INCLUSIVE

----------------------------------------------------------------------------------------------------------------

public abstract boolean popBackStackImmediate()

public abstract boolean popBackStackImmediate(@Nullable String var1, int var2);

public abstract boolean popBackStackImmediate(int var1, int var2);
//这三个方法倒是跟上面的差不多,只不过好像是同步的

总结下上面的方法

  • 当var2为0时,给我更多的感觉是,出栈=回退到该transition操作后
  • 当var2为1时,给我更多的感觉是,出栈=回退到该transition操作前
  • popBackStack(String var1,int var2),当var1为null,var2为0时,移除栈顶元素;
    var1为null,var2为1时,清空回退栈所有元素
  • popBackStackImmediate()系列的方法没有使用过,大家可以自己试试

以上总结自,3个Fragment,大家可以试试更多的Fragment看看效果如何


Fragment通讯

  • Fragment与Fragment之间通讯
  • Fragment与Activity之间通讯
  • Activity与Fragment之间通讯

先来说几种通用的方式吧

  • Broadcast (广播)
  • EventBus、RxBus等
  • (这两种方式打算另外开一章单独来讲,这里就不细谈了)
Fragment与Activity通讯
  1. 直接调用 ((MainActivity)getActivity).callback(value)
    在MainActivity中定义一个callback的函数,Fragment中通过((MainActivity)getActivity)转成MainActivity类型,直接调用callback方法就可以了,不过这样的写法也就相当于这个Fragment与这个Activity绑死了,很难复用了。
  2. 定义接口(也是最常用的一种方法),写法也挺简单的大家看看就好
public class MyFragment extends Fragment {

    private static final String TAG = "MyFragment";

    private MyFragmentCallBack myFragmentCallBack;

    public interface MyFragmentCallBack {
        void callBack();
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof MyFragmentCallBack) {
            myFragmentCallBack = (MyFragmentCallBack) context;
            //在需要的时候调用  myFragmentCallBack.callBack(),Activity就会执行对应的代码了
            myFragmentCallBack.callBack();
        }else {
            throw new RuntimeException(context.toString()
                    + " must implement MyFragmentCallBack");
        }
    }
}

Activity中:

public class MainActivity extends AppCompatActivity implements MyFragment.MyFragmentCallBack{

    private static final String TAG = "MainActivity";

    @Override
    public void callBack() {
        Log.d(TAG, "callBack: 开始执行");
    }
Activity与Fragment通讯
  1. 可以在Activity中保存Fragment实例,直接调用Fragment的方法
  2. 使用getSupportFragmentManager() .findFragmentByTaggetSupportFragmentManager().findFragmentById可以获取到Fragment实例

findFragmentById:只适用于静态加载Fragment
关于findFragmentByTag获取到Fragment为null的可能性:

  1. transaction commit以后不可立刻使用findFragmentByTag,此时transaction操作不一定立即被执行,find出来的Fragment有可能为null
  2. v4包与app包需要导对
 public class MainActivity extends AppCompatActivity  {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d(TAG, "onCreate: ");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState == null) {
            getSupportFragmentManager()
                    .beginTransaction()
                    .add(R.id.framelayout, UserInfoFragment.newInstance("value"), "UserInfoFragment")
                    .commit();
            //这行代码会报NullPointerException,输出的Fragment为null
            Log.d(TAG, "onCreate: " + getSupportFragmentManager()
                    .findFragmentByTag("UserInfoFragment")
                    .getClass()
                    .getName());
        }

    }

Fragment之间通讯

这个其实就是上面两种通讯方式结合一下,Fragment调Activity,然后Activity再调Fragment就可以了

总结

  • 由于Fragment是3.0以后导入的
    3.0之前想使用的需要用android.support.v4.app.Fragment这个包,对应getSupportFragmentManager
    3.0之后也可使用android.app.Fragment,对应getFragmentManager(本文就没使用这个包了)

你可能感兴趣的:(关于android Fragment那些事)