Fragment详解,以开发"显示图书详情的Fragment"为例

Fragment是学习Android以来觉得最麻烦的一个知识点,涉及的内容比较多,在书本上不好记录,所以决定写一篇博文来梳理内容。

Fragment的难点重点是消息传递,本文中的图书详情案例中的消息顺序是:1、用户点击图书的标题(BookListFragment) 2、通知activity 3、activity通知显示图书详细信息(BookDetailFragment)


部分内容参考博文:http://www.cnblogs.com/mengdd/archive/2013/01/11/2856374.html

1、界面

  • 创建Activity的界面。左边为一个fragment,id为book_list,对应的类为org.crazyit.app.BookListFragment。右边为FrameLayout,id为book_detail_container
  • 发现他们的layout_width都为0dp,Layout_weight分别为1和3,所以两者显示的界面区域大小为1:3,Layout_weight的理解可以参考博文中的Layout_weight详解。

activity_book_twopane.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- 定义一个水平排列的LinearLayout,并指定使用中等分隔条 -->
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="horizontal"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:layout_marginLeft="16dp"
	android:layout_marginRight="16dp"
	android:divider="?android:attr/dividerHorizontal"
	android:showDividers="middle">
	<!-- 添加一个Fragment -->
	<fragment
		android:name="org.crazyit.app.BookListFragment"
		android:id="@+id/book_list"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_weight="1" />
	<!-- 添加一个FrameLayout容器 -->
	<FrameLayout
		android:id="@+id/book_detail_container"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_weight="3" />
</LinearLayout>

2、创建ListFragment

  • 在BookListFragment类中定义一个接口——Callbacks( 内有onItemSelected(Interger id方法) ),该Fragment所在Activity需要实现该接口,Fragment将通过该接口与它所在的Activity交互。
  • ListFragment显示的数据,使用ArrayAdapter适配器,数据类型为BookContent.Book,ITEMS为List<Book>!!!!。原因:ArrayAdapter为什么可以处理对象类型的数据呢?其实,ArrayAdapter是使用数组中对象的toString()方法来填充指定的TextView,所以我们可以通过重写对象的toString()方法来自定义ListView的显示。而Book的toString()方法显示的是第一个字符串,即书本的标题。
  • onAttach方法在该Fragment被添加、显示到Activity时回调该方法。
  • onDetach方法在该Fragment从它所属的Activity中被删除时回调该方法。
  • onLIstItemClick为用户单机某列表时激发该回调方法,传入选中页面的书本id。
  • 为什么创建接口CalBacks?

一些情况下,可能需要fragment和activity共享事件,一个比较好的做法是在fragment里面定义一个回调接口,然后要求宿主activity实现它。当activity通过这个接口接收到一个回调,它可以同布局中的其他fragment分享这个信息。

例如,一个新闻显示应用在一个activity中有两个fragment,一个fragment A显示文章题目的列表,一个fragment B显示文章。所以当一个文章被选择的时候,fragment A必须通知activity,然后activity通知fragment B,让它显示这篇文章。这个情况下,在fragment A中声明一个这样的接口OnArticleSelectedListener:

public static class FragmentA extends ListFragment {
    ...
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}

之后包含这个fragment的activity实现这个OnArticleSelectedListener接口,用覆写的onArticleSelected()方法将fragment A中发生的事通知fragment B。

为了确保宿主activity实现这个接口,fragment A的onAttach() 方法(这个方法在fragment 被加入到activity中时由系统调用)中通过将传入的activity强制类型转换,实例化一个OnArticleSelectedListener对象:

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        }
    }
    ...
}

如果activity没有实现这个接口,fragment将会抛出ClassCastException异常,如果成功了,mListener将会是activity实现OnArticleSelectedListener接口的一个引用,所以通过调用OnArticleSelectedListener接口的方法,fragment A可以和activity共享事件。

  比如,如果fragment A是ListFragment的子类,每一次用户点击一个列表项目,系统调用fragment中的onListItemClick() 方法,在这个方法中可以调用onArticleSelected()方法与activity共享事件。

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Append the clicked item's row ID with the content provider Uri
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        // Send the event and Uri to the host activity
        mListener.onArticleSelected(noteUri);
    }
    ...
}

BookListFragment.java

