Android 进阶 Fragment 介绍和使用 (二)
管理fragment
因为FragmentManager的API是在Android 3.0,也即API level 11开始引入的,所以对于之前的版本,需要使用support library中的FragmentActivity,并且使用getSupportFragmentManager()方法。
用FragmentManager可以做的工作有:
得到Activity中存在的fragment:
使用findFragmentById()或findFragmentByTag()方法。
将fragment弹出back stack:
popBackStack():将back stack中最后一次的fragment转换弹出。如果没有可以出栈的东西,返回false。
这个函数是异步的:它将弹出栈的请求加入队列,但是这个动作直到应用回到事件循环才会执行。
为back stack加上监听器:
addOnBackStackChangedListener()
Fragment Transactions 事务
使用Fragment时,可以通过用户交互来执行一些动作,比如增加、移除、替换等。
所有这些改变构成一个集合,这个集合被叫做一个transaction。
可以调用FragmentTransaction中的方法来处理这个transaction,并且可以将transaction存进由activity管理的back stack中,这样用户就可以进行fragment变化的回退操作。
可以这样得到FragmentTransaction类的实例:
FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();每个transaction是一组同时执行的变化的集合。
用add(), remove(), replace()方法,把所有需要的变化加进去,然后调用commit()方法,将这些变化应用。
在commit()方法之前,你可以调用addToBackStack(),把这个transaction加入back stack中去,这个back stack是由activity管理的,当用户按返回键时,就会回到上一个fragment的状态。
比如下面的代码就是用一个新的fragment取代之前的fragment,并且将前次的状态存储在back stack中。
// Create new fragment and transaction Fragment newFragment = new ExampleFragment(); FragmentTransaction transaction = getFragmentManager().beginTransaction(); // Replace whatever is in the fragment_container view with this fragment, // and add the transaction to the back stack transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // Commit the transaction transaction.commit();在这个例子中,newFragment将取代在R.id.fragment_container容器中的fragment,如果没有,将直接添加新的fragment。
通过调用addToBackStack(),commit()的一系列转换作为一个transaction被存储在back stack中,用户按Back键可以返回上一个转换前的状态。
当你移除一个fragment的时候,如果commit()之前没有调用addToBackStack(),那个fragment将会是destroyed;如果调用了addToBackStack(),这个fragment会是stopped,可以通过返回键来恢复。
关于commit()方法
1你必须最后调用commit()。
2如果你添加了多个fragment,那么它们的显示顺序跟添加顺序一至(后显示的覆盖前面的)。
如果你在执行的事务中有删除fragment的动作,而且没有调用addToBackStack(),那么当事务提交时,那些被删除的fragment就被销毁了。反之,那些fragment就不会被销毁,而是处于停止状态。当用户返回时,它们会被恢复。
密技:对于fragment事务,你可以应用动画。在commit()之前调用setTransition()就行。――一般银我不告诉他哦。
但是,调用commit()后,事务并不会马上执行。它会在activity的UI线程(其实就是主线程)中等待直到线程能执行的时候才执行(废话)。如果必要,你可以在UI线程中调用executePendingTransactions()方法来立即执行事务。但一般不需这样做,除非有其它线程在等待事务的执行。
警告:你只能在activity处于可保存状态的状态时,比如running中,onPause()方法和onStop()方法中提交事务,否则会引发异常。这是因为fragment的状态会丢失。如果要在可能丢失状态的情况下提交事务,请使用commitAllowingStateLoss()。
实例程序
写了个小程序实践了一下fragment的管理,程序不是很完善,就是试试基本用法,先按第一个按钮添加一个fragment,第二个按钮将添加第二个fragment,按删除按钮删除相应fragment。
相关代码:
第一个fragment:
import android.os.Bundle; import android.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class FirstFragment extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_first, container, false); } }
布局:
<FrameLayout 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" tools:context="com.jcdh.jcli.myapplication.FirstFragment"> <!-- TODO: Update blank fragment layout --> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:textSize="20sp" android:gravity="center" android:background="@android:color/black" android:textColor="@android:color/white" android:text="我是第一个Fragment" /> </FrameLayout>
第二个Fragment
import android.os.Bundle; import android.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class SecondFragment extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_second, container, false); } }
布局:
<FrameLayout 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" tools:context="com.jcdh.jcli.myapplication.FirstFragment"> <!-- TODO: Update blank fragment layout --> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:textSize="30sp" android:gravity="center" android:background="@android:color/darker_gray" android:text="我是第二个Fragment" /> </FrameLayout>
主Actiivty:
import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class MainActivity extends Activity { private FirstFragment fristFragment; private SecondFragment secondFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void moveToFragment(View view) { // 开启Fragment事务 FragmentManager fm = getFragmentManager(); FragmentTransaction transaction = fm.beginTransaction(); switch (view.getId()) { case R.id.first_btn: if (fristFragment == null) { fristFragment = new FirstFragment(); transaction.replace(R.id.first_fragment, fristFragment); } else { Toast.makeText(MainActivity.this, "已经添加过了fristFragment,不能重复添加", Toast.LENGTH_SHORT).show(); return; } break; case R.id.second_btn: if (secondFragment == null) { secondFragment = new SecondFragment(); transaction.replace(R.id.first_fragment, secondFragment); } else { Toast.makeText(MainActivity.this, "已经添加过了fristFragment,不能重复添加", Toast.LENGTH_SHORT).show(); return; } break; } transaction.addToBackStack(null); transaction.commit(); } public void removeFragment(View v) { if (getFragmentManager().getBackStackEntryCount() == 0) { Toast.makeText(MainActivity.this, "没有 Fragment 可以被 删除, ", Toast.LENGTH_SHORT).show(); return; } FragmentTransaction transaction = getFragmentManager().beginTransaction(); switch (v.getId()) { case R.id.button: if (null != fristFragment) { transaction.remove(fristFragment); fristFragment = null; Toast.makeText(MainActivity.this, "删除fristFragment, ", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "没有fristFragment可以被删除 ", Toast.LENGTH_SHORT).show(); } break; case R.id.button2: if (null != secondFragment) { transaction.remove(secondFragment); secondFragment = null; Toast.makeText(MainActivity.this, "删除secondFragment, ", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "没有删除secondFragment可以被删除 ", Toast.LENGTH_SHORT).show(); } break; } transaction.commit();// 一定要提交才能生效 } }
Actiivity 布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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" tools:context="com.jcdh.jcli.myapplication.MainActivity"> <Button android:id="@+id/first_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:onClick="moveToFragment" android:text="第一个Fragment" /> <Button android:id="@+id/second_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/first_btn" android:onClick="moveToFragment" android:layout_alignParentTop="true" android:text="第二个Fragment" /> <fragment android:id="@+id/first_fragment" android:name="com.jcdh.jcli.myapplication.FirstFragment" android:layout_width="match_parent" android:layout_height="100dp" android:layout_below="@+id/first_btn"> </fragment> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="del \nfirstfragment" android:id="@+id/button" android:onClick="removeFragment" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="del \nsecondfragment" android:id="@+id/button2" android:onClick="removeFragment" android:layout_alignBottom="@+id/button" android:layout_alignParentLeft="true" /> </RelativeLayout>
效果图:
Fragment和Activity的交互
一个Fragment的实例总是和包含它的Activity直接相关。
fragment可以通过getActivity()
方法来获得Activity的实例,然后就可以调用一些例如findViewById()之类的方法。
如:
View listView =getActivity().findViewById(R.id.list);
但是注意调用getActivity()时,fragment必须和activity关联(attached to an activity),否则将会返回一个null。
相似的,activity也可以获得一个fragment的引用,从而调用fragment中的方法。
获得fragment的引用要用FragmentManager,之后可以调用findFragmentById()
或者 findFragmentByTag()
.
比如:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
创建事件回调
一些情况下,可能需要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); } ... }
效果图片:
处理Fragment的生命周期
三种停留状态
管理fragment的生命周期和管理activity的生命周期类似,和activity一样,fragment可以在三种状态下停留:
Resumed
fragment在running的activity中可见。
Paused
另一个activity在前景运行,并且享有焦点,但是这个fragment所在的activity仍然可见(前景activity部分遮挡或者是半透明的)。
Stopped
fragment不可见。可能是因为宿主activity处于stopped状态,或者fragment被remove掉,然后加在了back stack中。
一个处于stopped状态的activity还是存活状态的,所有的状态和成员信息会被系统保持。但是,它不再被用户可见,并且如果宿主activity被kill掉,它也会被kill掉。
数据存储和恢复
和Activity类似,可以用Bundle类对象保存fragment的状态,当activity的进程被kill之后,需要重建activity时,可以用于恢复fragment的状态。
存储时利用onSaveInstanceState()回调函数,恢复时是在 onCreate()
, onCreateView()
,
或者onActivityCreated()
里。
Back Stack
activity和fragment生命周期最重要的不同之处是它们如何存储在各自的back stack中。
Activity停止时,是存在一个由系统维护的back stack中,但是当fragment停止(被remove)时,需要程序员显示地调用addToBackStack()
,并且fragment是存在一个由宿主activity掌管的back
stack中。
Fragment和Activity的生命周期
宿主activity的声明周期直接影响到fragment的生命周期,比如activity生命周期的回调函数调用时,所有在其中的fragment的相同的回调函数会同时被调用。
Activity直接影响它所包含的fragment的生命周期,所以对activity的某个生命周期方法的调用也会产生对fragment相同方法的调用。例如:当activity的onPause()方法被调用时,它所包含的所有的fragment们的onPause()方法都会被调用。
Fragment比activity还要多出几个生命周期回调方法,这些额外的方法是为了与activity的交互而设立,如下:
onAttach()
当fragment被加入到activity时调用(在这个方法中可以获得所在的activity)。
onCreateView()
当activity要得到fragment的layout时,调用此方法,fragment在其中创建自己的layout(界面)。
onActivityCreated()
当activity的onCreated()方法返回后调用此方法。
onDestroyView()
当fragment的layout被销毁时被调用。
onDetach()
当fragment被从activity中删掉时被调用。
一旦activity进入resumed状态(也就是running状态),你就可以自由地添加和删除fragment了。因此,只有当activity在resumed状态时,fragment的生命周期才能独立的运转,其它时候是依赖于activity的生命周期变化的
如图:
第一个demo:
http://download.csdn.net/detail/q610098308/9311169
第二个demo activity 与 fragment 交互: