Fragment基本解析

Fragment是官方为Android大屏设备推出的一个特别的组件,为什么称为特别,因为他有自己的声明周期,但是他的生命周期依赖于其所属的Activity,你可以把Fragment当成Activity的一个界面的一个组成部分,甚至Activity的界面可以完全由不同的Fragment组成

Fragment 介绍

在使用Fragment中,常用的有三个类,分别是Fragment、FragmentManager 和 FragmentTransaction,介绍一下他们

1、Fragment

Fragment 是Fragment组件类,Android中包含两个版本:
android.support.v4 fragment 和 android.app.Fragment

  • android.app.Fragment 是在Android3.0 之后出现的,也就是api11之后才能够使用,如果最低兼容大于api11 可以使用这个包,使用< fragment > 标签没有什么特殊的地方,使用Activity就可以

  • android.support.v4 fragment 主要考虑兼容向下兼容使用,最低可以兼容到Android1.6,这个使用< fragment > 标签的时候,对应的Activity类必须继承FragmentAcitivity ,否则会报错 Caused by: java.lang.ClassCastException


2、 FragmentManager

主要用于在Activity中操控Fragment,获得FragmentManager 对应不同版本的Fragment 有对应方式
getFragmentManager()
getSupportFragmentManager // v4包


3、 FragmentTransaction

对Fragment进行添加,移除,替换以及执行其他动作

  • transaction.add()
    往Activity中添加一个Fragment

  • transaction.remove()
    从Activity中移除一个Fragment,如果该Fragment没有添加到回退栈,这个Fragment实例将会被销毁。

  • transaction.replace()
    使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体

  • transaction.hide()
    隐藏了一个Fragment。这个针对add操作后的Fragment,操作会将视图隐藏起来,并不会销毁

  • transaction.show()
    显示之前隐藏的Fragment

  • addToBackStack(String name)
    将Fragment压入栈中

  • attach(Fragment fragment)
    重建view视图,附加到UI上并显示

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

  • commit()
    提交一个事务

  • commitAllowingStateLoss()
    和commit()类似,但允许在Activity保存状态后提交事务。这是危险的,因为提交事务可能会丢失

    详细查阅 API

Fragment 生命周期

Fragment的生命周期依赖于Activity,Fragment的声明周期比Activity要多几个步骤,通过下面这两个图我们就可以看到

Fragment基本解析_第1张图片Fragment基本解析_第2张图片

我们重点说下几个新见的生命周期

onAttach:当Fragment与Activity发生关联时调用
onCreateView:此时创建Fragment的视图
onAcitivityCreated:在Activity的onCreate方法返回时调用
onDestroyView:移除Fragment视图使用此方法
onDetach:当Fragment和Activity取消关联时调用

上图中我们看到了从onDestroyView 到 onCreateView的一个环节,这个发生在 Fragment 从回退栈返回到当前界面可视的情况下,关于回退栈下面会有解释


Fragment 使用

1、创建一个Fragment

接下来我们说一下如何创建一个Fragment ,创建Fragment有两种方式,第一种是静态的创建,另一种是动态的创建。 静态创建就是将Fragment的 < fragment> 标签放入Activity的 xml 布局文件中,然后通过Activity 直接显示这个Fragment

静态创建:
MainActivity.java

public class MainActivity extends AppCompatActivity {

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

MyFirstFragment.java

public class MyFirstFragment extends Fragment {

    private View view;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

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

}

activity_main.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <fragment
        android:id="@+id/fragment_first"
        android:name="app.com.example.hkxlegend.mypractice.MyFirstFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

LinearLayout>

fragment_first.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="第一个Fragment"
        android:textSize="25sp" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="click" />

LinearLayout>


上面就是静态的方式创建一个Fragment,如果需要动态的添加、更新、以及删除Fragment 我们就需要采用动态的方式去添加Fragment,下面我们还是这个例子看看如何动态的添加

动态创建:
activity_main.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

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

LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private Fragment mFragment;
    private FragmentManager fm;
    private FragmentTransaction ft;

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

        mFragment = new MyFirstFragment();
        fm = getSupportFragmentManager();
        ft = fm.beginTransaction();
        ft.replace(R.id.id_content, mFragment);
        ft.commit();

    }
}

Fragment代码和之前一样,同样也实现了这个效果。这里我们看到,Activity继承了AppCompatActivity,AppCompatActivity继承了FragmentAcitivity,因为这里使用的Fragment是v4包,所以对应也要通过getSupportFragmentManager来获取FragmentManager。通过这样的方式就可以动态的添加、删除、替换Fragment了。

但是,我们可以发现Activity每次启动一次或因为屏幕旋转等原因发生重新启动时,Fragment就会重新创建一遍。这是没有必要的,我们可以通过检查onCreate的参数Bundle savedInstanceState就可以判断,当前是否发生Activity的重新创建,默认的savedInstanceState会存储一些数据,包括Fragment的实例。所以我们来优化一下代码

MainAcitivity.java

public class MainActivity extends AppCompatActivity {

    private Fragment mFragment;
    private FragmentManager fm;
    private FragmentTransaction ft;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            fm = getSupportFragmentManager();
            ft = fm.beginTransaction();
            mFragment = new MyFirstFragment();
            ft.replace(R.id.id_content, mFragment);
            ft.commit();
        }

    }
}

现在无论进行多次旋转都只会有一个Fragment实例在Activity中。说道数据保存 Fragment 和 Activity 类似,Fragment也有onSaveInstanceState的方法,在此方法中进行保存数据,然后在onCreate、onCreateView或者onActivityCreated进行恢复都可以,这就是Fragment恢复数据的方法。