public class BookListFragment extends ListFragment
{
	private Callbacks mCallbacks;
	// 定义一个回调接口,该Fragment所在Activity需要实现该接口
	// 该Fragment将通过该接口与它所在的Activity交互
	public interface Callbacks
	{
		public void onItemSelected(Integer id);
	}

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		// 为该ListFragment设置Adapter
		setListAdapter(new ArrayAdapter<BookContent.Book>(getActivity(),
				android.R.layout.simple_list_item_activated_1,
				android.R.id.text1, BookContent.ITEMS));
	}
	// 当该Fragment被添加、显示到Activity时,回调该方法
	@Override
	public void onAttach(Activity activity)
	{
		super.onAttach(activity);
		// 如果Activity没有实现Callbacks接口,抛出异常
		if (!(activity instanceof Callbacks))
		{
			throw new IllegalStateException(
				"BookListFragment所在的Activity必须实现Callbacks接口!");
		}
		// 把该Activity当成Callbacks对象
		mCallbacks = (Callbacks)activity;
	}
	// 当该Fragment从它所属的Activity中被删除时回调该方法
	@Override
	public void onDetach()
	{
		super.onDetach();
		// 将mCallbacks赋为null。
		mCallbacks = null;
	}
	// 当用户点击某列表项时激发该回调方法
	@Override
	public void onListItemClick(ListView listView
		, View view, int position, long id)
	{
		super.onListItemClick(listView, view, position, id);
		// 激发mCallbacks的onItemSelected方法
		mCallbacks.onItemSelected(BookContent
			.ITEMS.get(position).id);
	}

	public void setActivateOnItemClick(boolean activateOnItemClick)
	{
		getListView().setChoiceMode(
				activateOnItemClick ? ListView.CHOICE_MODE_SINGLE
						: ListView.CHOICE_MODE_NONE);
	}
}

3、图书详细信息显示的Fragment

  • 每次BookDetailFragment切换时系统会调用onCreate方法,根据activity中传入的参数获取book信息。
  • Fragment每次切换时显示的界面也会发生变化,onCreateView方法返回显示的View,使用的布局文件为R.layout.fragment_book_detail,显示标题和描述内容。
  • 获取activity传入的参数,通过getArguments方法

BookDetailFragment.java

public class BookDetailFragment extends Fragment
{
	public static final String ITEM_ID = "item_id";
	// 保存该Fragment显示的Book对象
	BookContent.Book book;
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		// 如果启动该Fragment时包含了ITEM_ID参数
		if (getArguments().containsKey(ITEM_ID))
		{
			book = BookContent.ITEM_MAP.get(getArguments()
				.getInt(ITEM_ID)); //①
		}
	}

	// 重写该方法,该方法返回的View将作为Fragment显示的组件
	@Override
	public View onCreateView(LayoutInflater inflater
		, ViewGroup container, Bundle savedInstanceState)
	{
		// 加载/res/layout/目录下的fragment_book_detail.xml布局文件
		View rootView = inflater.inflate(R.layout.fragment_book_detail,
				container, false);
		if (book != null)
		{
			// 让book_title文本框显示book对象的title属性
			((TextView) rootView.findViewById(R.id.book_title))
					.setText(book.title);
			// 让book_desc文本框显示book对象的desc属性
			((TextView) rootView.findViewById(R.id.book_desc))
				.setText(book.desc);	
		}
		return rootView;
	}
	
}


4、编写activity

  • 设置activity_book_twopane为布局文件,即左边是Fragment,右边是FrameLayout。
  • 实现Callbacks接口的onItemSelected方法,用户点击Fragment时激发mCallbacks的onItemSelected方法,通知BookDetailFragment这个描述书本详细信息的Fragment类,更新内容。
  • Activity的getFragmentManager()方法可以返回FragmentManager,FragmentManager对象的beginTransaction()方法即可开启并返回FragmentTransaction对象,使用Fragment替换book_detail_container容器当前显示的Fragment。
  • 通过setArguments方法传入参数。

SelectBookActivity.java

public class SelectBookActivity extends Activity implements
		BookListFragment.Callbacks
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		// 加载/res/layout目录下的activity_book_twopane.xml布局文件
		setContentView(R.layout.activity_book_twopane);
	}
	// 实现Callbacks接口必须实现的方法
	@Override
	public void onItemSelected(Integer id)
	{
		// 创建Bundle,准备向Fragment传入参数
		Bundle arguments = new Bundle();
		arguments.putInt(BookDetailFragment.ITEM_ID, id);
		// 创建BookDetailFragment对象
		BookDetailFragment fragment = new BookDetailFragment();
		// 向Fragment传入参数
		fragment.setArguments(arguments);
		// 使用fragment替换book_detail_container容器当前显示的Fragment
		getFragmentManager().beginTransaction()
			.replace(R.id.book_detail_container, fragment)
			.commit();  //①
	}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值