我找到了适用于我的具体场景的解决方案,我不想与你分享。
请注意,这不是对 ViewBinding 工作原理的解释。
我在下面创建了一些伪代码与您分享。 (使用显示AlertDialog 的DialogFragments 从我的解决方案迁移而来)。我希望它几乎可以正确地适应 Fragments(onCreateView() 与 onCreateDialog())。我让它以这种方式工作。
假设我们有一个抽象 BaseFragment 和两个扩展类 FragmentA 和 FragmentB。
首先看看我们所有的布局。请注意,我将布局的可重用部分移出到一个单独的文件中,稍后将包含在具体片段的布局中。特定视图保留在其片段的布局中。对于这种情况,使用常用布局很重要。
fragment_a.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- FragmentA-specific views -->
<EditText
android:id="@+id/edit_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/edit_name">
<!-- Include the common layout -->
<include
layout="@layout/common_layout.xml"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</RelativeLayout>
fragment_b.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- FragmentB-specific, differs from FragmentA -->
<TextView
android:id="@+id/text_explain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/explain" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/text_explain">
<!-- Include the common layout -->
<include
layout="@layout/common_layout.xml"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</RelativeLayout>
common_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="android.widget.RelativeLayout">
<Button
android:id="@+id/button_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/up"/>
<Button
android:id="@+id/button_down"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/button_up"
android:text="@string/down" />
</merge>
接下来是片段类。首先是我们的BaseFragment 实现。
onCreateView() 是绑定膨胀的地方。我们能够根据包含common_layout.xml 的片段绑定来绑定CommonLayoutBinding。我在onCreateView() 之上定义了一个抽象方法onCreateViewBinding(),它从FragmentA 和FragmentB 返回VewBinding。这样,当我需要创建 CommonLayoutBinding 时,我可以确保片段的绑定存在。
接下来我可以通过调用commonBinding = CommonLayoutBinding.bind(binding.getRoot()); 创建CommonLayoutBinding 的实例。请注意,具体片段绑定的根视图被传递给bind()。
getCommonBinding() 允许从扩展片段提供对CommonLayoutBinding 的访问。我们可以更严格:BaseFragment 应该提供访问该绑定的具体方法,而不是将其公开给它的子类。
private CommonLayoutBinding commonBinding; // common_layout.xml
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// Make sure to create the concrete binding while it's required to
// create the commonBinding from it
ViewBinding binding = onCreateViewBinding(inflater);
// We're using the concrete layout of the child class to create our
// commonly used binding
commonBinding = CommonLayoutBinding.bind(binding.getRoot());
// ...
return binding.getRoot();
}
// Makes shure to create the concrete binding class from child-classes before
// the commonBinding can be bound
@NonNull
protected abstract ViewBinding onCreateViewBinding(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container);
// Allows child-classes to access the commonBinding to access common
// used views
protected CommonLayoutBinding getCommonBinding() {
return commonBinding;
}
现在看看其中一个子类FragmentA。
我们从onCreateViewBinding() 创建我们的绑定,就像我们从onCreateView() 一样。原则上,它仍然是从onCreateVIew() 调用的。如上所述,此绑定从基类中使用。我正在使用getCommonBinding() 来访问来自common_layout.xml 的视图。 BaseFragment 的每个子类现在都可以从 ViewBinding 访问这些视图。
这样我可以将所有基于通用视图的逻辑上移到基类中。
private FragmentABinding binding; // fragment_a.xml
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// Make sure commonBinding is present before calling super.onCreateView()
// (onCreateViewBinding() needs to deliver a result!)
View view = super.onCreateView(inflater, container, savedInstanceState);
binding.editName.setText("Test");
// ...
CommonLayoutBinding commonBinding = getCommonBinding();
commonBinding.buttonUp.setOnClickListener(v -> {
// Handle onClick-event...
});
// ...
return view;
}
// This comes from the base class and makes sure we have the required
// binding-instance, see BaseFragment
@Override
protected ViewBinding onCreateViewBinding(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container) {
binding = FragmentABinding.inflate(inflater, container, false);
return binding;
}
优点:
- 通过将重复代码移至基类来减少重复代码。现在所有片段中的代码都更加清晰,并简化为基本要素
- 通过将可重用视图移动到通过
<include /> 包含的布局中,布局更加简洁
缺点:
- 可能不完全适用于无法将视图移动到常用布局文件中的情况
- 可能视图需要在片段/布局之间定位不同
- 许多
<included /> 布局会导致许多 Binding 类,然后没有什么可取胜的
- 需要另一个绑定实例 (
CommonLayoutBinding)。每个子级(FragmentA、FragmentB)不仅有一个绑定类,它提供对视图层次结构中所有视图的访问
如果视图无法移动到通用布局中怎么办?
我对如何将其作为最佳实践来解决非常感兴趣!让我们考虑一下:在具体的ViewBinding 周围引入一个包装类。我们可以引入一个接口来提供对常用视图的访问。从片段中,我们将绑定包装在这些包装类中。另一方面,这将导致每个 ViewBinding 类型的许多包装器。但是我们可以使用抽象方法(泛型)将这些包装器提供给BaseFragment。 BaseFragment 然后能够访问视图或使用定义的接口方法处理它们。 你怎么看?
结论:
也许这只是 ViewBinding 的实际限制,一种布局需要拥有自己的 Binding 类。 如果您在无法共享布局并且需要在每个布局中声明重复的情况下找到了一个好的解决方案,请告诉我。
我不知道这是否是最佳做法,或者是否有更好的解决方案。但在这是我用例的唯一已知解决方案之前,这似乎是一个好的开始!