Handle Button Klicken Sie in RecyclerView in eine Zeile


81

Ich verwende den folgenden Code für die Behandlung von Zeilenklicks. ( Quelle )

static class RecyclerTouchListener implements RecyclerView.OnItemTouchListener {

    private GestureDetector gestureDetector;
    private ClickListener clickListener;

    public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ClickListener clickListener) {
        this.clickListener = clickListener;
        gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return true;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
                if (child != null && clickListener != null) {
                    clickListener.onLongClick(child, recyclerView.getChildPosition(child));
                }
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

        View child = rv.findChildViewUnder(e.getX(), e.getY());
        if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) {
            clickListener.onClick(child, rv.getChildPosition(child));
        }
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
    }
}

Dies funktioniert jedoch, wenn ich in jeder Zeile eine Schaltfläche zum Löschen sagen möchte. Ich bin mir nicht sicher, wie ich das damit umsetzen soll.

Ich habe den OnClick-Listener an die Schaltfläche zum Löschen angehängt, die funktioniert (löscht die Zeile), aber auch den Onclick für die gesamte Zeile auslöst.

Kann mir jemand helfen, wie ich das Klicken in ganzen Zeilen vermeiden kann, wenn auf eine einzelne Schaltfläche geklickt wird?

Vielen Dank.

Antworten:


128

So behandle ich mehrere onClick-Ereignisse in einer recyclerView:

Bearbeiten: Aktualisiert, um Rückrufe einzuschließen (wie in anderen Kommentaren erwähnt). Ich habe ein WeakReferencein der verwendet ViewHolder, um einen möglichen Speicherverlust zu beseitigen.

Schnittstelle definieren:

public interface ClickListener {

    void onPositionClicked(int position);
    
    void onLongClicked(int position);
}

Dann der Adapter:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    
    private final ClickListener listener;
    private final List<MyItems> itemsList;

    public MyAdapter(List<MyItems> itemsList, ClickListener listener) {
        this.listener = listener;
        this.itemsList = itemsList;
    }

    @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.my_row_layout), parent, false), listener);
    }

    @Override public void onBindViewHolder(MyViewHolder holder, int position) {
        // bind layout and data etc..
    }

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

    public static class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {

        private ImageView iconImageView;
        private TextView iconTextView;
        private WeakReference<ClickListener> listenerRef;

        public MyViewHolder(final View itemView, ClickListener listener) {
            super(itemView);

            listenerRef = new WeakReference<>(listener);
            iconImageView = (ImageView) itemView.findViewById(R.id.myRecyclerImageView);
            iconTextView = (TextView) itemView.findViewById(R.id.myRecyclerTextView);

            itemView.setOnClickListener(this);
            iconTextView.setOnClickListener(this);
            iconImageView.setOnLongClickListener(this);
        }

        // onClick Listener for view
        @Override
        public void onClick(View v) {

            if (v.getId() == iconTextView.getId()) {
                Toast.makeText(v.getContext(), "ITEM PRESSED = " + String.valueOf(getAdapterPosition()), Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(v.getContext(), "ROW PRESSED = " + String.valueOf(getAdapterPosition()), Toast.LENGTH_SHORT).show();
            }
            
            listenerRef.get().onPositionClicked(getAdapterPosition());
        }


        //onLongClickListener for view
        @Override
        public boolean onLongClick(View v) {

            final AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext());
            builder.setTitle("Hello Dialog")
                    .setMessage("LONG CLICK DIALOG WINDOW FOR ICON " + String.valueOf(getAdapterPosition()))
                    .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {

                        }
                    });

            builder.create().show();
            listenerRef.get().onLongClicked(getAdapterPosition());
            return true;
        }
    }
}

Dann in Ihrer Aktivität / Ihrem Fragment - was auch immer Sie implementieren können: Clicklistener- oder in einer anonymen Klasse, wenn Sie dies wünschen:

MyAdapter adapter = new MyAdapter(myItems, new ClickListener() {
            @Override public void onPositionClicked(int position) {
                // callback performed on click
            }

            @Override public void onLongClicked(int position) {
                // callback performed on click
            }
        });

Um zu erhalten, auf welches Element geklickt wurde, stimmen Sie mit der Ansichts-ID ievgetId () == WhateverItem.getId () überein.

Hoffe dieser Ansatz hilft!


1
Vielen Dank, ich habe dieses Muster nur für meine Implementierung verwendet. Das Problem war jedoch etwas anderes. Schauen Sie sich hier stackoverflow.com/questions/30287411/… an
Ashwani K

1
Woher wird "das" gezogen? Es gibt dies nicht im Adapter, es sei denn, Sie stimmen mit dem Kontext überein, und wenn ich das aus irgendeinem Grund mache, ist es auch Casting View.OnclickListener dazu
Lion789

2
thisverweist auf sich selbst, den ViewHolder (in diesem Fall eine separate statische Klasse). Wenn Sie den Listener auf den Viewholder setzen, an den Ihre Daten gebunden sind onBindViewHolder(), hat dies nichts mit dem Kontext im Adapter zu tun. Ich weiß nicht, welche Probleme Sie genau haben, aber diese Lösung funktioniert gut.
Mark Keen

1
@YasithaChinthaka Haben Sie versucht, dieses Attribut festzulegen: android:background="?attr/selectableItemBackground"in Ihrer XML für die Ansicht?
Mark Keen

2
Ich möchte nur vorbeischauen und mich bedanken, diese Lösung ist wirklich einfach zu befolgen und zu implementieren.
CodeGeass

50

Ich finde das typisch:

  • Ich muss mehrere Listener verwenden, da ich mehrere Schaltflächen habe.
  • Ich möchte, dass meine Logik in der Aktivität und nicht im Adapter oder Ansichtsinhaber enthalten ist.

Die Antwort von @ mark-scharf funktioniert also gut, aber eine Benutzeroberfläche bietet mehr Flexibilität:

public static class MyViewHolder extends RecyclerView.ViewHolder {

    public ImageView iconImageView;
    public TextView iconTextView;

    public MyViewHolder(final View itemView) {
        super(itemView);

        iconImageView = (ImageView) itemView.findViewById(R.id.myRecyclerImageView);
        iconTextView = (TextView) itemView.findViewById(R.id.myRecyclerTextView);

        iconTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onClickListener.iconTextViewOnClick(v, getAdapterPosition());
            }
        });
        iconImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onClickListener.iconImageViewOnClick(v, getAdapterPosition());
            }
        });
    }
}

Wo onClickListener in Ihrem Adapter definiert ist:

public MyAdapterListener onClickListener;

public interface MyAdapterListener {

    void iconTextViewOnClick(View v, int position);
    void iconImageViewOnClick(View v, int position);
}

Und wahrscheinlich durch Ihren Konstruktor eingestellt:

public MyAdapter(ArrayList<MyListItems> newRows, MyAdapterListener listener) {

    rows = newRows;
    onClickListener = listener;
}

Dann können Sie die Ereignisse in Ihrer Aktivität oder überall dort behandeln, wo Ihre RecyclerView verwendet wird:

mAdapter = new MyAdapter(mRows, new MyAdapter.MyAdapterListener() {
                    @Override
                    public void iconTextViewOnClick(View v, int position) {
                        Log.d(TAG, "iconTextViewOnClick at position "+position);
                    }

                    @Override
                    public void iconImageViewOnClick(View v, int position) {
                        Log.d(TAG, "iconImageViewOnClick at position "+position);
                    }
                });
mRecycler.setAdapter(mAdapter);

Dies ist eine andere Möglichkeit und baut auf meiner Antwort auf (eine ähnliche, die ich selbst verwende). Wie gelangen Sie onClickListenerjedoch zur statisch verschachtelten Viewholder-Klasse? Wenn mir nichts fehlt, kann ich nicht sehen, wie Sie es an Ihren ViewHolder weitergeben. Wenn Sie nur eine Schnittstellenmethode verwenden, können Sie auch einen Lambda-Ausdruck verwenden, der alles komprimiert.
Mark Keen

public MyAdapterListener onClickListener; ist eine Mitgliedsvariable, die in Ihrem Adapter im obigen Code definiert und in Ihrem Adapterkonstruktor festgelegt ist. (Alternativ, aber oben nicht gezeigt, können Sie auch einen benutzerdefinierten Setter wie setOnClickListener verwenden.)
LordParsley

