- PVSM.RU - https://www.pvsm.ru -
Со времени первого анонса на Google IO 2015 новой библиотеки Data Binding Library [1] прошло немало времени. Появилось много примеров, много гайдов и много исправлений и доделок в самой библиотеке. Вот уже и биндинг стал two-way, и ссылаться на другие View по их id можно в самом layout-файле да и армия поклонников этой библиотеки неуклонно растет. И, наверное, каждый новый адепт начинает с поиска примеров — как правильно использовать так чтобы и удобно, и меньше кода, и по-феншуй. Если сейчас вбить запрос на подобии «Android DataBinding + RecyclerView» то, наверняка, получим целую кучу ссылок на различные гайды. Даже на Хабре уже была подобная статья — Android Data Binding in RecyclerView [2].
Но не смотря на такое обилие ресурсов/гайдов, многие из них показывают базовый функционал, и каждый разработчик, начиная активно использовать Data Binding, придумывает свой, удобный ему способ работы. Далее будет показан один из таких способов.
Этапы:
— реализация/настройка Адаптера (viewTypes, items, обработка кликов по элементам и внутри самих элементов списка);
— настройка RecyclerView (задать LayoutManager, Adapter, ItemDecorator, ItemAnimator, item divider size, ScrollListener, ...).
Оставим пока реализацию адаптера и рассмотрим способ задания конфигурации самого RecyclerView. Самый простой здесь способ, просто присвоить id для RecyclerView и уже в коде задать все параметры:
mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(context)); // + другие настройки.
Второй, часто встречаемый, способ — сделать часть самых банальных инициализаций в коде, например, так:
<android.support.v7.widget.RecyclerView
app:layoutManager="android.support.v7.widget.GridLayoutManager" />
И наконец, используемый автором способ — использовать класс-посредник, который будет сконфигурирован в коде и применен через биндинг. Профит такого подхода в возможности скрыть «дефолтные» настройки внутри класса-посредника (хелпера), но при этом иметь полный контроль над конфигурированием RecyclerView в одном месте.
<android.support.v7.widget.RecyclerView
app:listConfig="@{viewModel.listConfig}"/>
В коде:
ListConfig listConfig = new ListConfig.Builder(mAdapter)
//.setLayoutManagerProvider(new GridLayoutManagerProvider(mCount, mSpanLookup)) //LinearLayoutManager if not set
//.addItemDecoration(new ColorDividerItemDecoration(color, spacing, SPACE_LEFT | SPACE_TOP, false))
.setDefaultDividerEnabled(true)
.addOnScrollListener(new OnLoadMoreScrollListener(mCallback))
.setItemAnimator(getItemAnimator())
.setHasFixedSize(true)
.setItemTouchHelper(getItemTouchHelper())
.build(context);
:
public class ListConfig {
// Adapter, LayoutManager, ItemAnimator, ItemDecorations, ScrollListeners,
// ItemTouchHelper, hasFixedSize
private ListConfig(/*params*/) {
// init fields
}
public void applyConfig(final Context context, final RecyclerView recyclerView) {
//... apply config
}
public static class Builder {
public Builder(Adapter adapter) {/*set field*/}
public Builder setLayoutManagerProvider(LayoutManagerProvider layoutManagerProvider){/*set field*/}
public Builder setItemAnimator(ItemAnimator itemAnimator){/*set field*/}
public Builder addItemDecoration(ItemDecoration itemDecoration){/*set field*/}
public Builder addOnScrollListener(OnScrollListener onScrollListener){/*set field*/}
public Builder setHasFixedSize(boolean isFixedSize){/*set field*/}
public Builder setDefaultDividerEnabled(boolean isEnabled){/*set field*/}
public Builder setDefaultDividerSize(int size){/*set field*/}
public Builder setItemTouchHelper(ItemTouchHelper itemTouchHelper){/*set field*/}
public ListConfig build(Context context) {
/*set default values*/
return new ListConfig(/*params*/);
}
}
public interface LayoutManagerProvider {
LayoutManager get(Context context);
}
}
Один из самых интересных вопросов — наследование или композиция. Многие повторяют как мантру «Предпочитай композицию наследованию», но все равно продолжают и дальше плодить наследников от наследников от наследников… Кто еще не знаком с потрясающей статьей на эту тему в применении к Адаптерам списков, обязательно просмотрите — JOE'S GREAT ADAPTER HELL ESCAPE [3]. Если кратко, то представим такую ситуацию: нам дают задание реализовать простенькое приложение с 2-мя списками: список пользователей (User) и список локаций (Location). Ничего сложного, правда?) Создаем два адаптера, — UserAdapter и LocationAdapter, — и, по сути, все. Но тут, в следующем «спринте» (мы же по Agile, верно? ) заказчик хочет добавить еще и рекламу в каждый из этих списков (Advertisment).
public class User implements BaseModel {
public String name;
public String avatar;
}
public class Location implements BaseModel {
public String name;
public String image;
}
public class Advertisement implements BaseModel {
public String label;
public String image;
}
Никаких проблем, говорим мы, создаем еще один адаптер AdvertismentAdapter и наследуем от него оба предыдущих: UserAdapter extends AdvertismentAdapter и LocationAdapter extends AdvertismentAdapter. Все хорошо, все рады, но вот в новом «спринте» клиент хочет еще один список, где будут смешаны все 3 сущности сразу. Как быть теперь?
И вот тут и переходим от наследования к композиции. До этого у нас на каждый список был отдельный адаптер со своими типами (viewTypes), теперь заменим эту систему на один адаптер и 3 делегата на каждый тип элемента списка. Адаптер не будет ничего знать о типах элементов, которые отображает, но знает, что у него есть несколько делегатов, спросив по очереди каждый из которых, можно найти конкретный для нужного элемента списка и делегировать ему создание этого элемента.
В таком случае, нам уже абсолютно все равно сколько списков и с какими типами элементов будут, любой список формируется как конструктор — набором делегатов.
mAdapter = new DelegateAdapter<>(
new UserDelegate(actionHandler),
// or new ModelItemDelegate(User.class, R.layout.item_user, BR.user),
new LocationDelegate(),
new AdvertismentDelegate(),
// ...
);
public class UserDelegate extends ActionAdapterDelegate<BaseModel, ItemUserBinding> {
public UserDelegate(final ActionClickListener actionHandler) {
super(actionHandler);
}
@Override
public boolean isForViewType(@NonNull final List<BaseModel> items, final int position) {
return items.get(position) instanceof User;
}
@NonNull
@Override
public BindingHolder<ItemUserBinding> onCreateViewHolder(final ViewGroup parent) {
return BindingHolder.newInstance(R.layout.item_user, LayoutInflater.from(parent.getContext()), parent, false);
}
@Override
public void onBindViewHolder(@NonNull final List<BaseModel> items, final int position, @NonNull final BindingHolder<ItemUserBinding> holder) {
final User user = (User) items.get(position);
holder.getBinding().setUser(user);
holder.getBinding().setActionHandler(getActionHandler());
}
@Override
public long getItemId(final List<BaseModel> items, final int position) {
return items.get(position).getId();
}
}
Что касается DataBinding, то вся магия — в особом ViewHolder:
public class BindingHolder<VB extends ViewDataBinding> extends RecyclerView.ViewHolder {
private VB mBinding;
public static <VB extends ViewDataBinding> BindingHolder<VB> newInstance(
@LayoutRes int layoutId, LayoutInflater inflater, ViewGroup parent, boolean attachToParent) {
final VB vb = DataBindingUtil.inflate(inflater, layoutId, parent, attachToParent);
return new BindingHolder<>(vb);
}
public BindingHolder(VB binding) {
super(binding.getRoot());
mBinding = binding;
}
public VB getBinding() {
return mBinding;
}
}
Если же, даже лень создавать отдельный делегат для каждого нового типа/вида элемента списка, можно воспользоваться особенностью биндинга и использовать единый универсальный делегат для любого типа:
// new UserDelegate(actionHandler),
new ModelItemDelegate(User.class, R.layout.item_user);
// or
new ModelItemDelegate(R.layout.item_user, BR.model, (item) -> item instance of User);
public class ModelItemDelegate<T> extends BaseListBindingAdapterDelegate<T, ViewDataBinding> {
private final int mModelId;
private final int mItemLayoutResId;
private final ViewTypeClause mViewTypeClause;
public ModelItemDelegate(@NonNull Class<? extends T> modelClass, @LayoutRes int itemLayoutResId) {
this(itemLayoutResId, BR.model, new SimpleViewTypeClause(modelClass));
}
public ModelItemDelegate(@LayoutRes int itemLayoutResId, int modelId, ViewTypeClause viewTypeClause) {
mItemLayoutResId = itemLayoutResId;
mViewTypeClause = viewTypeClause;
mModelId = modelId != 0 ? modelId : BR.model;
}
@Override
public boolean isForViewType(@NonNull List<T> items, int position) {
return mViewTypeClause.isForViewType(items, position);
}
@NonNull
@Override
public BindingHolder<ViewDataBinding> onCreateViewHolder(ViewGroup parent) {
return BindingHolder.newInstance(mItemLayoutResId, LayoutInflater.from(parent.getContext()), parent, false);
}
@Override
public void onBindViewHolder(@NonNull List<T> items, int position, @NonNull BindingHolder<ViewDataBinding> holder) {
ViewDataBinding binding = holder.getBinding();
binding.setVariable(mModelId, items.get(position));
binding.executePendingBindings();
}
public interface ViewTypeClause {
boolean isForViewType(List<?> items, int position);
}
public static class SimpleViewTypeClause implements ViewTypeClause {
private final Class<?> mClass;
public SimpleViewTypeClause(@NonNull Class<?> aClass) {
mClass = aClass;
}
@Override
public boolean isForViewType(List<?> items, int position) {
return mClass.isAssignableFrom(items.get(position).getClass());
}
}
}
Обработку кликов по элементам несложно реализовать, передав через биндинг обработчик кликов, например, как описано тут — Android и Data Binding: обработка действий [4], или использовав любой другой, удобный для вас, способ.
Таким образом, используя Android Data Binding Library, реализация списков становиться совершенно обыденной вещью. Даже не нужно писать реализацию показанных выше вещей, а просто импортировав готовую библиотеку автора, или просто «скопипастив» их оттуда:)
Библиотека с примером: DataBinding_For_RecyclerView [5]
Автор: d_romka
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/183527
Ссылки в тексте:
[1] Data Binding Library: https://developer.android.com/topic/libraries/data-binding/index.html
[2] Android Data Binding in RecyclerView: https://habrahabr.ru/company/dataart/blog/267735/
[3] JOE'S GREAT ADAPTER HELL ESCAPE: http://hannesdorfmann.com/android/adapter-delegates
[4] Android и Data Binding: обработка действий: https://habrahabr.ru/post/305916/
[5] DataBinding_For_RecyclerView: https://github.com/drstranges/DataBinding_For_RecyclerView
[6] Источник: https://habrahabr.ru/post/308872/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.