Как вызвать метод MainActivity из ViewHolder в RecyclerView.Adapter?

В простом проекте приложения на GitHub у меня есть только 2 пользовательских Java-файла :

  1. MainActivity .java содержит исходный код, связанный с Bluetooth и пользовательским интерфейсом.
  2. DeviceListAdapter .java содержит Adapter и ViewHolder для отображения устройств Bluetooth в RecyclerView

скриншот приложения

MainActivity.java содержит метод, который вызывается, когда пользователь нажимает на устройство Bluetooth в RecyclerView:

public void confirmConnection(String address) {
    final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);

    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setMessage("Do you want to pair to " + device + "?");
    builder.setPositiveButton(R.string.button_ok, 
      new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            device.createBond();
        }
    });
    builder.setNegativeButton(R.string.button_cancel, null);
    builder.show();
}

А в классе ViewHolder (в файле DeviceListAdapter.java) определяется прослушиватель кликов:

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

  private ArrayList<BluetoothDevice> mDevices = new ArrayList<BluetoothDevice>();

  protected static class ViewHolder
        extends RecyclerView.ViewHolder
        implements View.OnClickListener {

    private TextView deviceAddress;

    public ViewHolder(View v) {
        super(v);
        v.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        String address = deviceAddress.getText().toString();

        Toast.makeText(v.getContext(),
                "How to call MainActivity.confirmConnection(address)?",
                Toast.LENGTH_SHORT).show();
    }
  }

Моя проблема:

Как вызвать метод confirmConnection(address) из метода ViewHolders onClick?

Я продолжаю перемещать объявление класса ViewHolder между двумя файлами Java, а также пытался поместить его в отдельный файл - и просто не могу найти правильный путь.

Должен ли я добавить поле в класс ViewHolder и (когда?) сохранить там ссылку на экземпляр MainActivity?

ОБНОВЛЕНИЕ:

Это работает для меня, но кажется обходным путем (а также я думал об использовании LocalBroadcastReceiver - что было бы еще более хакерским обходным путем) -

    @Override
    public void onClick(View v) {
        String address = deviceAddress.getText().toString();

        try {
            ((MainActivity) v.getContext()).confirmConnection(address);
        } catch (Exception e) {
            // ignore
        }
    }

person Alexander Farber    schedule 22.09.2015    source источник
comment
Ваш ViewHolder содержит представление. Это представление имеет контекст. Этот контекст, скорее всего, является вашей деятельностью. Так что приведите Context к MainActivity, и все будет в порядке. Однако использование интерфейсов является лучшим подходом. ((MainActivity)v.getContext()).confirmConnection() должен быть им.   -  person ElDuderino    schedule 22.09.2015


Ответы (5)


Чтобы ваши классы были разделены, я бы предложил определить интерфейс на вашем адаптере, например:

public interface OnBluetoothDeviceClickedListener {
    void onBluetoothDeviceClicked(String deviceAddress);
}

Затем добавьте для этого сеттер в свой адаптер:

private OnBluetoothDeviceClickedListener mBluetoothClickListener;

public void setOnBluetoothDeviceClickedListener(OnBluetoothDeviceClickedListener l) {
    mBluetoothClickListener = l;
}

Затем внутренне, в ваших ViewHolder onClick():

if (mBluetoothClickListener != null) {
    final String addresss = deviceAddress.getText().toString();
    mBluetoothClickListener.onBluetoothDeviceClicked(address);
}

Тогда просто дайте MainActivity пройти слушателю Adapter:

mDeviceListAdapter.setOnBluetoothDeviceClickedListener(new OnBluetoothDeviceClickedListener() {
    @Override
    public void onBluetoothDeviceClicked(String deviceAddress) {
        confirmConnection(deviceAddress);
    }
});

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

