Fragment是官方为Android大屏设备推出的一个特别的组件,为什么称为特别,因为他有自己的声明周期,但是他的生命周期依赖于其所属的Activity,你可以把Fragment当成Activity的一个界面的一个组成部分,甚至Activity的界面可以完全由不同的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的生命周期依赖于Activity,Fragment的声明周期比Activity要多几个步骤,通过下面这两个图我们就可以看到
我们重点说下几个新见的生命周期
onAttach:当Fragment与Activity发生关联时调用
onCreateView:此时创建Fragment的视图
onAcitivityCreated:在Activity的onCreate方法返回时调用
onDestroyView:移除Fragment视图使用此方法
onDetach:当Fragment和Activity取消关联时调用
上图中我们看到了从onDestroyView 到 onCreateView的一个环节,这个发生在 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恢复数据的方法。
看一下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传来的数据
类似与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的基本解析,最后附上结尾小例子的代码