一、创建 Fragment
在使用 Fragment 之前,请先在项目 Gradle 配置文件中添加 v4 支持库。
dependencies {
......
implementation 'com.android.support:support-v4:26.1.0'
.....
}
1. 创建 Fragment 类
要创建 Fragment,请先创建 Fragment 的子类,然后覆盖必要的生命周期方法。
事实上,要让 Fragment 运行,你只需要实现 onCreateView() 回调来定义布局。例如,下面是一个简单的 Fragment,它指定了它自己的布局:
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);
}
}
最好也实现其他生命周期回调,以便在添加或删除 Fragment 时以及宿主 Activity 的生命周期状态转换时管理 Fragment。例如,当宿主 Activity 调用 onPause() 时,Activity 中的任何 Fragment 也会收到一个 onPause() 调用。
2. 使用 XML 在 Activity 中添加 Fragment
虽然 Fragment 是可重用的模块化 UI 组件,但 Fragment 类的每个实例都必须与父 Activity 关联。可以通过在 Activity 布局 XML 文件中定义每个 Fragment 来实现此关联。
以下是一个布局文件示例(由 large 限定符指定),当设备屏幕配置是横向时,会向 Activity 添加两个 Fragment。
res/layout-large/news_articles.xml
然后在 Activity 中应用此布局:
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);
}
}
FragmentActivity 是支持库中提供的一个特殊 Activity,在 API 级别 11 以前用于处理 Fragment。如果系统版本为 API 级别为 11或更高,则可以使用常规 Activity。
如果项目中使用 v7 appcompat 库,则你的 Activity 应该扩展AppCompatActivity,它是 FragmentActivity 一个子类。
注意:使用 XML 文件中将 Fragment 添加到 Activity 布局,则无法在运行时删除 Fragment。如果想要在用户交互过程中添加或移除 Fragment,则应该通过代码将 Fragment 添加进 Activity。
二、构建灵活的 UI
在设计支持多种屏幕尺寸的应用时,可以在不同的布局配置中重复使用 Fragment,根据可用的屏幕空间优化用户体验。
1. 在运行时为 Activity 添加 Fragment
如果需要在 Activity 的生命周期内更改 Fragment,就需要在 Activity 运行时为 Activity 添加 Fragment。
要执行添加或移除 Fragment 等事务,就必须使用 FragmentManager 创建 FragmentTransaction,后者将提供添加、移除、替换 Fragment 以及执行其他 Fragment 事务所需的 API。
一般在 Activity 的 onCreate() 方法内添加初始 Fragment。
在处理 Fragment(尤其是在运行时添加 Fragment 的情况下)时,有一个重要准则:Activity 布局必须包含一个可以插 Fragment 的容器 View。
下面这个 FrameLayout 用来充当 Fragment 的容器,res/layout/news_articles.xml
:
调用 getSupportFragmentManager() 以获取 FragmentManager。然后,调用 beginTransaction() 创建一个 FragmentTransaction,并调用 add() 添加一个 Fragment。
可以使用同一 FragmentTransaction 为 Activity 执行多 Fragment 事务,最后必须调用 commit() 提交更改。
例如,可以采用以下方法为上面的布局添加 Fragment:
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();
}
}
}
由于该 Fragment 已在运行时被添加到 FrameLayout 容器(而不是利用
2. 替换 Fragment
替换 Fragment 与添加 Fragment 类似,只不过调用的方法从 add() 改为 replace()。
当执行替换或移除 Fragment 等事务时,通常最好让用户能够回退并 “撤消” 更改。要让用户回退所执行的事务,必须先调用 addToBackStack(),然后再提交 FragmentTransaction。
注:当移除或替换一个 Fragment 并向返回栈添加事务时,系统会停止(而非销毁)移除的 Fragment。 如果用户执行回退操作进行恢复,该 Fragment 将重新启动。 如果不向返回栈添加事务,则系统会在移除或替换 Fragment 时将其销毁。
Fragment 替换示例:
// 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 API 执行高级 Fragment 操作,否则并不需要该名称。
三、与其他 Fragment 进行通信
一个 Fragment 可能需要与另一个 Fragment 进行通信,例如根据用户事件更改内容。
两个 Fragment 之间不应该直接通信,有两种方法可以实现 Fragment 与 Fragment 之间通信:
通过共享 ViewModel;
通过相关的 Activity 完成;
如果不知道如何使用共享 ViewModel 在 Fragment 之间进行通信,则可以使用接口手动实现通信。然而,这最终会导致更多的工作,并且在其他 Fragment 中不容易复用。
1. 定义一个接口
可以在 Fragment 类中定义一个接口并在 Activity 中实现它。Fragment 在其 onAttach() 生命周期方法中获取该接口实例,然后在需要时调用接口方法。
以下是 Fragment 到 Activity 通信的示例:
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");
}
}
...
}
当用户单击列表项时,会调用 Fragment 中的以下方法。Fragment 使用回调接口将事件传递给父 Activity。
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Send the event to the host activity
mCallback.onArticleSelected(position);
}
2. 实现接口
为了接收来自 Fragment 的事件回调,宿主 Activity 必须实现定义在 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
}
}
3. 将信息传递给 Fragment
宿主 Activity 可以通过 findFragmentById() 或者 findFragmentByTag() 捕获 Fragment 实例,然后将信息传递给 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();
}
}
}