person Kevin Coppock    schedule 22.09.2015
comment
Что делать, если я получаю нестатический метод, на который нельзя ссылаться из ошибки статического контекста? - person user3484582; 30.06.2016
comment
@user3484582 user3484582 Вероятно, это означает, что вы пытаетесь вызвать метод в охватывающем классе из статического внутреннего класса (например, если у вас есть статический внутренний класс в вашей деятельности и вы пытаетесь вызвать метод в своей деятельности оттуда) - person Kevin Coppock; 30.06.2016
comment
Как можно использовать нестатическое поле mBluetoothClickListener в статическом классе ViewHolder? - person MBDevelop; 26.08.2016
comment
@MBDevelop, я написал ответ ниже: stackoverflow.com/a/40563598/2914140. - person CoolMind; 12.11.2016

Для тех, кто ищет вызов обратного вызова из статического ViewHolder, сделайте следующее. Пусть у вас есть адаптер:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    private final int resource;
    private final List<Item> items;
    private final LayoutInflater inflater;
    ...
    private Callback callback;

    private static class ViewHolder extends RecyclerView.ViewHolder {
        ...
    }

    public interface Callback {
        void onImageClick(int position);
        void onRemoveItem(int position);
    }
}

Затем вы должны добавить метод setCallback и вызывать его из действия/фрагмента. Также не стоит делать callback статическим (это может привести к проблемам при использовании одного и того же адаптера во многих классах). Вы должны создать поле внутри ViewHolder. Так:

    public MyAdapter(Context context, int resource, List<Item> items, Callback callback) {
        super();
        this.resource = resource;
        this.items = items;
        this.inflater = LayoutInflater.from(context);
        this.callback = callback;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        final ViewHolder viewHolder = (ViewHolder) holder;
        final Item item = this.items.get(position);
        viewHolder.caption.setText(item.caption);
        viewHolder.callback = callback;
    }

    // A method to set a callback from activity/fragment.
    public void setCallback(Callback callback) {
        this.callback = callback;
    }

    public static class Item {
        public long id;
        public String number;
        public String caption;

        ...
    }

    private static class ViewHolder extends RecyclerView.ViewHolder {
        protected final TextView caption;
        // A reference to an adapter's callback.
        protected Callback callback;

        public ViewHolder(View view) {
            super(view);
            this.caption = (TextView) view.findViewById(R.id.caption);
        }

        private View.OnClickListener onClickListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int id = v.getId();
                if (id == R.id.image) {
                    // Invoke the callback here.
                    if (callback != null) {
                        callback.onImageClick(getLayoutPosition());
                    }
                }
            }
        };
    }
}

После того, как вы сделали адаптер, вы можете вызвать его так:

adapter = new MyAdapter(getActivity(), R.layout.item,
        new ArrayList<MyAdapter.Item>(), null);

adapterListener = new MyAdapter.Callback() {
    @Override
    public void onImageClick(int position) {
        // Some actions.
    }

    @Override
    public void onRemoveItem(int position) {
        // Some actions.
    }
};

adapter.setCallback(adapterListener);
person CoolMind    schedule 12.11.2016

Вы можете передать MainActivity в качестве параметра-конструктора для адаптера и сохранить его в поле. Или вы используете шину событий - есть несколько способов сделать это - я бы пошел на поле

person ligi    schedule 22.09.2015

В вашем адаптере создайте интерфейс, который будет обеспечивать обратный вызов основной активности.

public interface MyCallback{
    void onItemClicked();
}

private MyCallback listener;

public setOnItemClickListener(MyCallback callback){
    listener = callback;
}

Пусть ваша основная деятельность реализует это.

public class MainActivity extends AppCompatActivity implements MyCallback

затем реализовать обратный вызов

@Override
public void onItemClick(){
    //do work
}

затем просто установите обратный вызов от адаптера

mDeviceListAdapter.setOnItemClickListener(this);
person tyczj    schedule 22.09.2015

Вы можете вызвать метод Activity, используя такой экземпляр Activity, внутри MainActivity напишите ниже код

mDeviceListAdapter = new DeviceListAdapter(MainActivity.this);

Внутренний адаптер

 private MainActivity _mainActivity;
 public DeviceListAdapter(MainActivity activity){
 this._mainActivity=activity;
 }

Внутри вашего метода onClick

 _mainActivity.yourActivityMethod(address);
person Ajit Kumar Dubey    schedule 22.09.2015