2、Activity与Fragment的通信

看一下Activity和Fragment是如何通信的,在Activity中,我们可以直接通过Fragment的引用来操控Fragment,如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例。
在Fragment中,我们可以通过getActivity()来获得所属Activity对象的引用,通过这个引用来使用Activity的数据从而达到通信的效果,如果需要长久引用可以使用getAcitivity().getApplication()来获得。

我们设想一个场景,如果一个Activity通过Intent方式跳转到当前Fragment的Activity中,并且Intent中传递了参数,那么我们的Fragment应该如何获取这些参数呢? 这时候我们可以通过上面那种方式getAcitivity()然后getIntent() 然后获取相应参数。这里存在一问题:这样Fragment和Activity就彻底绑定了,Fragment无法复用了,也就失去了Fragment的一个意义,并且确保参数被持久保存需要setArguments方式进行数据交互。

setArguments方法必须在fragment创建以后,也就是new一个Fragment之后;将Fragment添加给Activity前完成之前,也就是在beginTranscation操作之前来进行setArguments,我们先看一下变化后的MyFirstFragment和MainActivity

MyFirstFragment.java

public class MyFirstFragment extends Fragment {

    private View view;
    private Button button;
    private String entity;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Bundle bundle = getArguments();
        if(bundle!= null)
            entity = bundle.getString("data");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.fragment_first, null);
        button = (Button) view.findViewById(R.id.button_fragment);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getActivity(), "button response", Toast.LENGTH_SHORT).show();
            }
        });
        return view;
    }

    public static MyFirstFragment getInstance(String entity){
        Bundle bundle = new Bundle();
        bundle.putString("data",entity);
        MyFirstFragment mFragment = new MyFirstFragment();
        mFragment.setArguments(bundle);
        return mFragment;
    }

}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private Fragment mFragment;
    private FragmentManager fm;
    private FragmentTransaction ft;

    private String entity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        entity = getIntent().getStringExtra("data");

        if (savedInstanceState == null) {

            MyFirstFragment.getInstance(entity);

            ft = fm.beginTransaction();
            ft.replace(R.id.id_content, mFragment);
            ft.commit();
        }

    }
}

通过给 Fragment 添加getInstance方法,MainActivity将需要的参数传入,设置到bundle中,然后setArguments(bundle),FragmentTranscation提交事务后绑定了Fragment,最后在MyFragment的onCreate中进行获取MainActivity传来的数据


3、Fragment回退栈管理

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

那么如何添加一个Fragment事务到回退栈呢,使用这个方法
FragmentTransaction.addToBackStack(String)

我们调用ft.addToBackStack(null),会将当前的事务添加到了回退栈,那么Fragment的实例不会被销毁,但是视图层依然会被销毁,就像上面生命周期那张图,即会调用onDestoryView和onCreateView的那个循环。

下面我们通过一个例子来看一下整个流程,有一个Activity界面,通过Intent跳转传值到我们的OwnActivity,OwnActivity包含着可以通过手势滑动来切换两个Fragment界面,两个Fragment中的MyFirstFragment通过TextView可以显示传递来的数据,MySecondFragment中有个一EditText来检测我们回退栈的作用,为了节省代码,第一个Activity就不写出来了,他通过传递了一个“I’m from MainActivity” 来显示在第一个MyFirstFragment的界面上

我们来看一下承载两个Fragment的Activity布局:

activity_own.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

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

LinearLayout>

布局很简单,我们要通过动态加载的方式放入可以滑动替换的两个Fragment界面:MyFirstFragment和MySecondFragment,然后我们看一下OwnActivity

OwnActivity.java

package app.com.example.hkxlegend.mypractice;

import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;

/**
 * @since 2016
 */
public class OwnActivity extends AppCompatActivity {

    private MyFirstFragment firstFragment;
    private MySecondFragment secondFragment;
    private FragmentManager fm;
    private FragmentTransaction ft;

    private float current = 0;

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

        Bundle bundle = getIntent().getExtras();

        if (savedInstanceState == null) {
            firstFragment = new MyFirstFragment();
            secondFragment = new MySecondFragment();
            fm = getSupportFragmentManager();

            firstFragment.setArguments(bundle);

            ft = fm.beginTransaction();
            ft.add(R.id.own_content, firstFragment);
            ft.commit();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                current = event.getX();
                break;

            case MotionEvent.ACTION_UP:
                if (event.getX() - current < -200) {

                    //没有满足 added||attached||not hidden
                    if (!secondFragment.isVisible()) {
                        ft = fm.beginTransaction();
                        ft.replace(R.id.own_content, secondFragment);
                        ft.show(secondFragment);
                        ft.commit();
                    }

                } else if (event.getX() - current > 200) {

                    if (!firstFragment.isVisible()) {
                        ft = fm.beginTransaction();
                        ft.hide(secondFragment);
                        ft.add(R.id.own_content,firstFragment);
                        ft.commit();
                    }

                } else {

                }
                break;
        }
        return super.onTouchEvent(event);
    }
}

动过滑动来切换Fragment,为了让MySecondFragment中EditText能保存输入的东西,我们采用hide和show方式不去重新onCreateView。FragmentTranscation使用后需要重新beginTranscation一次,否则无法使用的,看一下效果
Fragment基本解析_第3张图片

以上就是关于Fragment的基本解析,最后附上结尾小例子的代码

你可能感兴趣的:(【Android,组件开发】)