Я нашел подходящее решение для своего конкретного сценария и не хочу делиться им с вами.
Обратите внимание, что это не объяснение того, как работает ViewBinding
.
Я создал псевдокод ниже, чтобы поделиться с вами. (Перенос из моего решения с использованием DialogFragments
, отображающего AlertDialog
). Надеюсь, он почти правильно адаптирован к фрагментам (onCreateView()
vs. 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()
- это место, где надуваются крепления. Мы можем связать CommonLayoutBinding
на основе привязок фрагмента, в который включен common_layout.xml
. Я определил абстрактный метод onCreateViewBinding()
, вызываемый поверх onCreateView()
, который возвращает VewBinding
из FragmentA
и FragmentB
. Таким образом я гарантирую, что привязка фрагмента присутствует, когда мне нужно создать CommonLayoutBinding
.
Затем я могу создать экземпляр CommonLayoutBinding
, вызвав commonBinding = CommonLayoutBinding.bind(binding.getRoot());
. Обратите внимание, что корневое представление из привязки конкретного фрагмента передается в 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 />
Минусы:
- Possibly not exact applicable where views can't be moved into a commonly used layout file
- Possibly views needs to be positioned different between frgments/layouts
- Многие
<included />
макеты приведут к множеству классов привязки, тогда ничего не выиграет
- Требуется другой экземпляр привязки (
CommonLayoutBinding
). Для каждого дочернего элемента (FragmentA
, FragmentB
) существует не только один класс привязки, который обеспечивает доступ ко всем представлениям в иерархии представлений.
Что делать, если представления не могут быть перемещены в общий макет?
Мне очень интересно, как решить эту проблему в качестве передовой практики! Давайте подумаем: представьте класс-оболочку вокруг конкретного ViewBinding
. Мы могли бы представить интерфейс, обеспечивающий доступ к часто используемым представлениям. Из фрагментов мы оборачиваем наши привязки в эти классы-оболочки. С другой стороны, это привело бы к созданию множества оберток для каждого типа ViewBinding. Но мы можем предоставить эту оболочку для BaseFragment
, используя абстрактный метод (дженерики). BaseFragment
затем может получить доступ к представлениям или работать с ними, используя определенные методы интерфейса. Как вы думаете?
В заключение:
Может быть, это просто фактический предел ViewBinding, когда один макет должен иметь собственный класс Binding. Если вы нашли хорошее решение в случаях, когда макет не может быть опубликован и должен быть объявлен дублированным в каждом макете, дайте мне знать.
Я не знаю, является ли это наилучшей практикой или есть лучшие решения. Но пока это единственное известное решение для моего случая использования, это кажется хорошим началом!
person
Danny
schedule
13.11.2020
public abstract int getLayoutResourse();
в своем BaseFragment и передать егоDataBindingUtil.inflate()
вместоR.layout.frag_layout
, иначе я не понял вопроса - person Alex Rmcf   schedule 16.06.2020ViewDataBinding
из базового класса. Но я не могу получить доступ к представлениям черезViewDataBinding.textTitle
, например, из базового класса, не зная конкретного типа класса привязки. - person Danny   schedule 16.06.2020