Android Fragment 详解

Fragment是Activity的碎片,起初Fragment的出现是为了适配大屏平板出现的,但是在小屏幕手机上也经常使用到,经常使用的控件组合是ViewPager+Fragment,ViewPager不是本章的重点,本章主要针对Fragrant讲解。

(1)生命周期
图片.png

Fragment的生命周期要比Activity多几个方法,下面简单说明一下:

  • onAttach(Activity)

Activity和Fragment发生关联时调用,也就是说Activity和Fragment之间必须存在依赖关系,它的作用就是将Activity和Fragment绑定在一起。只要执行了这个方法,Activity对象就会传递到Fragment,所以在Fragment中可以直接调用getActivity()来获取当前Fragment对应的Activity对象,如果没有执行onAttach(Activity)方法,将获取不到Activity对象。

  • onCreateView(LayoutInflater, ViewGroup,Bundle)

创建Fragment的视图。

  • onActivityCreated(Bundle)

当Activity的onCreate方法返回时调用。

  • onDestoryView()

与onCreateView想对应,当该Fragment的视图被移除时调用。

  • onDetach()

与onAttach相对应,当Fragment与Activity关联被取消时调用。

(2)选择正确的导包
图片.png

如图所示,怎么选择Fragment的导包已经显而易见了,android.app中的Fragment已经过时,所以目前只能使用support.v4中的Fragment。

(3)如何去创建Fragment视图

首先,我们需要新建一个类,继承于Fragment,然后重写onCreateView方法,默认情况下Fragment是没有视图的,所以我们需要自己新建一个视图,这里必须重写onCreateView新建视图。代码如下:

public class Demo1Fragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.activity_main, null);
        return view;
    }
}

有关LayoutInflater的使用,可以看一下这篇博客:LayoutInflater使用

(4)在Activity中添加Fragment
public class Demo1Fragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, null);
        return view;
    }

    public static Demo1Fragment newInstance() {
        Demo1Fragment fragment = new Demo1Fragment();
        return fragment;
    }
}


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

    //加载默认Fragment
    getSupportFragmentManager().beginTransaction().add(R.id.container, Demo1Fragment.newInstance()).commit();

}


<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity">

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

</RelativeLayout>

fragment1.xml

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


    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textSize="14sp"
        android:visibility="visible"
        android:background="@drawable/ripple_ani"
        android:layout_centerHorizontal="true"
        android:text="点击"/>

</RelativeLayout>
(6)常用方法介绍
  • getSupportFragmentManager(): 这是support.v4特有的方法,以前android.appgetFragmentManager()方法已经过时,大家不要再使用了。
  • add: 添加一个Fragment

常用add方法如下:

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

第一个参数是:装载Fragment容器的ID;
第二个参数是:Fragment对象;
第三个参数是:Fragment的Tag;

如果您在添加Fragment时给它设置了Tag,比如:

getSupportFragmentManager().beginTransaction().add(R.id.container, Demo1Fragment.newInstance(), "demo1").commit();

这样Demo1Fragment的Tag为demo1,我们可以根据Tag获取fragment对象,如下:

Demo1Fragment frag = (Demo1Fragment) getActivity().getSupportFragmentManager().findFragmentByTag("demo1");
  • hide: 隐藏一个Fragment,当您想隐藏一个Fragment时,可以使用这个方法。

      getSupportFragmentManager().beginTransaction()
              .add(R.id.container, fragment1)
              .add(R.id.container, fragment2)
              .add(R.id.container, fragment3)
              .hide(fragment2)
              .commit();
    
  • remove: 移除一个Fragment,当您想移除一个Fragment时,可以使用这个方法。

      getSupportFragmentManager().beginTransaction()
              .add(R.id.container, fragment1)
              .add(R.id.container, fragment2)
              .add(R.id.container, fragment3)
              .remove(fragment2)
              .commit();
    
  • replace: 替换一个Fragment

      getSupportFragmentManager().beginTransaction()
              .replace(R.id.container, fragment2)
              .commit();
    
  • show: 显示Fragment

  • commit: 提交一个事务。

我们都知道,当Fragment状态发生改变时(比如add、remove、hide、replace操作)都会执行commit方法提交事务,但是commit方法只能在onSaveInstanceState之前执行,如果在onSaveInstanceState之后执行就会报以下错误:

图片.png

为了解决这个问题,可以从三方面考虑:
[一]: 是都可以将commit放到onSaveInstanceState之前执行;
[二]:尽量避免在异步线程中执行Fragment事务操作(commit);
[三]: 将commit改成commitAllowingStateLoss

  • addToBackStack: 回退栈

我们知道Activity有任务栈,用户通过startActivity将Activity加入栈,点击返回按钮将Activity出栈。Fragment也有类似的栈,称为回退栈(Back Stack),回退栈是由FragmentManager管理的。默认情况下,Fragment事务是不会加入回退栈的,如果想将Fragment事务加入回退栈,则可以加入addToBackStack("")。如果没有加入回退栈,则用户点击返回按钮会直接将Activity出栈;如果加入了回退栈,则用户点击返回按钮会回滚Fragment事务。代码如下:

            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, Demo1Fragment.newInstance(), "demo1")
                    .addToBackStack("a")
                    .commit();