5
Ich habe nur gefragt, wie Sie innerhalb einer statisch verschachtelten Klasse (ViewHolder) auf eine Member- / Instanzvariable in Ihrer Adapterklasse zugreifen.
Mark Keen

Wie Mark konnte ich nicht im Adapter nisten. siehe meine Antwort, wie man vermeiden muss, nisten zu müssen
Tony BenBrahim

@ MarkKeen .. genau die gleiche Frage, die ich hatte.
user2695433

6

Ich wollte eine Lösung, die keine zusätzlichen Objekte (dh Listener) erstellt, die später als Müll gesammelt werden müssten, und die kein Verschachteln eines Ansichtshalters in einer Adapterklasse erforderte.

In der ViewHolderKlasse

private static class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        private final TextView ....// declare the fields in your view
        private ClickHandler ClickHandler;

        public MyHolder(final View itemView) {
            super(itemView);
            nameField = (TextView) itemView.findViewById(R.id.name);
            //find other fields here...
            Button myButton = (Button) itemView.findViewById(R.id.my_button);
            myButton.setOnClickListener(this);
        }
        ...
        @Override
        public void onClick(final View view) {
            if (clickHandler != null) {
                clickHandler.onMyButtonClicked(getAdapterPosition());
            }
        }

Zu beachtende Punkte: Die ClickHandlerSchnittstelle wird hier definiert, aber nicht initialisiert. Daher wird in der onClickMethode nicht davon ausgegangen , dass sie jemals initialisiert wurde.

Die ClickHandlerOberfläche sieht folgendermaßen aus:

private interface ClickHandler {
    void onMyButtonClicked(final int position);
} 

Legen Sie im Adapter eine Instanz von 'ClickHandler' im Konstruktor fest und überschreiben Sie diese onBindViewHolder, um 'clickHandler' auf dem Ansichtsinhaber zu initialisieren:

private class MyAdapter extends ...{

    private final ClickHandler clickHandler;

    public MyAdapter(final ClickHandler clickHandler) {
        super(...);
        this.clickHandler = clickHandler;
    }

    @Override
    public void onBindViewHolder(final MyViewHolder viewHolder, final int position) {
        super.onBindViewHolder(viewHolder, position);
        viewHolder.clickHandler = this.clickHandler;
    }

Hinweis: Ich weiß, dass viewHolder.clickHandler möglicherweise mehrmals mit genau demselben Wert festgelegt wird. Dies ist jedoch billiger als die Überprüfung auf Null und Verzweigung, und es fallen keine Speicherkosten an, sondern nur eine zusätzliche Anweisung.

Wenn Sie den Adapter erstellen, müssen Sie eine ClickHandlerInstanz wie folgt an den Konstruktor übergeben:

adapter = new MyAdapter(new ClickHandler() {
    @Override
    public void onMyButtonClicked(final int position) {
        final MyModel model = adapter.getItem(position);
        //do something with the model where the button was clicked
    }
});

Beachten Sie, dass dies adapterhier eine Mitgliedsvariable ist, keine lokale Variable


Vielen Dank für Ihre Antwort :) Eine Sache, die ich hier hinzufügen möchte, verwenden Sie nicht adapter.getItem (Position), sondern yourmodel.get (Position)
Khubaib Raza

5

Ich wollte nur eine weitere Lösung hinzufügen, wenn Sie bereits einen Recycler-Touch-Listener haben und alle darin enthaltenen Touch-Ereignisse behandeln möchten, anstatt das Touch-Ereignis der Schaltfläche separat im Ansichtshalter zu behandeln. Das Wichtigste, was diese angepasste Version der Klasse tut, ist, die Schaltflächenansicht im onItemClick () - Rückruf zurückzugeben, wenn sie getippt wird, im Gegensatz zum Elementcontainer. Sie können dann testen, ob die Ansicht eine Schaltfläche ist, und eine andere Aktion ausführen. Beachten Sie, dass ein langes Tippen auf die Schaltfläche als langes Tippen auf die gesamte Zeile interpretiert wird.

