除了使用fragment标签,新的代码也说明了fragment事务。我们再次对该应用进行扩展来对这个概念进行进一步的说明。
但是,在探讨事务之前,我们需要先简单地做一些说明。在此之前我们已经指出Android开发者文档建议fragment子类不要有显式的构造函数。那么,外部对象如何为新的fragment执行初始化呢?Fragment类支持两种方法:setArguments和getArguments,这两个方法提供了这种功能。它们允许外部调用方(可能是fragment创建者)在fragment中存储Bundle,并且该fragment在后期可以重新获取这个bundle。
以上说明了fragment的新实例、Bundle和类似构造函数的setArguments函数的关系。可以把它们组合成Fragment对象的静态工厂方法,如下所示:
public static DateTime newInstance(Date time) { Bundle init = new Bundle; init.putString( DateTime.TAG_DATE_TIME, getDateTimeString(time)); DateTime frag = new DateTime; frag.setArguments(init); return frag;}private static String getDateTimeString(Date time) { return new SimpleDateFormat(/"d MMM yyyy HH:mm:ss/") .format(time);}
现在,我们可以使用静态工厂方法SimpleFragment的onCreate方法来创建fragment的新实例,对其参数bundle执行正确的初始化。这段代码和预览版本的几乎完全相同,区别在于它使用的是DateTime的静态工厂方法,并给它传递了一个参数:
@Overridepublic void onCreate(Bundle state) { super.onCreate(state); setContentView(R.layout.main); FragmentManager fragMgr = getFragmentManager; FragmentTransaction xact = fragMgr.beginTransaction; if (null == fragMgr.findFragmentByTag(FRAG1_TAG)) { xact.add( R.id.date_time, DateTime.newInstance(new Date), FRAG1_TAG); } xact.commit;}
最后,onCreate方法从传递的参数bundle中获取初始化数据,除非从之前的版本中可以获取状态:
@Overridepublic void onCreate(Bundle state) { super.onCreate(state); if (null == state) { state = getArguments; } if (null != state) { time = state.getString(TAG_DATE_TIME); } if (null == time) { time = getDateTimeString(new Date); }}
对这个应用的修改,到目前为止,还尚未影响到执行结果。但是,其实现是非常不同的,而且更加灵活。特别是,现在有了可以在外部初始化的fragment,可以用于说明事务。
正如其名,Fragment事务的思想在于所有的变化作为单一的、原子性的操作。为了说明这一点,我们对示例代码做最后的扩展:添加创建一组fragment的功能。
以下是新的布局:
<LinearLayout xmlns:android=/"http://schemas.android.com/apk/res/android/" android:orientation=/"vertical/" android:layout_ android:layout_ > <Button android:id=/"@+id/new_fragments/" android:layout_ android:layout_ android:layout_weight=/"1/" android:textSize=/"24dp/" android:text=/"@string/doit/" /> <FrameLayout android:id=/"@+id/date_time2/" android:layout_ android:layout_ android:layout_weight=/"2/" android:background=/"@color/blue/" /> <FrameLayout android:id=/"@+id/date_time/" android:layout_ android:layout_ android:layout_weight=/"2/" android:background=/"@color/green/" /></LinearLayout>
以下是在SimpleFragment的onCreate方法中添加的代码:
public void onCreate(Bundle state) { super.onCreate(state); setContentView(R.layout.main); ((Button) findViewById(R.id.new_fragments)) .setOnClickListener( new Button.OnClickListener { @Override public void onClick(View v) { update; } }); Date time = new Date; FragmentManager fragMgr = getFragmentManager; FragmentTransaction xact = fragMgr.beginTransaction; if (null == fragMgr.findFragmentByTag(FRAG1_TAG)) { xact.add( R.id.date_time, DateTime.newInstance(time), FRAG1_TAG); } if (null == fragMgr.findFragmentByTag(FRAG2_TAG)) { xact.add( R.id.date_time2, DateTime.newInstance(time), FRAG2_TAG); } xact.commit;}
最后,该示例代码的执行结果和之前有所不同。运行时,它看起来如图7-2所示。
图7-2:Fragment事务
两个fragment都显示完全相同的日期和时间,因为给它们传递的是相同的值。访问其他应用再返回演示应用或旋转显示屏幕都不会导致应用丢失状态。因此,我们可以实现一个按钮,如例7-1所示:
例7-1:替换当前的fragment
void update { Date time = new Date; FragmentTransaction xact = getFragmentManager.beginTransaction; xact.replace( R.id.date_time, DateTime.newInstance(time), FRAG1_TAG); xact.replace( R.id.date_time2, DateTime.newInstance(time), FRAG2_TAG); xact.addToBackStack(null); xact.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); xact.commit;}
该方法实际上利用了fragment事务的原子性。它看起来很像SimpleFragment的onCreate方法的初始化代码。但是,它没有使用事务来添加新的fragment实例,而是替换了当前的fragment。在方法最后调用commit导致新的fragment同时变得不可见。蓝色和绿色总是同步的。
警告:在布局中创建的fragment(使用XML fragment标签)永远都不能被动态创建的fragment替换。虽然从外观上很难区分这两种不同方式创建的fragment,但其生命周期有很大区别。并不是说不能在应用中同时使用这两种方式创建的fragment,但是不要相互替换。例如,如果布局的fragment被动态创建的fragment替换,则调用setContentView时会产生Bug,而且该Bug难以查找和修正。这种问题的常见现象是弹出“Fragment did not create a view”的IllegalStateException异常。
这是我们要介绍的fragment的最后一个基础特征,即备用栈(back stack)。如果你顺序运行了几个活动,则可以使用后退按钮按顺序返回。该行为也适用于fragment事务。
如果你运行该应用,其显示看起来如图7-2所示。如果你按下显示屏最上方的按钮,则蓝色和绿色的fragment会同时更新。但是,更好的是,当你按下后退按钮(在显示屏的右下角向左的箭头图标),你会发现按下“Do It!”按钮时,它们会以相反的顺序更新。例如,如果两个fragment都显示“5 Apr 2011 12:49:32”,则当你按下“Do It!”按钮时,显示屏会执行更新,蓝色和绿色区域都显示日期时间为“5 Apr 2011 13:02:43”。当你按下后退按钮时,两个fragment都会显示“5 Apr 2011 12:49:32”。整个事务(更新两个fragment)是作为单一事件插入备用栈中的。当你按下回退按钮时,会删除整个事务,显示前一个事务的整个状态。