一.为啥需要它?
Fragment表示碎片、片段。其目的是为了解决不同屏幕分辩率的动态和灵活UI设计。大屏幕如平板,小屏幕如手机,平板电脑的设计使得其有更多的空间来放更多的UI组件,而多出来的空间存放UI使其会产生更多的交互,从而诞生了fragments。小屏幕可能每次显示一个Fragment,而大屏幕则可以显示两个或更多Fragment。
Fragment必须被写成可重用的模块。因为fragment有自己的layout,自己进行事件响应,拥有自己的生命周期和行为,所以你可以在多个activity中包含同一个Fragment的不同实例。这对于让你的界面在不同的屏幕尺寸下都能给用户完美的体验尤其重要。
二.怎样使用它?
Android平板的Setting界面就是很好的案例,左边显示列表,右边显示对应列表项的详细内容。就像这样:
在小屏幕的设备上,一屏只能显示列表或者内容,所以就需要分开显示,就像这样:
1.设计布局文件
准备两套资源分别放到文件夹res/layout,res/layout-large里面即可。
res/layout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".app.fragment.TrainingActivity">
<fragment
android:name="com.wujingchao.android.demo.app.fragment.TrainingListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
res/layout-large:
<?xml version="1.0" encoding="utf-8"?>
<com.wujingchao.android.demo.supportLibrary.percentlayout.PercentLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<fragment
app:layout_widthPercent="30%"
android:tag="list"
android:layout_height="match_parent"
android:name="com.wujingchao.android.demo.app.fragment.TrainingListFragment"/>
<fragment
app:layout_widthPercent="70%"
android:tag="detail"
android:layout_height="match_parent"
android:name="com.wujingchao.android.demo.app.fragment.TrainingDetailFragment"/>
</com.wujingchao.android.demo.supportLibrary.percentlayout.PercentLinearLayout>
2.判断加载了哪个布局文件
当我们把fragment标签写入了布局文件,那么在LayoutInflater加载布局的时候会实例化Fragment,把Fragment#onCreateView加载的View作为子View放到ViewTree里,并且会把对应的fragment放到Activity#FragmentController里面同一管理,让Fragment拥有自己的声明周期。
所以在Activity#setContentView之后,我们就可以使用FragmentManager查找tag为detail的Fragment是否存在,就可以判断加载的是哪一个布局了。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_training);
originTitle = getTitle().toString();
fragment = getSupportFragmentManager().findFragmentByTag("detail");
large = fragment != null;
}
3.Activity与Fragment通信,Fragment与Activity通信
TrainingListFragment与TrainingDetailFragment的职责是单一的,ListFragment显示列表内容,DetailFragment显示具体的内容,他们都不知道对方的存在。
- Fragment与Activity通信
当ListFragment列表项点击的时候,就需要通知Activity去做相应的操作,就涉及了Fragment需要与Activity通信,可以在getActivity里面获取对应的Activity,并强转为具体的Activity,调用对应的方法。这里有一个耦合性很大的地方就是,获取了指定的Activity。可以在Fragment里面定义一个接口,让对应的Activity实现该接口。
public class TrainingListFragment extends ListFragment implements AdapterView.OnItemClickListener {
//...
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Object o = getActivity();
if(OnItemClickListener.class.isInstance(o)) {
((OnItemClickListener)o).OnItemClick(position,adapter.getItem(position));
}
}
interface OnItemClickListener {
void OnItemClick(int position,String title);
}
}
- Activity与Fragment通信
在Activity实现的OnItemClick方法里面就需要根据前面加载的布局文件判断做怎样的操作。
如果当前加载的布局是适合手机显示的,那么页面只有一个ListFragment,那么就需要加载DetailFragment,并且将ListFragment加入过Framgnet的返回栈(Back Stack)里面。
如果当前加载的布局是适合平板显示的,那么页面有两个Fragment,就需要通知DeatailFragment更新内容,就涉及到了Activity与Fragment通信。可以通过FragmentManager#findFragmentByTag或者FragmentManager#findFragmentById找到对应的Fragment调用方法,这里同样可以使用接口来减小耦合性,简单起见直接强转对应类型了:
public class TrainingActivity extends BaseActivity implements TrainingListFragment.OnItemClickListener{
//...
@Override
public void OnItemClick(int position,String title) {
if(large) {
TrainingDetailFragment trainingDetailFragment = (TrainingDetailFragment) fragment;
trainingDetailFragment.setDataPosition(position);
}else {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.replace(android.R.id.content,TrainingDetailFragment.newInstance(position));
ft.addToBackStack(null);
ft.commit();
setTitle(title);
}
}
}
另外需要注意导入Fragment的包,Fragment在support v4 library和android.jar里面都存在,需要统一,并且获取的FragmentManager也要统一,否则会出现莫名其妙的问题,比如返回栈不起作用。
三.它的原理是什么?
暂不知道
四.附加知识有哪些?
1.Fragment生命周期
onCreate->Fragment onAttach->Fragment onCreate->Fragment onCreateView->Fragment onActivityCreate
->start->Fragment start->resume->Fragment resume->Fragment onPause->onPause->
Fragment stop->onStop->Fragment onDestoryView->Fragment onDestory->Fragment onDetach
->onDestroy
2.Fragment静态使用
- 写好Frament的xml布局文件
- 编写Fragment的实现类,可以通过继承Fragment或者其之类来实现。
- 在常用的布局文件中把Fragment作为控件进行引用。
<?xml version="1.0" encoding="utf-8"?>
<com.wujingchao.android.demo.supportLibrary.percentlayout.PercentLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<fragment
app:layout_widthPercent="30%"
android:tag="list"
android:layout_height="match_parent"
android:name="com.wujingchao.android.demo.app.fragment.TrainingListFragment"/>
<fragment
app:layout_widthPercent="70%"
android:tag="detail"
android:layout_height="match_parent"
android:name="com.wujingchao.android.demo.app.fragment.TrainingDetailFragment"/>
</com.wujingchao.android.demo.supportLibrary.percentlayout.PercentLinearLayout>
注意事项:
1.静态引用fragment的布局文件中一定要有ID,不然会报错。
2.如果页面代码继承自Activity,Fragment类就必须继承自android.app.Fragment,不能使用android.support.v4.app.Fragment,否则会报错。如果页面代码继承自AppCompatActivity或FragmentActvity,那么都可以使用。
3.Fragment动态使用
- 前两个步骤和静态使用一样
- 通过获得FragmentManager获取事务FragmentTransaction
FragmentManager fragmentManager= getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
FragmentOne fragmentOne=new FragmentOne();
fragmentTransaction.replace(R.id.content,fragmentOne);
fragmentTransaction.commit();
可以让我们在代码当中动态的决定加载哪些Fragment显示出来。这里我们需要重点关注的是FragmentTransaction对象。除了例子当中使用的add操作以外,它还有replace,hide,show,remove等操作,下面就对这几种方法一一进行解释。
1)add(int containerViewId, Fragment fragment)
这个方法是将fragmen添加到我们指定id的layout中.
2)hide(Fragment fragment)和show(Fragment fragment)
隐藏或者显示指定的fragment,类似于我们在View中经常使用的setVisibly方法,需要注意的是,这里的hide和show仅仅只是让fragment显示和隐藏,不会对fragment进行销毁,甚至我们在hide的时候fragment的onPause方法都没有被调用。
3)remove(Fragment fragment)
会将fragment移除,如果被移除的Fragment没有添加到回退栈,该Fragment会同时被销毁。
4)replace(int containerViewId, Fragment fragment)
replace方法是用来进行替换的,实际上也就是对指定的layout id先remove掉其fragment,然后再add上去我们指定的fragment的一种组合操作。
5)detach()
会将view从UI中移除,和remove()不同,此时fragment并没有与Activity断绝关系,所以生命周期的onDestroy方法和onDetach方法并没有被调用
6)attach()
重建view视图,附加到UI上并显示,如果调用完detach方法后再来调用该方法的话不会去走onAttach和onCreate方法。
需要注意的是,我们在进行了上述的各种操作以后一定要调用commit方法提交事务才能生效。虽然我没有研究过源码里针对这一段是怎样实现的,但是可以类比数据库的事务操作。当系统因为某种不可抗力而终端了操作就需要进行回滚,至于不回滚会发生什么,需要日后深入源码中进行研究才能得知,在这里我就不胡乱进行猜测免得误导大家。