public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener
{
    public static interface OnItemClickListener
    {
        public void onItemClick(View view, int position);
        public void onItemLongClick(View view, int position);
    }

    private OnItemClickListener mListener;
    private GestureDetector mGestureDetector;

    public RecyclerItemClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener)
    {
        mListener = listener;

        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener()
        {
            @Override
            public boolean onSingleTapUp(MotionEvent e)
            {
                // Important: x and y are translated coordinates here
                final ViewGroup childViewGroup = (ViewGroup) recyclerView.findChildViewUnder(e.getX(), e.getY());

                if (childViewGroup != null && mListener != null) {
                    final List<View> viewHierarchy = new ArrayList<View>();
                    // Important: x and y are raw screen coordinates here
                    getViewHierarchyUnderChild(childViewGroup, e.getRawX(), e.getRawY(), viewHierarchy);

                    View touchedView = childViewGroup;
                    if (viewHierarchy.size() > 0) {
                        touchedView = viewHierarchy.get(0);
                    }
                    mListener.onItemClick(touchedView, recyclerView.getChildPosition(childViewGroup));
                    return true;
                }

                return false;
            }

            @Override
            public void onLongPress(MotionEvent e)
            {
                View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());

                if(childView != null && mListener != null)
                {
                    mListener.onItemLongClick(childView, recyclerView.getChildPosition(childView));
                }
            }
        });
    }

    public void getViewHierarchyUnderChild(ViewGroup root, float x, float y, List<View> viewHierarchy) {
        int[] location = new int[2];
        final int childCount = root.getChildCount();

        for (int i = 0; i < childCount; ++i) {
            final View child = root.getChildAt(i);
            child.getLocationOnScreen(location);
            final int childLeft = location[0], childRight = childLeft + child.getWidth();
            final int childTop = location[1], childBottom = childTop + child.getHeight();

            if (child.isShown() && x >= childLeft && x <= childRight && y >= childTop && y <= childBottom) {
                viewHierarchy.add(0, child);
            }
            if (child instanceof ViewGroup) {
                getViewHierarchyUnderChild((ViewGroup) child, x, y, viewHierarchy);
            }
        }
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e)
    {
        mGestureDetector.onTouchEvent(e);

        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView view, MotionEvent motionEvent){}

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
}

Dann benutze es aus Aktivität / Fragment:

recyclerView.addOnItemTouchListener(createItemClickListener(recyclerView));

    public RecyclerItemClickListener createItemClickListener(final RecyclerView recyclerView) {
        return new RecyclerItemClickListener (context, recyclerView, new RecyclerItemClickListener.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                if (view instanceof AppCompatButton) {
                    // ... tapped on the button, so go do something
                } else {
                    // ... tapped on the item container (row), so do something different
                }
            }

            @Override
            public void onItemLongClick(View view, int position) {
            }
        });
    }

1

Sie müssen true inside zurückgeben, onInterceptTouchEvent()wenn Sie das Klickereignis behandeln.


1
Hallo, kannst du das näher erläutern? Ich verwende folgenden Code zum Binden, um die Schaltfläche zum Löschen zu binden. BtnDelete = (ImageButton) itemView.findViewById (R.id.btnDelete); btnDelete.setOnClickListener (neuer View.OnClickListener () {@Override public void onClick (Ansichtsansicht) {remove (getLayoutPosition ());}});
Ashwani K

Wie bei onTouchEvent () gibt der Rückgabewert an, ob das Ereignis behandelt wurde oder nicht, und wenn dies nicht der Fall ist, wird das Ereignis an die vollständige Zeile übergeben.
Eliyahu Shwartz

0

Sie können zuerst überprüfen, ob Sie ähnliche Einträge haben. Wenn Sie eine Sammlung mit der Größe 0 erhalten, starten Sie eine neue Abfrage zum Speichern.

ODER

professioneller und schneller Weg. Erstellen Sie einen Cloud-Trigger (vor dem Speichern)

Überprüfen Sie diese Antwort https://stackoverflow.com/a/35194514/1388852


0

Fügen Sie einfach eine Überschreibungsmethode mit dem Namen getItemId ein. Klicken Sie mit der rechten Maustaste auf> generieren> Überschreibungsmethoden> getItemId. Fügen Sie diese Methode in die Adapterklasse ein

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.