【AndroidX】Fragment
1.简介
Fragment表示应用界面中可重复使用的部分,通过将界面划分为独立的块从而实现模块化和可重用性。Fragment定义并管理自己的布局,具有自己的生命周期。但fragment不能独立存在,必须由activity或其他fragment托管。
- 官方文档:https://developer.android.google.cn/guide/fragments
- API文档:https://developer.android.google.cn/reference/androidx/fragment/app/Fragment
2.创建fragment
Fragment需要依赖AndroidX Fragment库。在build.gradle文件中添加以下依赖项:
1
implementation "androidx.fragment:fragment:1.8.8"
2.1 创建fragment类
为了创建fragment,需要扩展androidx.fragment.app.Fragment
类,并通过超类构造器指定布局资源id。例如:
1
2
3
4
5
6
7
import androidx.fragment.app.Fragment;
public class ExampleFragment extends Fragment {
public ExampleFragment() {
super(R.layout.example_fragment);
}
}
也可以在onCreateView()
方法中手动创建布局视图:
1
2
3
4
5
6
public class ExampleFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_example, container, false);
}
}
2.2 向activity添加fragment
Fragment必须嵌入AndroidX FragmentActivity
中。FragmentActivity
是AppCompatActivity
的超类。因此,如果已经继承了AppCompatActivity
,则无需修改activity超类。
为了将fragment添加到activity视图中,可以直接在activity的布局文件中定义fragment;或者在布局文件中定义fragment容器,然后以编程方式添加fragment。强烈建议使用FragmentContainerView
(而不是FrameLayout
)作为fragment容器。
直接通过XML添加fragment的示例如下:
1
2
3
4
5
6
<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.ExampleFragment" />
android:name
属性指定fragment类名。
为了以编程方式添加fragment,布局应该包含FragmentContainerView
作为fragment容器(与上面的示例相同,只是没有android:name
属性)。在activity运行时,可以执行fragment事务,如添加、移除或替换fragment。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ExampleActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.setReorderingAllowed(true)
.add(R.id.fragment_container_view, ExampleFragment.class, null)
.commit();
}
}
}
注意,只有在savedInstanceState
为null
时才会创建fragment事务,这是为了确保fragment仅在activity首次创建时添加一次。当activity重新创建时,fragment会自动从savedInstanceState
中恢复。
如果fragment需要初始数据,可以通过在FragmentTransaction.add()
调用中提供一个Bundle
将参数传递到fragment,如下所示:
1
2
3
4
5
6
7
8
9
if (savedInstanceState == null) {
Bundle bundle = new Bundle();
bundle.putInt("some_int", 0);
getSupportFragmentManager().beginTransaction()
.setReorderingAllowed(true)
.add(R.id.fragment_container_view, ExampleFragment.class, bundle)
.commit();
}
然后在fragment中通过调用requireArguments()
获取参数:
1
2
3
4
5
6
7
8
9
public class ExampleFragment extends Fragment {
...
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
int someInt = requireArguments().getInt("some_int");
...
}
}
注:
- 也可以单独创建fragment实例,这种情况下需要通过
fragment.setArguments(args)
设置参数,然后调用add(containerViewId, fragment)
。 - 在fragment中也可以通过
getArguments()
获取参数。requireArguments()
与该方法的区别是当参数为null
时会抛出异常。
3.保存和恢复状态
各种Android系统操作都会影响fragment的状态。Android框架会自动保存和恢复fragment,而fragment中的数据需要手动保存和恢复。
3.1 视图状态
视图负责管理自己的状态。Android框架提供的视图都实现了onSaveInstanceState()
和onRestoreInstanceState()
,因此不必在fragment中管理视图状态(自定义视图也应该实现这两个方法)。
在布局中有id的视图才能保留其状态,没有id的视图无法保留状态。
3.2 局部变量
Fragment负责管理局部变量中的状态数据。可以使用onSaveInstanceState(Bundle)
保存数据。当fragment被重新创建时,可以在onCreate()
、onCreateView()
或onViewCreated()
方法中访问bundle中的数据。
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class RandomGoodDeedFragment extends Fragment {
private boolean isEditing;
private String randomGoodDeed;
private RandomGoodDeedViewModel viewModel;
...
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(IS_EDITING_KEY, isEditing);
outState.putString(RANDOM_GOOD_DEED_KEY, randomGoodDeed);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
isEditing = savedInstanceState.getBoolean(IS_EDITING_KEY, false);
randomGoodDeed = savedInstanceState.getString(RANDOM_GOOD_DEED_KEY);
} else {
randomGoodDeed = viewModel.generateRandomGoodDeed();
}
}
}
注:也可以使用ViewModel来保存UI状态,这样就无需手动保存和恢复,详见ViewModel概览。
4.与fragment通信
为了正确响应用户事件和共享状态信息,通常需要在activity和fragment之间或者两个fragment之间进行通信。
Fragment库提供了两种通信方式:共享ViewModel和Fragment Result API。如果需要与自定义API共享持久性数据,则使用ViewModel。对于数据可放在Bundle中的一次性结果,使用Fragment Result API。
4.1 使用ViewModel共享数据
ViewModel
对象用于存储和管理UI数据,详细信息参见ViewModel概览。
4.1.1 与宿主activity共享数据
在某些情况下,可能需要在fragment与其宿主activity之间共享数据。
例如,考虑下面的ItemViewModel
:
1
2
3
4
5
6
7
8
9
10
11
public class ItemViewModel extends ViewModel {
private final MutableLiveData<Item> selectedItem = new MutableLiveData<Item>();
public void selectItem(Item item) {
selectedItem.setValue(item);
}
public LiveData<Item> getSelectedItem() {
return selectedItem;
}
}
LiveData
是可感知生命周期的、可观测的数据容器类,详细信息参见LiveData概览。
Fragment及其宿主activity都可以通过将activity传入ViewModelProvider
构造器来获取ViewModel的共享实例。二者都可以观察和修改其中的数据。
1
2
3
4
5
6
7
8
9
10
11
12
public class MainActivity extends AppCompatActivity {
private ItemViewModel viewModel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(ItemViewModel.class);
viewModel.getSelectedItem().observe(this, item -> {
// Perform an action with the latest item data.
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ListFragment extends Fragment {
private ItemViewModel viewModel;
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewModel = new ViewModelProvider(requireActivity()).get(ItemViewModel.class);
...
items.setOnClickListener(item -> {
// Set a new item.
viewModel.selectItem(item);
});
}
}
注:如果ListFragment
以自身为作用域(即new ViewModelProvider(this)
),将会得到与MainActivity
不同的ViewModel实例。
4.1.2 在fragment之间共享数据
同一activity中的两个或多个fragment通常需要相互通信。
例如,假设一个fragment显示一个列表,另一个fragment允许用户对该列表应用各种过滤器。这两个fragment可以使用其activity为作用域共享一个ViewModel来处理这种通信。这样fragment不需要相互了解,activity也不需要做任何事来辅助通信。
下面的示例展示了两个fragment如何使用共享的ViewModel进行通信。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ListViewModel extends ViewModel {
private final MutableLiveData<Set<Filter>> filters = new MutableLiveData<>();
private final LiveData<List<Item>> originalList = ...;
private final LiveData<List<Item>> filteredList = ...;
public LiveData<List<Item>> getFilteredList() {
return filteredList;
}
public LiveData<Set<Filter>> getFilters() {
return filters;
}
public void addFilter(Filter filter) { ... }
public void removeFilter(Filter filter) { ... }
}
1
2
3
4
5
6
7
8
9
10
11
12
public class ListFragment extends Fragment {
private ListViewModel viewModel;
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
// Update the list UI.
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class FilterFragment extends Fragment {
private ListViewModel viewModel;
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
viewModel.getFilters().observe(getViewLifecycleOwner(), set -> {
// Update the selected filters UI.
});
}
public void onFilterSelected(Filter filter) {
viewModel.addFilter(filter);
}
public void onFilterDeselected(Filter filter) {
viewModel.removeFilter(filter);
}
}
4.1.3 在父fragment与子fragment之间共享数据
为了在父fragment与子fragment之间共享数据,使用父fragment作为ViewModel作用域,如下面的示例所示:
1
2
3
4
5
6
7
8
9
10
11
public class ListFragment extends Fragment {
private ListViewModel viewModel;
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
viewModel = new ViewModelProvider(this).get(ListViewModel.class);
viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
// Update the list UI.
}
}
}
1
2
3
4
5
6
7
8
public class ChildFragment extends Fragment {
private ListViewModel viewModel;
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
viewModel = new ViewModelProvider(requireParentFragment()).get(ListViewModel.class);
...
}
}
4.2 使用Fragment Result API获取结果
在某些情况下,可能需要在两个fragment之间或fragment与其宿主activity之间传递一次性的值。例如,一个fragment扫描二维码,并将数据传回之前的fragment。
在Fragment库1.3.0及更高版本中,FragmentManager
类实现了FragmentResultOwner
接口,这意味着FragmentManager
可以充当fragment结果的集中存储。这使得fragment可以通过设置和监听结果进行通信,而无需相互引用。
4.2.1 在fragment之间传递结果
为了将数据从fragment B传回fragment A,首先在fragment A(即接收结果的fragment)中设置结果监听器。对fragment A的FragmentManager
调用setFragmentResultListener()
,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getParentFragmentManager().setFragmentResultListener("requestKey", this, new FragmentResultListener() {
@Override
public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
// We use a String here, but any type that can be put in a Bundle is supported.
String result = bundle.getString("bundleKey");
// Do something with the result.
}
});
}
在fragment B(即生成结果的fragment)中,通过在同一个FragmentManager
上调用setFragmentResult()
使用相同的requestKey
设置结果:
1
2
3
4
5
6
7
8
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle result = new Bundle();
result.putString("bundleKey", "result");
getParentFragmentManager().setFragmentResult("requestKey", result);
}
});
然后,一旦fragment A处于STARTED
状态,它就会收到结果并执行监听器回调。
对于给定的键只能有一个监听器和结果。如果对同一个键多次调用setFragmentResult()
,并且监听fragment未处于STARTED
状态,则新结果会替换旧结果。
4.2.2 在父fragment与子fragment之间传递结果
为了将结果从子fragment传递给父fragment,在父fragment中调用setFragmentResultListener()
时使用getChildFragmentManager()
而不是getParentFragmentManager()
。
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set the listener on the child fragmentManager.
getChildFragmentManager()
.setFragmentResultListener("requestKey", this, new FragmentResultListener() {
@Override
public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
String result = bundle.getString("bundleKey");
// Do something with the result.
}
});
}
子fragment使用getParentFragmentManager()
设置结果:
1
2
3
4
5
6
7
8
9
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle result = new Bundle();
result.putString("bundleKey", "result");
// The child fragment needs to still set the result on its parent fragment manager.
getParentFragmentManager().setFragmentResult("requestKey", result);
}
});
4.2.3 在宿主activity中接收结果
为了在宿主activity中接收fragment结果,使用getSupportFragmentManager()
设置结果监听器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainActivity extends AppCompatActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportFragmentManager().setFragmentResultListener("requestKey", this, new FragmentResultListener() {
@Override
public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
// We use a String here, but any type that can be put in a Bundle is supported.
String result = bundle.getString("bundleKey");
// Do something with the result.
}
});
}
}