(7)Fragment通信

Fragment通信分为两个方向:Activity-->FragmentFragment-->Activity

Activity访问Fragment比较简单,大致有三种方式:

[方式一]: 我们可以在创建Fragment对象的时候使用setArguments传递参数,代码如下:

public static Demo1Fragment newInstance(String str) {
    Demo1Fragment fragment = new Demo1Fragment();
    Bundle bundle = new Bundle();
    bundle.putString("key", str);
    fragment.setArguments(bundle);
    return fragment;
}

[方式二]: 如果Activity中存在Fragment对象,那么可以使用Fragment对象调用Fragment中public方法。

[方式三]: 使用findFragmentByTag方法

可以通过以下方式获取某Fragment对象,然后再调用Fragment中的方法,或就想数据从Activity传递到Fragment。

getActivity().getSupportFragmentManager().findFragmentByTag("demo1");

但是,如果在Fragment中想调用Activity中的方法,或者将数据从Fragment传递到Activity该怎么做呢?

[方式一]: 通过广播

在Activity中注册广播,在Fragment中发送广播可以轻松实现Fragment访问Activity,但是不推荐这种方式,因为广播是非常消耗性能的。

[方式二]: 自定义监听方式

第一步,在Fragment中定义接口:

public interface FragmentClickListener {

    void doString();

}

第二步,在Activity中实现FragmentClickListener 接口

第三步,调用接口中的方法

    if(getActivity() instanceof FragmentClickListener){
         ((FragmentClickListener) getActivity()).doString();
    }

只要完成以上三步就可以实现Fragment访问Activity。

[方式三]: EventBus实现

这个其实本质上就是一个监听,这里不做具体说明了。

[方式四]: RxBus

RxbBus是RxJava模仿EventBus实现的,具体代码如下:

RxBus类

public class RxBus {

    private FlowableProcessor<Object> bus;

    private RxBus() {
        //把非线程安全的PublishSubject包装成线程安全的SerializedSubject
        bus = PublishProcessor.create().toSerialized();
    }

    public static RxBus getDefault() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        public static volatile RxBus INSTANCE = new RxBus();
    }

    /**
     * 发送事件
     *
     * @param event 事件对象
     */
    public void post(Object event) {
        if (bus.hasSubscribers()) {
            bus.onNext(event);
        }
    }

    /**
     * 监听事件
     *
     * @return 特定类型的Observable
     */
    public Flowable<Object> observe() {
        return bus;
    }

    /**
     * 监听事件
     *
     * @param event 事件对象
     * @param <T>       事件类型
     * @return 特定类型的Observable
     */
    public <T> Flowable<T> observe(Class<T> event) {
        return bus.ofType(event);
    }
}

注册监听

    RxBus.getDefault().observe(String.class).subscribe(new Consumer<String>() {

        @Override
        public void accept(String s) throws Exception {
            Log.d("aaa", "我收到的数据是:"+s);
        }
    });

发送数据

    RxBus.getDefault().post("我是数据,可以是字符串,也可以是一个任意对象");

[方式五]: 使用github框架FABridge

FABridge的使用也比较简单,具体使用方法可以查看githubFABridge

(8)DialogFragment的使用

DialogFragment是Android 3.0提出的,代替了Dialog,用于实现对话框。他的优点是:即使旋转屏幕,也能保留对话框状态(即使屏幕旋转了,对话框也不会消失)。
代码如下:

public class ProgressDialogFragment extends DialogFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); //消除Title区域
        setCancelable(false);  //点击外部不可取消
        View root = inflater.inflate(R.layout.fragment_dialog, container);
        return root;
    }

    public static ProgressDialogFragment newInstance() {
        return new ProgressDialogFragment();
    }
}

fragment_dialog.xml

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我是一个对话框"/>

</RelativeLayout>

显示对话框

    ProgressDialogFragment fragment = ProgressDialogFragment.newInstance();
    fragment.show(getActivity().getSupportFragmentManager(), "tag");

另外,当屏幕旋转的时候,Dialog就一定会消失吗?
答:屏幕旋转时,其生命周期是:onPause --> onSaveInstanceState --> onStop --> onDestroy --> onStart --> onRestoreInstanceState --> onResume,我们发现屏幕旋转时是先销毁当前的Activity,然后重启当前Activity,所以如果旋转的时候屏幕上的Dialog肯定会消失的。
但是有一种情况可以做到即使屏幕旋转了,Dialog也不会消失,只需要在AndroidManifest.xml配置文件中添加

android:configChanges="orientation|screenSize"

即可,这样屏幕旋转的时候就不会走生命周期了,不走生命周期的话当前Activity也不会销毁。

[本章完...]

推荐阅读更多精彩内容