Fragment是Android API中的一个类,它代表Activity中的一部分界面;你可以在一个Activity界面中使用多个Fragment,或者在多个Activity中重用某一个Fragment。本文将会介绍Fragment的基本使用、Fragment参数传递、与宿主Activity交互、以及Fragment与Toolbar继承。
第一、编写类ExampleFragment继承自Fragment
public class ExampleFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.example_fragment, container, false);
}
}
onCreateView()为Fragment生命周期中的一个方法,当第一次在Fragment上绘制UI时,系统回调这个方法。
下图为Fragment生命周期
第二、R.layout.example_fragment为ExampleFragment需要绘制UI的布局文件,这里很简单,是一个TextView。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
<TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="TextView in Fragment" android:textSize="22sp" android:gravity="center"/>
</LinearLayout>
第三、在Activity的布局文件中使用这个ExampleFragment
<LinearLayout 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" android:orientation="vertical" tools:context="com.czh.MainActivity">
<fragment android:name="com.czh.ExampleFragment" android:id="@+id/FragmentOne" android:layout_width="match_parent" android:layout_height="match_parent"/>
</LinearLayout>
有个需要注意的地方:每个fragment都需要一个唯一的标识。系统在资源紧缺或者横屏、竖屏切换都会发生宿主Activity重构,那么在重构的过程中会去恢复宿主Activity所管理的Fragment队列,这个过程需要用到每个Fragment的唯一标识。
有三种方法为fragment设置唯一标识:
OK, 当系统加载Activiy的Layout视图时,同时加载Fragment绑定的视图,并回调Fragment的onCreateView()方法,系统将fragment标签替换为onCreateView()方法返回的view。
首先,Activity的Layout文件修改如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.czh.MainActivity">
</LinearLayout>
这里给LinearLayout添加了一个ID,用作Fragment的容器ID。
下面是Activity动态添加Fragment代码
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
ft.add(R.id.fragment_container, fragment);
ft.commit();
}
}
FragmentManager:
为了在Activity中管理Fragment,我们需要FragmentManager实例,通过getFragmentManager()方法获取。可以通过FragmentManager完成如下操作:
FragmentTransaction:
Fragment最大的好处就是可以动态的添加, 删除, 替换等操作。每一组向Activity提交的变化称为事务,也就是FragmentTransaction对应的一些API。
FragmentTransaction常用API有:add(), remove(), replace()(就是先remove再add),最后为了使事务在Activity生效,需调用commit()方法。
在调用commit()方法之前,可以调用addToBackStack() 方法将事务添加到宿主Activity管辖Fragment后退栈中。这样用户通过点击后退键对Fragment进行导航。比如,使用remove()方法移除了Fragment,如果没有执行addToBackStack()方法,那么这个Fragment实例就会被销毁,用户无法通过后退键导航到这个Fragment。
回退栈举例如下:
//创建一个新的Fragment,并开启事务
ExampleFragment fragment = new ExampleFragment();
FragmentTransaction ft = getFragmentManager().beginTransaction();
//将容器中替换成当前fragment
//并把当前事务添加到回退栈
ft.replace(R.id.fragment_container, fragment);
ft.addToBackStack(null);
//提交事务
ft.commit();
防止Activity重构导致多个Fragment重叠:
Activity重构时,系统会自动重建该Activity所管辖的Fragment。如果按照上面的代码,当Activity发生重构时,多个Fragment就会发生重叠。
需要对代码做如下改进:
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getFragmentManager();
//没有为Fragment明确指明ID或者Tag,系统使用fragment容器的LayoutID来唯一标识Fragment
ExampleFragment fragment = (ExampleFragment) fm.findFragmentById(R.id.fragment_container);
//非空判断,防止出现多个Fragment重叠
if(fragment == null) {
FragmentTransaction ft = fm.beginTransaction();
fragment = new ExampleFragment();
ft.add(R.id.fragment_container, fragment);
ft.commit();
}
}
在启动Activity的时候,通过Intent可以给Activity传递参数。启动Fragment,如何传递参数呢?
通过Fragment.setArguments(Bundle)设置参数,通过Bundle bundle = getArguments()获取参数;
下面看代码:
public class ExampleFragment extends Fragment {
public static final String KEY = "ARGUMENT";
private String mArg;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//获取参数
Bundle bundle = getArguments();
if(bundle != null) {
mArg = bundle.getString(KEY);
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View contentView = inflater.inflate(R.layout.example_fragment, container, false);
TextView tv = (TextView) contentView.findViewById(R.id.tv);
tv.setText(mArg);
return contentView;
}
public static ExampleFragment createFragment(String argument)
{
ExampleFragment fragment = new ExampleFragment();
Bundle args = new Bundle();
args.putString(KEY, argument);
//为fragment传递参数
fragment.setArguments(args);
return fragment;
}
}
静态方法createFragment(String argument)用来在外部创建Fragment实例,在方法中首先创建了一个Bundle对象,将需要传递的参数存储到Bundle对象中,最后通过fragment.setArguments()方法将Bundle对象传递给Fragment。
在ExampleFragment 的onCreate()回调函数中使用getArguments()获取Bundle对象,之后通过Bundle对象获取参数内容。
下面是Activity中的代码:
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getFragmentManager();
ExampleFragment fragment = (ExampleFragment) fm.findFragmentById(R.id.fragment_container);
if(fragment == null) {
FragmentTransaction ft = fm.beginTransaction();
//给Fragment传递参数
ft.add(R.id.fragment_container, ExampleFragment.createFragment("argument"));
ft.commit();
}
}
注意:setArguments()必须在添加给Activity之前完成。也就是说必须在ft.add()方法之前调用Fragment的setArguments()方法。
向上一个Activity回传数据通过startActivityForResult(intent, requestCode)方法,以及onActivityResult()回调实现。向上一个Fragment回传数据也是通过类似的方法实现。
新的场景:
依然新闻的例子,ArticleListFragment中是新闻列表,ArticleContentFragment中是新闻内容。两个Fragment分别在两个Activity中。需求:当我们从ArticleContentFragment返回到ArticleListFragment时,需要带上数据。
先贴上效果图:
下面分析代码:
首先是列表Acitivity
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getFragmentManager();
mListFragment = (ArticleListFragment) fm.findFragmentById(R.id.fragment_container);
if(mListFragment == null) {
mListFragment = new ArticleListFragment();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.fragment_container, mListFragment);
ft.commit();
}
}
就是简单的把ArticleListFragment添加到Activity容器当中。
下面是新闻列表Fragment的代码:
public class ArticleListFragment extends ListFragment {
public static final int REQUESTCODE = 0x110;
private List<String> mTitles = Arrays.asList("item1", "item2", "item3");
private ArrayAdapter<String> mAdapter;
private int mCurPos;
@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
mAdapter = new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1, mTitles);
setListAdapter(mAdapter);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id)
{
mCurPos = position;
//构建Intent,以带返回结果的方式启动新闻内容的Activity
Intent intent = new Intent(getActivity(), ContentActivity.class);
intent.putExtra(ArticleContentFragment.KEY, mTitles.get(position));
startActivityForResult(intent, REQUESTCODE);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == REQUESTCODE)
{
mTitles.set(mCurPos, mTitles.get(mCurPos) + "---" + data.getStringExtra(ArticleContentFragment.RESPONSE));
mAdapter.notifyDataSetChanged();
}
}
}
ArticleListFragment 继承自ListFragment,在onActivityCreated()回调函数中设置了adapter,在列表项的点击回调函数onListItemClick()中使用startActivityForResult()来启动新的Activity,当从下一个Activity返回到当前Activity就回调onActivityResult()方法。
新闻内容的Activity代码:
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content);
Intent intent = getIntent();
FragmentManager fm = getFragmentManager();
mContentFragment = (ArticleContentFragment) fm.findFragmentById(R.id.fragment_container);
if(mContentFragment == null) {
mContentFragment = ArticleContentFragment.createFragment(intent.getStringExtra(ArticleContentFragment.KEY));
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.fragment_container, mContentFragment);
ft.commit();
}
}
下面是新闻内容Fragment代码:
public class ArticleContentFragment extends Fragment {
public static final String KEY = "KEY";
public static final String RESPONSE = "RESPONSE";
private String mArg;
private TextView mTv;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//获取参数
Bundle bundle = getArguments();
if(bundle != null) {
mArg = bundle.getString(KEY);
Intent intent = new Intent();
intent.putExtra(RESPONSE, "good");
//Fragment本身没有setResult()方法,必须通过宿主Activity实现
getActivity().setResult(ArticleListFragment.REQUESTCODE, intent);
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View contentView = inflater.inflate(R.layout.example_fragment, container, false);
mTv = (TextView) contentView.findViewById(R.id.tv);
mTv.setText(mArg);
return contentView;
}
public static ArticleContentFragment createFragment(String argument)
{
ArticleContentFragment fragment = new ArticleContentFragment();
if(argument != null) {
Bundle args = new Bundle();
args.putString(KEY, argument);
//为fragment传递参数
fragment.setArguments(args);
}
return fragment;
}
}
就一个地方需要注意,第24行。由于Fragment本身没有setResult()方法,所以我们需要通过getActivity().setResult()来设置返回数据。这也就是说:Fragment能够从Activity接收返回结果,但Fragment自身无法产生返回结果,需要借助宿主Activity实现。
Fragment可以通过getActivity()方法获取宿主Activity的对象引用,通过该引用,可以调用Activity中的findViewById()方法获得布局中的视图控件。
如下所示:
TextView tv = (TextView)getActivity().findViewById(R.id.tv);
类似地,也可以在Activity中获取Fragment实例:
FragmentManager fm = getFragmentManager();
ExampleFragment fragment = (ExampleFragment) fm.findFragmentById(R.id.fragment_container);
由于Fragment和宿主Activity都可以获取对方的实例对象,就可访问到对方内部所有的public类型的方法。
新的场景:
宿主Activity需要对Fragment中的UI控件的事件进行响应。好的做法就是在Fragment中定义添加回调接口,让宿主Activity去实现这个接口。
举例来说:一个新闻应用的Activity包含两个Fragment,Fragment A展示新闻标题,Fragment B显示新闻内容。Fragment A必须告诉Activity它的列表项何时被点击,这样Activity可以控制Fragment B的显示内容。
下面给出示例代码:
public class FragmentA extends ListFragment {
...
//宿主Activity必须实现这个接口
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}
接着在宿主Activity中实现这个接口,重写onArticleSelected()方法,并通知FragmentB来自于FragmentA的点击事件。为了保证宿主Activity实现该接口,需要在FragmentA中的onAttach()回调方法中做如下工作(当Fragment添加至Activity,系统回调onAttach()方法):
public class FragmentA extends ListFragment {
private OnArticleSelectedListener mActivity;
...
@Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
try {
mActivity = (OnArticleSelectedListener)activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() +
"must implement OnArticleSelectedListener");
}
}
...
}
这样FragmentA的成员变量mActivity持有了实现OnArticleSelectedListener 接口的引用,也就是FragmentA可以向宿主Activity传递点击事件,宿主Activity重写了onArticleSelected()方法,在该方法中执行具体逻辑操作,比如:控制FragmentB显示被点击的列表项所对应的新闻内容。
为了直观,写了一个简单的demo
这个demo的代码就不贴了。从gif图中可以看出,宿主Activity有上下两个Fragment:上面的Fragment称为FragmentA,下面的叫FragmentB。FragmentA的UI布局包含两个Button,FragmentB的UI布局包含一个TextView。点击FragmentA中的Button,FragmentB中的TextView的内容发生变化。
这个Demo就是按照上面新闻场景进行编写。宿主Activity负责维系两个Fragment的通信,宿主Activity起到一个桥梁的作用。FragmentA中的按钮点击之后,并不会直接操作FragmentB中的TextView,而是通过接口回调的方式传递给宿主Activity,宿主Activity根据FragmentA中的点击事件去改变FragmentB中的内容。
如果对Toolbar的用法还不清楚的,请点击Android常用UI之Toolbar 。所以关于Toolbar的细节就不讲了,下面主要讲一下Fragment与Toolbar的集成。
其实,在Fragment中使用Toolbar的方式几乎和在Activity中使用是一致的。在Fragment也有onCreateOptionsMenu()回调和onOptionsItemSelected()回调,但是在Fragment中需要使能菜单功能,通过setHasOptionsMenu(true);方法实现。
下面看代码,首先是Activity的布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.czh.MainActivity">
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:theme="@style/AppTheme.AppBarOverlay" app:logo="@mipmap/ic_launcher"/>
<FrameLayout android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent"/>
</LinearLayout>
</LinearLayout>
添加了v7包中的Toolbar布局;下面的FrameLayout是Fragment的容器。
下面看styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style>
<style name="AppTheme.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/> </resources>
下面是Activity的代码:
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//使用Toolbar
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//添加Fragment到容器中
FragmentManager fm = getFragmentManager();
mToolbarFragment = (ToolbarFragment) fm.findFragmentById(R.id.fragment_container);
if(mToolbarFragment == null) {
mToolbarFragment = new ToolbarFragment();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.fragment_container, mToolbarFragment);
ft.commit();
}
}
下面是Fragment的代码:
public class ToolbarFragment extends Fragment {
private TextView mTv;
private AppCompatActivity mActivity;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//在Fragment中使用菜单,必须先使能
setHasOptionsMenu(true);
ActionBar ab = mActivity.getSupportActionBar();
ab.setTitle("Fragment");
//添加Toolbar的导航按钮
ab.setDisplayHomeAsUpEnabled(true);
}
@Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
try {
mActivity = (AppCompatActivity) activity;
} catch (ClassCastException e) {
throw new ClassCastException("activity must be AppCompatActivity");
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View contentView = inflater.inflate(R.layout.example_fragment, container, false);
mTv = (TextView) contentView.findViewById(R.id.tv);
mTv.setText("hello world!!!");
return contentView;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
inflater.inflate(R.menu.menu_main, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case R.id.action_search:
Toast.makeText(getActivity(), "search", Toast.LENGTH_SHORT).show();
break;
case R.id.action_setting:
Toast.makeText(getActivity(), "setting", Toast.LENGTH_SHORT).show();
break;
case android.R.id.home:
mActivity.finish();
break;
}
return true;
}
}
OK,在onCreate()回调中调用setHasOptionsMenu(true)来在Fragment中使能菜单,通过onCreateOptionsMenu()回调来加载菜单资源(res/menu/menu_main.xml),通过onOptionsItemSelected(),来响应菜单项点击事件。
最后别忘了在manifest文件中使用android:theme=”@style/AppTheme.NoActionBar”主题,目的是阻止系统使用自带的ActionBar。
最后,贴运行效果图:
好的,本篇文章到这就结束了,总算写完了~~~
参考文章:
官方文档:https://developer.android.com/guide/components/fragments.html
鸿洋的博客:http://blog.csdn.net/lmj623565791/article/details/42628537