10

Почему в RecyclerView отсутствует onItemClickListener()?

17

Я изучал 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 ответ(ов)

12

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 наконец-то это признал.

0

Вы можете использовать этот способ, и он действительно позволяет легко обрабатывать нажатия на элементы 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));
    }
}

Хотя я читал, что существуют и более сложные способы решения этой задачи, я тоже считаю, что данный подход прост и понятен.

0

Благодарю @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 более эффективно!

0

Вопрос: Как RecyclerView отличается от ListView?

Одно из основных отличий заключается в том, что RecyclerView использует класс LayoutManager, который позволяет управлять его компоновкой. Например:

  • Для горизонтальной или вертикальной прокрутки можно использовать LinearLayoutManager.
  • Для сеточной компоновки — GridLayoutManager.
  • Для ступенчатой сеточной компоновки — StaggeredGridLayoutManager.

Вот пример настройки горизонтальной прокрутки для RecyclerView:

LinearLayoutManager llm = new LinearLayoutManager(context);
llm.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(llm);

Таким образом, использование LayoutManager в RecyclerView предоставляет большую гибкость в организации представления данных по сравнению с ListView.

0

Для решения вашей задачи можете использовать следующий код в главной активности. Это очень эффективный метод.

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.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь