2015.10.27-10.28
个人英文阅读练习笔记(低水准)。原文地址:http://developer.android.com/training/basics/fragments/index.html
2015.10.27
若要在安卓上创建动态或多窗格的用户界面,您需要将UI组件以及活动的行为封装成能够进入、退出活动的模块。您可以使用Fragment类来创建这种类型的模块,Fragment类的行为像嵌套的、能够定义自己布局且能自己管理生命周期的活动。
当片段指定自己的布局后,它还能够和活动中的其它片段关联起来为不同的屏幕尺寸修改布局(一个较小的屏幕在同一时间可能只会展示一个片段,但是一个大屏幕可能会显示两个或者更多的片段)。
此笔记记录如何用片段来创建动态的用户体验,并优化设备在不同屏幕尺寸下应用程序的用户体验,应用程序支持android系统的最老版本到1.6。
学习如何构建一个片段,如何用片段内的回调方法实现片段的基本行为。
您可以把片段视为活动的一个经模块化的子板块,此子板块有自己的生命周期,接收自己的输入事件,在活动运行期间可以增加或者移除片段(有点像能够在其它活动中被重复利用的“子活动”)。此笔记记录如何用Support Library来延伸Fragment类,这样的应用程序能够兼容1.6及以上版本的android系统。
注:如果您在应用程序中使用的最小API级别为11或者更高,您就可以不用SupportLibrary,而就可以用搭建在Fragment类中的框架和相关的APIs。需要注意本笔记是使用SupportLibrary中的APIs,这个库使用特定的包签名并且有的API名字跟平台所包含的版本中的APIs的名字不一样。
在按照此笔记学习之前,您需要用SupportLibrary来配置您的android工程。如果您没有用过SupportLibrary,您可以参照文档Support Library Setup用v4库来设置您的android工程。然而,您也可以在活动中包含“app bar”来代替使用v7 appcompat库,app bar兼容android 2.1(API 7)以及系统内包含的片段的APIs。
创建片段、扩展片段(Fragment)类,然后重写关键的生命周期方法到应用程序逻辑中,类似对待一个活动(Activity)类。
创建片段的不同在于您必须使用onCreateView()回调方法定义片段的布局。事实上,这是片段运行唯一需要开发者实现的回调方法。例如,以下代码为片段指明了片段布局:
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.ViewGroup;
public class ArticleFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.article_view, container, false);
}
}
就像活动一样,片段也应该实现其它的生命周期回调方法来管理片段是被添加还是删除的状态以及片段在各个生命阶段之间的转移。例如,当活动的onPause()方法被调用时,活动中的所有片段也应该去调用onPause()方法。
更多关于片段生命周期和回调方法在参考Fragments开发手册。
片段是可重用的、模块化用户接口(UI)组件的,每个片段类的实例必须和父类FragmentActivity联系在一起。您可以通过在活动的XML布局文件中定义每个片段的方式来获得这样的联系。
注:FragmentActivity是SupportLibrary库提供来支持系统版本的API在11之前的的系统的类。如果您使用的系统支持API 11或者更高,您可以使用正常的Activity类。
当设备屏幕变大(用large作为目录的标识符)时,以下代码示例将往活动中添加两个片段:
res/layout-large/news_articles.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent">
<fragment android:name="com.example.android.fragments.HeadlinesFragment" android:id="@+id/headlines_fragment" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent" />
<fragment android:name="com.example.android.fragments.ArticleFragment" android:id="@+id/article_fragment" android:layout_weight="2" android:layout_width="0dp" android:layout_height="match_parent" />
</LinearLayout>
提示(Tip):更多关于为不同屏幕尺寸设计布局参考Supporting Different Screen Sizes。
然后将布局应用到活动中:
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
public class MainActivity extends FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_articles);
}
}
如果您使用的是 v7 appcompat library,活动将用AppCompatActivity来扩展,它是Fragment的一个子类。更多信息参考Adding the App Bar。
注:当使用将片段定义到活动的XML布局文件中的方式将片段添加到活动的布局文件中时,在运行时不能移除片段。如果您计划在用户交互的过程中能够自如的移除或添加片段,您必须在活动第一次启动时就将片段添加到活动中,接下来会笔记此过程。
2015.10.28
学习如何用提供不同片段配置的布局文件来构建应用程序去适合不同的屏幕。
当设计应用程序来支持不同尺寸的屏幕时,您可以将片段应用到不同的布局文件中来优化基于不同屏幕尺寸的用户体验。
举例来说,在手机上的图形界面下一般同时只会运行一个片段。反之,在平板上您可能会想让几个片段紧挨着显示在屏幕上,以给用户多显示些信息。
图1.两个片段在不同配置的相同活动中显示在不同屏幕上。在大屏幕上,两个片段并排显示,但在手机上,在同一时间只有一个片段显示,所以用户需要根据导航来切换两个片段
FragmentManager类提供了方法来管理正在运行的活动的片段的添加、移除、替换等操作,这个类可以创造动态体验。
除了像前面笔记的在活动的布局文件中使用来定义片段外,您可以在活动运行时增添片段到活动中。如果您计划在活动的声明周期中改变活动的片段,后者是必要的。
要想添加或者移除片段,您必须使用FragmentManager来创建能提供添加、移除、替换片段及片段的其它转换的APIs的FragmentTransaction。
如果活动允许其片段的移除、替换,您应该在活动的onCreate()方法中初始化片段。
在使用片段时有一个重要的规则,尤其是在活动运行时添加片段时,那就是在活动布局文件中必须包含一个能够插入片段的视图容器。
之前笔记中活动的布局文件提供了可选的片段,但同时只能显示某一个片段。为了能够用另一个片段来替换当前的片段,需要在活动的布局文件中包含一个空FrameLayout来充当片段容器。
注意活动的布局文件跟之前的布局文件名字一样,但是布局文件所在目录不再包含large标识符,所以这种布局在屏幕变小时被使用,因为在此时其它片段都不适合当前屏幕。
res/layout/news_articles.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent" />
在活动中,用SupportLibrary的getSupportFragmentManager()来获取FragmentManager。然后调用beginTransaction()来创建一个FragmentTransaction,再调用add()来添加具体的片段。
您可以用相同的FragmentTransaction来为活动操作片段的多种转变。当您确定要做这样的转变后,您必须调用commit()。
基于之前的布局文件,以下代码显示如何添加片段:
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
public class MainActivity extends FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_articles);
// Check that the activity is using the layout version with
// the fragment_container FrameLayout
if (findViewById(R.id.fragment_container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create a new Fragment to be placed in the activity layout
HeadlinesFragment firstFragment = new HeadlinesFragment();
// In case this activity was started with special instructions from an
// Intent, pass the Intent's extras to the fragment as arguments
firstFragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
}
}
}
因为是通过在活动运行时将片段添加到FrameLayout容器中而不是直接在布局文件中为活动定义的片段,所以活动能够用另外的片段移除或替换片段。
替换片段的步骤跟增添片段的步骤相同,只是需要用replace()方法来替换add()方法。
记住,当在做像移除或替换这样的片段转变时,当用户按下返回导航时要能够恢复到原来的片段中。要实现这个功能,在实现FragmentTransaction之前必须调用addToBackStack()。
注:当移除或者替换片段且将这样的转变添加到后栈中后,片段进入被移除被停止的状态(不是销毁)。如果用户通过导航回到存储的片段中,片段将会被重启。如果您不讲转变添加到后栈中,那么当片段在被移除或替换后将会被销毁。
用另外的片段替换当前片段的程序示例:
// Create fragment and give it an argument specifying the article it should show ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment, // and add the transaction to the back stack so the user can navigate back transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction transaction.commit();
addToBackStack()方法有一个用来指定片段转变可选参数。除了打算使用FragmentManager.BackStackEntry APIs来运行片段操作外不用这个参数。
学习如何从一个片段建立到活动及其它片段的通信路径。
为了能够重复使用片段用户接口(Fragment UI)组件,您应该将每个片段都构建成定义了自己的布局和行为的完全独立的模块化组件。一旦定义了可重复使用的片段,您就可以将片段结合到活动且将它们连接到程序逻辑上以实现整体用户界面的复合化。
您通常会希望片段之间能够进行通信,例如基于用户事件更改内容。通过与活动发生关联就能够让片段之间通信。两个片段之间不能够直接进行通信。
为了让片段去和它所在的活动通信,您可以在片段中定义一个接口并在活动中实现这个接口。片段在其onAttach()生命周期方法中会捕捉接口的实现并随之调用这个接口方法来和其所在活动通信。
以下代码为片段和所在活动通信的示例:
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
// Container Activity must implement this interface
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnHeadlineSelectedListener");
}
}
...
}
这样,片段通过使用OnHeadlineSelectedListener接口的实例mCallback来调用onArticleSelected()方法(或者接口中的其它方法)来向活动传递消息。
以下在片段中的代码在用户点击对应的列表框(list item)时会被调用。片段用这段代码作为回调接口来将事件传递给父活动。
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Send the event to the host activity
mCallback.onArticleSelected(position);
}
为了能够接收来自片段的回调事件,包含片段的活动必须实现定义在片段类中的接口。
举例,以下活动实现了以上片段类中定义的接口。
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// The user selected the headline of an article from the HeadlinesFragment
// Do something here to display that article
}
}
主活动通过findFragmentById()捕捉Fragment实例再直接调用片段的公用方法的方式将消息传递给片段。
比如,假设以上的活动包含了另外一个用来展示回调方法返回值的片段。如此,活动就可以将回调方法返回的数据传递到另外的片段中去显示。
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// The user selected the headline of an article from the HeadlinesFragment
// Do something here to display that article
ArticleFragment articleFrag = (ArticleFragment)
getSupportFragmentManager().findFragmentById(R.id.article_fragment);
if (articleFrag != null) {
// If article frag is available, we're in two-pane layout...
// Call a method in the ArticleFragment to update its content
articleFrag.updateArticleView(position);
} else {
// Otherwise, we're in the one-pane layout and must swap frags...
// Create fragment and give it an argument for the selected article
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack so the user can navigate back
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
}
}
}
[2015.11.11-09:34]