Почему в RecyclerView отсутствует onItemClickListener()?
Я изучал RecyclerView
и был удивлён, что в нём нет метода onItemClickListener()
.
У меня есть два вопроса.
Основной вопрос
Почему Google убрал onItemClickListener()
?
Связано ли это с проблемами производительности или чем-то другим?
Вторичный вопрос
Я решил свою проблему, добавив onClick
в свой RecyclerView.Adapter
:
public static class ViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
public TextView txtViewTitle;
public ImageView imgViewIcon;
public ViewHolder(View itemLayoutView) {
super(itemLayoutView);
txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
}
@Override
public void onClick(View v) {
// Обработка события клика
}
}
Это нормально? Есть ли какой-то лучший способ решения этой проблемы?
5 ответ(ов)
tl;dr 2016 Используйте RxJava и PublishSubject, чтобы предоставить Observable для кликов.
public class ReactiveAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
String[] mDataset = { "Data", "In", "Adapter" };
private final PublishSubject<String> onClickSubject = PublishSubject.create();
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
final String element = mDataset[position];
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onClickSubject.onNext(element);
}
});
}
public Observable<String> getPositionClicks(){
return onClickSubject.asObservable();
}
}
Оригинальное сообщение:
С момента появления ListView
использование onItemClickListener
создавало множество проблем. Как только вы добавляли обработчик кликов для любого из внутренних элементов, обратный вызов не срабатывал, и это не было должным образом задокументировано (если вообще). В результате возникло много путаницы и вопросов на StackOverflow.
Поскольку RecyclerView
идет дальше и не имеет концепции строки/столбца, а вместо этого работает с произвольным количеством дочерних элементов, он делегировал обработку кликов каждому из них или же программисту.
Следует рассматривать RecyclerView
не как замену ListView
, а как более гибкий компонент для сложных случаев использования. Как вы сказали, ваше решение соответствует ожиданиям Google. Теперь у вас есть адаптер, который может делегировать клики интерфейсу, переданному в конструктор, что является правильным паттерном как для ListView
, так и для RecyclerView
.
public static class ViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
public TextView txtViewTitle;
public ImageView imgViewIcon;
public IMyViewHolderClicks mListener;
public ViewHolder(View itemLayoutView, IMyViewHolderClicks listener) {
super(itemLayoutView);
mListener = listener;
txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
imgViewIcon.setOnClickListener(this);
itemLayoutView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v instanceof ImageView) {
mListener.onTomato((ImageView)v);
} else {
mListener.onPotato(v);
}
}
public static interface IMyViewHolderClicks {
public void onPotato(View caller);
public void onTomato(ImageView callerImage);
}
}
Затем в вашем адаптере:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
String[] mDataset = { "Data" };
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_layout, parent, false);
MyAdapter.ViewHolder vh = new ViewHolder(v, new MyAdapter.ViewHolder.IMyViewHolderClicks() {
public void onPotato(View caller) { Log.d("VEGETABLES", "Poh-tah-tos"); };
public void onTomato(ImageView callerImage) { Log.d("VEGETABLES", "To-m8-tohs"); }
});
return vh;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.txtViewTitle.setText(mDataset[position]);
}
@Override
public int getItemCount() {
return mDataset.length;
}
}
Обратите внимание на последний фрагмент кода: onCreateViewHolder(ViewGroup parent, int viewType)
, подпись которого уже подразумевает наличие различных типов представлений. Для каждого из них вам понадобится свой собственный ViewHolder, и, соответственно, у каждого из них может быть свой набор обработчиков кликов. Либо вы можете создать универсальный ViewHolder, который принимает любое представление и один onClickListener
, применяя его соответствующим образом. Либо делегируйте на уровень выше, чтобы несколько фрагментов/активностей использовали один и тот же список с различным поведением кликов. Снова вся гибкость остается за вами.
Это действительно необходимый компонент, весьма близкий к тому, что наши внутренние реализации и улучшения ListView
были до сих пор. Хорошо, что Google наконец-то это признал.
Вы можете использовать этот способ, и он действительно позволяет легко обрабатывать нажатия на элементы RecyclerView
. Например, в методе
public Adapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
вы можете создать ваш элемент представления следующим образом:
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_image_and_text, parent, false);
v.setOnClickListener(new MyOnClickListener());
Далее, вам нужно создать класс MyOnClickListener
, который реализует интерфейс View.OnClickListener
. Это можно сделать где угодно в вашем коде:
class MyOnClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
int itemPosition = recyclerView.indexOfChild(v);
Log.e("Clicked and Position is ", String.valueOf(itemPosition));
}
}
Хотя я читал, что существуют и более сложные способы решения этой задачи, я тоже считаю, что данный подход прост и понятен.
Благодарю @marmor, я обновил свой ответ.
Я считаю, что хорошим решением будет обрабатывать метод onClick() в конструкторе класса ViewHolder и передавать его в родительский класс через интерфейс OnItemClickListener.
MyAdapter.java
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private LayoutInflater layoutInflater;
private List<MyObject> items;
private AdapterView.OnItemClickListener onItemClickListener;
public MyAdapter(Context context, AdapterView.OnItemClickListener onItemClickListener, List<MyObject> items) {
layoutInflater = LayoutInflater.from(context);
this.items = items;
this.onItemClickListener = onItemClickListener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = layoutInflater.inflate(R.layout.my_row_layout, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
MyObject item = items.get(position);
}
public MyObject getItem(int position) {
return items.get(position);
}
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView title;
private ImageView avatar;
public ViewHolder(View itemView) {
super(itemView);
title = itemView.findViewById(R.id.title);
avatar = itemView.findViewById(R.id.avatar);
title.setOnClickListener(this);
avatar.setOnClickListener(this);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
// Передаем позицию нажатого элемента в родительский класс
onItemClickListener.onItemClick(null, view, getAdapterPosition(), view.getId());
}
}
}
Использование адаптера в других классах:
MyFragment.java
public class MyFragment extends Fragment implements AdapterView.OnItemClickListener {
private RecyclerView recycleview;
private MyAdapter adapter;
// ...
private void init(Context context) {
// Передаем этот фрагмент как OnItemClickListener в адаптер
adapter = new MyAdapter(context, this, items);
recycleview.setAdapter(adapter);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Вы можете получить нажатый элемент из адаптера, используя его позицию
MyObject item = adapter.getItem(position);
// Также можете выяснить, какой вид был нажат
switch (view.getId()) {
case R.id.title:
// Нажат заголовок
break;
case R.id.avatar:
// Нажата аватарка
break;
default:
// Нажата вся строка
}
}
}
Надеюсь, это поможет вам реализовать обработку кликов в RecyclerView
более эффективно!
Вопрос: Как RecyclerView отличается от ListView?
Одно из основных отличий заключается в том, что RecyclerView использует класс LayoutManager
, который позволяет управлять его компоновкой. Например:
- Для горизонтальной или вертикальной прокрутки можно использовать
LinearLayoutManager
. - Для сеточной компоновки —
GridLayoutManager
. - Для ступенчатой сеточной компоновки —
StaggeredGridLayoutManager
.
Вот пример настройки горизонтальной прокрутки для RecyclerView:
LinearLayoutManager llm = new LinearLayoutManager(context);
llm.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(llm);
Таким образом, использование LayoutManager
в RecyclerView предоставляет большую гибкость в организации представления данных по сравнению с ListView.
Для решения вашей задачи можете использовать следующий код в главной активности. Это очень эффективный метод.
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.users_list);
UsersAdapter adapter = new UsersAdapter(users, this);
recyclerView.setAdapter(adapter);
adapter.setOnCardClickListner(this);
Теперь вот ваш класс адаптера.
public class UsersAdapter extends RecyclerView.Adapter<UsersAdapter.UserViewHolder> {
private ArrayList<User> mDataSet;
OnCardClickListner onCardClickListner;
public UsersAdapter(ArrayList<User> mDataSet) {
this.mDataSet = mDataSet;
}
@Override
public UserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.user_row_layout, parent, false);
UserViewHolder userViewHolder = new UserViewHolder(v);
return userViewHolder;
}
@Override
public void onBindViewHolder(UserViewHolder holder, final int position) {
holder.name_entry.setText(mDataSet.get(position).getUser_name());
holder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onCardClickListner.OnCardClicked(v, position);
}
});
}
@Override
public int getItemCount() {
return mDataSet.size();
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
public static class UserViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
TextView name_entry;
public UserViewHolder(View itemView) {
super(itemView);
cardView = (CardView) itemView.findViewById(R.id.user_layout);
name_entry = (TextView) itemView.findViewById(R.id.name_entry);
}
}
public interface OnCardClickListner {
void OnCardClicked(View view, int position);
}
public void setOnCardClickListner(OnCardClickListner onCardClickListner) {
this.onCardClickListner = onCardClickListner;
}
}
После этого вам нужно переопределить следующий метод в вашей активности:
@Override
public void OnCardClicked(View view, int position) {
Log.d("OnClick", "Card Position: " + position);
}
Это создаст список пользователей, который будет реагировать на нажатия на карточки. Убедитесь, что вы передали список пользователей в адаптер и правильно настроили макет user_row_layout
.
Как исправить 'android.os.NetworkOnMainThreadException'?
Ошибка «Необходимо переопределить метод суперкласса» после импорта проекта в Eclipse
Не удается запустить Eclipse - Java был запущен, но вернул код завершения = 13
Как вызвать метод с задержкой в Android
Как передать объект из одной активности в другую на Android