Обработка выбора элемента в RecyclerView

Последнее обновление: 16.10.2021

При работе с виджетом RecyclerView неизбежно встанет вопрос, а как обработать выбор элемента в RecyclerView. И тут надо заметить, что в отличие от других типов виджетов для работы со списками RecyclerView по умолчанию не предоставляет какого-то специального метода, с помощью которого можно было бы определить слушатель нажатия на элемент в списке. Поэтому всю инфраструктуру необходимо определять самому разработчику. Но к счастью в реальности все не так сложно. Для примера возьмем проект из прошлой темы:

Проект для RecyclerView в Android и Java

Итак, для представления данных в проекте есть класс State, который представляет государство:

package com.example.listapp;

public class State {

    private String name; // название
    private String capital;  // столица
    private int flagResource; // ресурс флага

    public State(String name, String capital, int flag){

        this.name=name;
        this.capital=capital;
        this.flagResource=flag;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCapital() {
        return this.capital;
    }

    public void setCapital(String capital) {
        this.capital = capital;
    }

    public int getFlagResource() {
        return this.flagResource;
    }

    public void setFlagResource(int flagResource) {
        this.flagResource = flagResource;
    }
}

Класс State содержит поля для хранения названия и столицы страны, а также ссылку на ресурс изображения флага страны. В данном случае предполагается, что в папке res/drawable будут располагаться файлы изображений флагов для используемых стран.

В папке res/layout для вывода одного объекта State в списке определен следующий файл list_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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">
    <ImageView
        android:id="@+id/flag"
        android:layout_width="70dp"
        android:layout_height="50dp"
        android:layout_marginTop="16dp"
        android:layout_marginBottom="16dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/name"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />

    <TextView
        android:id="@+id/name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:text="Название"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/flag"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/capital" />

    <TextView
        android:id="@+id/capital"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:text="Столица"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/flag"
        app:layout_constraintTop_toBottomOf="@+id/name"
        app:layout_constraintBottom_toBottomOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Теперь перейдем к классу StateAdapter и следующим образом определим его код:

package com.example.listapp;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class StateAdapter  extends RecyclerView.Adapter<StateAdapter.ViewHolder>{

    interface OnStateClickListener{
        void onStateClick(State state, int position);
    }

    private final OnStateClickListener onClickListener;

    private final LayoutInflater inflater;
    private final List<State> states;

    StateAdapter(Context context, List<State> states, OnStateClickListener onClickListener) {
        this.onClickListener = onClickListener;
        this.states = states;
        this.inflater = LayoutInflater.from(context);
    }
    @Override
    public StateAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = inflater.inflate(R.layout.list_item, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(StateAdapter.ViewHolder holder, int position) {
        State state = states.get(position);
        holder.flagView.setImageResource(state.getFlagResource());
        holder.nameView.setText(state.getName());
        holder.capitalView.setText(state.getCapital());

        holder.itemView.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v)
            {
                onClickListener.onStateClick(state, position);
            }
        });
    }

    @Override
    public int getItemCount() {
        return states.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        final ImageView flagView;
        final TextView nameView, capitalView;
        ViewHolder(View view){
            super(view);
            flagView = view.findViewById(R.id.flag);
            nameView = view.findViewById(R.id.name);
            capitalView = view.findViewById(R.id.capital);
        }
    }
}

Здесь я остановлюсь на тех моментах, которые были добавлены по сравнению с кодом из прошлой статьи.

Прежде всего нам надо определить интерфейс слушателя события нажатия. Для этого в классе StateAdapter определен интерфейс:

interface OnStateClickListener{
	void onStateClick(State state, int position);
}

Интерфейс определяет один метод onStateClick(), который, как предполагается, будет вызываться при выборе объекта State и который будет получать выбранный объект State и его позицию в списке.

Следующий момент - определение в классе адаптера переменной для хранения объекта этого интерфейса и получение для нее значения в конструкторе:

private final OnStateClickListener onClickListener;

StateAdapter(Context context, List<State> states, OnStateClickListener onClickListener) {
	
	this.onClickListener = onClickListener;
	// ........................
}

Таким образом, вне кода адаптера мы можем установить любой объект слушателя и передать его в адаптер.

И третий момент - вызов метода слушателя при нажатии на элемент в списке в методе onBindViewHolder:

public void onBindViewHolder(StateAdapter.ViewHolder holder, int position) {
	State state = states.get(position);
	holder.flagView.setImageResource(state.getFlagResource());
	holder.nameView.setText(state.getName());
	holder.capitalView.setText(state.getCapital());

	// обработка нажатия
	holder.itemView.setOnClickListener(new View.OnClickListener(){
		@Override
		public void onClick(View v)
		{
			// вызываем метод слушателя, передавая ему данные
			onClickListener.onStateClick(state, position);
		}
	});
}

Класс ViewHolder имеет поле itemView, которое представляет интерфейс для одного объекта в списке и фактически объект View. А у этого объекта есть метод setOnClickListener(), через который можно подлючить стандартный слушатель нажатия OnClickListener и в его методе onClick() вызвать метод нашего интерфейса, передав ему необходимые данные - выбранный объект State и его позицию в списке.

Может возникнуть вопрос, а почему бы сразу тут и не обработать нажатие на элемент? К чему создавать дополнительный интерфейс, его переменную и вызывать его метод? Конечно, мы можем попыться прямо тут обработать нажатия, но это не является хорошей или распространенной практикой, поскольку, возможно, мы захотим определить обработку нажатия в классе MainActivity исходя из того, кода, который там определен (или из какого-то другого места извне).

В файле activity_main.xml остается тот же самый визуальный интерфейс:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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="match_parent"
    android:padding="16dp">
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="0dp"
        android:layout_height="0dp"
		
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
		
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

И в конце изменим класс MainActivity:

package com.example.listapp;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.widget.Toast;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    ArrayList<State> states = new ArrayList<State>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // начальная инициализация списка
        setInitialData();
        RecyclerView recyclerView = findViewById(R.id.list);
        // определяем слушателя нажатия элемента в списке
        StateAdapter.OnStateClickListener stateClickListener = new StateAdapter.OnStateClickListener() {
            @Override
            public void onStateClick(State state, int position) {

                Toast.makeText(getApplicationContext(), "Был выбран пункт " + state.getName(),  
                        Toast.LENGTH_SHORT).show();
            }
        };
        // создаем адаптер
        StateAdapter adapter = new StateAdapter(this, states, stateClickListener);
        // устанавливаем для списка адаптер
        recyclerView.setAdapter(adapter);
    }
    private void setInitialData(){

        states.add(new State ("Бразилия", "Бразилиа", R.drawable.brazilia));
        states.add(new State ("Аргентина", "Буэнос-Айрес", R.drawable.argentina));
        states.add(new State ("Колумбия", "Богота", R.drawable.columbia));
        states.add(new State ("Уругвай", "Монтевидео", R.drawable.uruguai));
        states.add(new State ("Чили", "Сантьяго", R.drawable.chile));
    }
}

При создании адаптера ему передается определенный в классе MainActivity слушатель:

StateAdapter.OnStateClickListener stateClickListener = new StateAdapter.OnStateClickListener() {
	@Override
	public void onStateClick(State state, int position) {

		Toast.makeText(getApplicationContext(), "Был выбран пункт " + state.getName(),  
                        Toast.LENGTH_SHORT).show();
	}
};

Здесь просто выводится всплывающее сообщение о выбранном элементе списка.

OnClickListener в ViewHolder и RecyclerView в Android и Java
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850