1. Fragment概述
1.1 介绍
Fragment是一种可以嵌入在活动中的UI片段,能够让程序更加合理和充分地利用大屏幕的空间,出现的初衷是为了适应大屏幕的平板电脑,可以将其看成一个小型Activity,又称作Activity片段
- Fragment依赖于Activity,不能独立存在
- 一个Activity可以有多个Fragment
- 一个Fragment可以被多个Activity重用
- Fragment有自己的生命周期,并能接收输入事件
- 可以在Activity运行时动态地添加或删除Fragment
Fragment的优势:
- 模块化(Modularity):我们不必把所有代码全部写在Activity中,而是把代码写在各自的Fragment中。
- 可重用(Reusability):多个Activity可以重用一个Fragment。
- 可适配(Adaptability):根据硬件的屏幕尺寸、屏幕方向,能够方便地实现不同的布局,这样用户体验更好。
1.2 Fragment基本生命周期
image.pngFragment的几种状态
-
Fragment和activity一样,也是有四种状态
活动状态:Resumed
当前Fragment位于前台,用户可见,可以获得焦点; -
暂停状态: Paused
另一个Activity处于前台并拥有焦点, 但是该Fragment所在的Activity仍然可见(前台Activity局部透明或者没有覆盖整个屏幕),不过不能获得焦点; -
停止状态:Stopped
活动进入停止状态,相关联碎片就会进入停止状态,或者通过FragmentTransaction
的remove()
、replace()
方法将碎片从从活动中移除,但如果在事务提交之前调用addToBackStack()
方法,这时的碎片也会进入到停止状态 -
销毁状态:当活动被销毁,相关联碎片进入销毁状态。或者调用
FragmentTransaction
的remove()
、replace()
方法将碎片从活动中移除,但在事务提交之前并没有调用addToBackStack()
方法,碎片也会进入到销毁状态
2.Fragment和Activity的对比
(1)活动和碎片之间的对比图
image.png(2)由上图可以看出,Fragment比Activity多了几个额外的生命周期回调方法:
- onAttach() 当碎片和活动建立关联时调用。(获得activity的传递的值)
- onCreateView() 为碎片创建视图调用就是加载布局时。
- onActivityCreated() 确保与碎片相关联的活动一定已经创建完毕的时候调用。
- onDestroyView() 当与碎片的视图被移除的时候调用。
- onDetach()当碎片与活动解除关联的时候调用。
2. Fragment使用
使用Fragment有两种方式,分别是静态加载和动态加载
2.1 静态加载
关于静态加载的流程如下:
- 定义Fragment的xml布局文件
- 自定义Fragment类,继承Fragment类或其子类,同时实现onCreate()方法,在方法中,通过inflater.inflate加载布局文件,接着返回其View
- 在需要加载Fragment的Activity对应布局文件中
<fragment>
的name属性设为全限定类名,即包名.fragment - 最后在Activity调用setContentView()加载布局文件即可
示例:
- 定义Fragment布局,新建left_fragment.xml和right_fragment.xml文件
<?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">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Button" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#00ff00"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="20sp"
android:text="this is Fragment" />
</LinearLayout>
- 自定义Fragment类,继承Fragment或其子类,重写
onCreateView()
,在方法中调用inflater.inflate()
方法加载Fragment布局文件,接着返回加载的view对象
public class LeftFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.left_fragment, container,false);
return view;
}
}
public class RigthFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.right_fragment, container, false);
return view;
}
}
- 在需要加载Fragment的Activity对应的布局文件中添加Fragment标签
<fragment
android:id="@+id/left_fragment"
android:name="com.vivo.a11085273.secondfragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/right_fragment"
android:name="com.vivo.a11085273.secondfragmenttest.RigthFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
/>
注意:
xml中fragment
一定要写id(left_fragment
/right_fragment
),否则,会出现如下错误:
aused by: java.lang.IllegalArgumentException: Binary XML file line #49: Must specify unique android:id, android:tag, or have a parent with an id for com.xxx.XXXFragment
- 在Activity的
onCreate()
方法中调用setContentView()
加载布局文件即可
2.2 动态加载Fragment
动态加载Fragment的流程如下:
- 获得FragmentManager对象,通过
getSupportFragmentManager()
- 获得FragmentTransaction对象,通过
fm.beginTransaction()
- 调用
add()
方法或者repalce()
方法加载Fragment
; - 最后调用
commit()
方法提交事务
简单示例:
-
同静态加载一样,首先定义Fragment的布局和类,修改主布局文件,不指定
<fragment>
标签的name属性。 -
实现Fragment调用
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(this);
replaceFragment(new RigthFragment());
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
replaceFragment(new AnotherRightFragment());
break;
default:
break;
}
}
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction(); // 开启一个事务
transaction.replace(R.id.right_layout, fragment);
transaction.commit();
}
}
2.3 使用注意点
- Fragment的
onCreateView()
方法返回Fragment的UI布局,需要注意的是inflate()的第三个参数是false,因为在Fragment内部实现中,会把该布局添加到container中,如果设为true,那么就会重复做两次添加,则会抛如下异常:
Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child’s parent first.
- 如果在创建Fragment时要传入参数,必须要通过
setArguments(Bundle bundle)
方式添加,而不建议通过为Fragment添加带参数的构造函数,因为通过setArguments()方式添加,在由于内存紧张导致Fragment被系统杀掉并恢复(re-instantiate)时能保留这些数据
可以在Fragment的onAttach()
中通过getArguments()
获得传进来的参数。如果要获取Activity对象,不建议调用getActivity(),而是在onAttach()中将Context对象强转为Activity对象
public class Fragment1 extends Fragment{
private static String ARG_PARAM = "param_key";
private String mParam;
private Activity mActivity;
public void onAttach(Context context) {
mActivity = (Activity) context;
mParam = getArguments().getString(ARG_PARAM); //获取参数
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_1, container, false);
TextView view = root.findViewById(R.id.text);
view.setText(mParam);
return root;
}
public static Fragment1 newInstance(String str) {
Fragment1 frag = new Fragment1();
Bundle bundle = new Bundle();
bundle.putString(ARG_PARAM, str);
fragment.setArguments(bundle); //设置参数
return fragment;
}
}
-
动态加载
Fragment
中,FragmentTransaction
类提供了方法完成增删等操作,完成后调用FragmentTransaction.commit()
方法提交修改 -
transaction.add():
往Activity
里面添加一个片段 -
transaction.remove():
从Activity
中移除一个Fragment
,如果被移除的Fragment
没有添加到回退栈,这个Fragment
实例将会被销毁 -
transaction.replace():
使用另一个Fragment
替换当前的,实际上是remove()
然后add()
的合体 -
transaction.hide():
隐藏当前Fragment
,仅不可见,不会销毁 -
transaction.show():
显示之前隐藏的Fragment
-
detach():
会将view从UI中移除,和remove()
不同,此时fragment
的状态依然由FragmentManager
维护 -
attach():
重建view视图,附加到UI上并显示。
commit方法一定要在Activity.onSaveInstance()之前调用
commit()
操作是异步的,内部通过mManager.enqueueAction()
加入处理队列。对应的同步方法为commitNow()
,commit()
内部会有checkStateLoss()
操作,如果开发人员使用不当(比如commit()
操作在onSaveInstanceState()
之后),可能会抛出异常,而commitAllowingStateLoss()
方法则是不会抛出异常版本的commit()
方法,但是尽量使用commit()
,而不要使用commitAllowingStateLoss()
。
-
FragmentManager拥有
回退栈(BackStack)
,类似于Activity的任务栈,如果添加了该语句,就把该事务加入回退栈,当用户点击返回按钮,会回退该事务(回退指的是如果事务是add(frag1),那么回退操作就是remove(frag1));如果没添加该语句,用户点击返回按钮会直接销毁Activity。 -
Fragment常见异常
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
出现原因:commit()在onSaveInstanceState()后调用。
由于onSaveInstanceState()
在onPause()
之后,onStop()
之前调用。onRestoreInstanceState()
在onStart()
之后,onResume()
之前,因此避免出现该异常的方案有:
- 不要把Fragment事务放在异步线程的回调中
- 逼不得已时使用
commitAllowingStateLoss()
3.1 回退栈
类似Android系统为Activity维护一个任务栈,我们也可以通过Activity维护一个回退栈来保存每次Fragment事务发生的变化。如果你将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的Fragment。
一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前Activity
添加一个Fragment事务到回退栈:
FragmentTransaction.addToBackStack(String)
简单示例:
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout, fragment);
transaction.addToBackStack(null); //添加进回退栈
transaction.commit();
}
replace是remove和add的合体,并且如果不添加事务到回退栈,前一个Fragment实例会被销毁。这里很明显,我们调用transaction.addToBackStack(null)
;将当前的事务添加到了回退栈,所以FragmentOne实例不会被销毁,但是视图层次依然会被销毁,即会调用onDestoryView和onCreateView
如果不希望视图重绘,可以将原来的Fragment隐藏:
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.hide(this);
transaction.add(R.id.right_layout, fragment);
transaction.addToBackStack(null); //添加进回退栈
transaction.commit();
}
Fragment与Activity通信
image.pngFragment与Activity的通信交互如上图所示:
-
如果Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
-
如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过
getFragmentManager.findFragmentByTag()
或者findFragmentById()
获得任何Fragment实例,然后进行操作 -
在Fragment中可以通过
getActivity
得到当前绑定的Activity的实例,然后进行操作。
备注:
-
如果在Fragment中需要Context,可以通过
getActivity()
,如果该Context需要在Activity被销毁后还存在,则使用getActivity.getApplicationContext()
;
考虑Fragment的重复使用问题,降低与Activity的耦合,Fragment操作应该由它的管理者Activity决定。
4.1 传递数据给Fragment
步骤流程:
-
在Activity中创建Bundle数据包,调用
Fragment
实例的setArguments()
,将Bundle
数据包传给Fragment
-
Fragment调用
getArguments()
获得Bundle
对象,然后进行解析就可以
简单示例:
//创建Fragment对象,并通过Bundle对象传递值(在onCreate方法中)
MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle();
bundle.putString("key", values);
fragment.setArguments(bundle);
//(在Fragment类中的onCreateView方法中)
Bundle bundle = this.getArguments();
if (bundle != null)
{
String str = bundle.getString("key");
}
TextView textView = new TextView(getActivity());
textView.setText("上上下下的享受");//是电梯,别误会
4.2 传递数据给Activity
步骤流程:
- 在Fragment中定义一个内部回调接口,再让包含该Fragment的Activity实现该回调接口
- Fragment通过回调接口传数据
简单示例:
- 首先在Fragment中定义一个接口(定义抽象方法,传什么类型参数)
/*接口*/
public interface Mylistener{
public void thanks(String code);
}
- Fragment类中定义该接口
private Mylistener listener;
- 在onAttach方法中,将定义的该接口强转为activity类型
@Override
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
listener=(Mylistener) activity;
super.onAttach(activity);
}
- Activity只需实现该接口并重写该方法即可
@Override
public void thanks(String code) {
// TODO Auto-generated method stub
Toast.makeText(this, "已收到Fragment的消息:--"+code+"--,客气了", Toast.LENGTH_SHORT).show();;
}
除了接口回调,还可以使用EventBus进行交互通信。
5. Fragment间通信
5.1 setArguments()
示例:
- 在Fragment B中新建一个函数:newInstance()接收传过来的参数
public static Fragment2 newInstance(String text) {
Fragment2 fragment = new Fragment2();
Bundle args = new Bundle();
args.putString("param", text);
fragment.setArguments(args);
return fragment;
}
- 在Fragment B的onCreateView中获取参数
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment2, container, false);
if (getArguments() != null) {
String mParam1 = getArguments().getString("param");
TextView tv = (TextView)view.findViewById(R.id.textview);
tv.setText(mParam1);
}
return view;
}
- 在Fragment A中,调用Fragment B时,通过newInstance函数获取实例并传递参数:
public class Fragment1 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment1, container, false);
Button btn = (Button)view.findViewById(R.id.load_fragment2_btn);
btn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(final View view) {
Fragment2 fragment2 = Fragment2.newInstance("从Fragment1传来的参数");
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(R.id.main_layout, fragment2);
transaction.addToBackStack(null);
transaction.commit();
}
});
return view;
}
}
5.2 同Activity不同Container的Fragment交互
这种情况有三中方法解决:
方法一:直接在Activity中操作
直接在Activity中找到对应控件的实例,然后直接操控即可
方法二:直接在Fragment中操作
这里有两个问题:如何获取自己控件的引用?如何获取其他Fragment页控件的引用?
- 首先获取自己控件的引用
可以在onCreateView()
中获取
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment1, container, false);
listView = (ListView)rootView.findViewById(R.id.list);//获取自己视图里的控件引用,方法一
return rootView;
}
在onCreateView()
中,还没有创建视图,所以在这里如果使用getView()
方法将返回空
另一种方法是在onActivityCreated()
中获取,其回调在onCreate()
执行后再执行
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
listView = (ListView) getView().findViewById(R.id.list);//获取自己视图里的控件引用,方法二
}
- 获取其它Fragment页控件引用方法
获取Activity资源,须等Activity创建完成后,必须放在onActivityCreated()
回调函数中
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mFragment2_tv = (TextView) getActivity().findViewById(R.id.fragment2_tv);//获取其它fragment中的控件引用的唯一方法!!!
}
总的实现示例如下:
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mFragment2_tv = (TextView) getActivity().findViewById(R.id.fragment2_tv);//获取其它fragment中的控件引用的唯一方法!!!
listView = (ListView) getView().findViewById(R.id.list);//获取自己视图里的控件引用,方法二
ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mStrings);
listView.setAdapter(arrayAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String str = mStrings[position];
mFragment2_tv.setText(str);
}
});
}
方法三:在各自Fragment中操作
方法二在Fragment A中操作了Fragment B,违背模块分离思想,应通过Activity将其分离
在Activity中可以直接通过FragmentManager.findFragmentById()
获取Fragment
实例
示例:
- 在Fragment2设置TextView函数
public class Fragment2 extends Fragment {
private TextView mTv;
…………
public void setText(String text) {
mTv.setText(text);
}
}
- 在Fragment1 中定义处理方式
- 定义接口与变量
private titleSelectInterface mSelectInterface;
public interface titleSelectInterface{
public void onTitleSelect(String title);
}
-
接口变量赋值
接口给Activity使用,在Activity中给接口变量赋值,在Fragment与Activity关联时,需要强转
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mSelectInterface = (titleSelectInterface) activity;
} catch (Exception e) {
throw new ClassCastException(activity.toString() + "must implement OnArticleSelectedListener");
}
}
- 调用接口变量
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
listView = (ListView) getView().findViewById(R.id.list);//获取自己视图里的控件引用,方法二
ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mStrings);
listView.setAdapter(arrayAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String str = mStrings[position];
mSelectInterface.onTitleSelect(str);
}
});
}
- 在Activity中实现接口
public class MainActivity extends FragmentActivity implements Fragment1.titleSelectInterface {
……
@Override
public void onTitleSelect(String title) {
FragmentManager manager = getSupportFragmentManager();
Fragment2 fragment2 = (Fragment2)manager.findFragmentById(R.id.fragment2);
fragment2.setText(title);
}
}
网友评论