web-dev-qa-db-fra.com

Cliquez avec le bouton de la poignée dans une rangée dans RecyclerView

J'utilise le code suivant pour gérer les clics de ligne. ( source )

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) {
    }
}

Cela fonctionne cependant, si je veux avoir un bouton Supprimer sur chaque ligne. Je ne suis pas sûr de savoir comment mettre cela en œuvre avec cela.

J'ai attaché l'écouteur OnClick pour supprimer le bouton qui fonctionne (supprime la ligne), mais il déclenche également le clic onClick sur la ligne complète.

Quelqu'un peut-il m'aider à éviter de cliquer sur une ligne entière si un seul bouton est cliqué?.

Merci.

75
Ashwani K

voici comment je gère plusieurs événements onClick dans un recyclerView:

Edit: Mis à jour pour inclure les rappels (comme mentionné dans d'autres commentaires). J'ai utilisé un WeakReference dans le ViewHolder pour éliminer une fuite de mémoire potentielle.

Définir l'interface:

public interface ClickListener {

    void onPositionClicked(int position);

    void onLongClicked(int position);
}

Puis l'adaptateur:

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;
        }
    }
}

Ensuite, dans votre activité/fragment - tout ce que vous pouvez implémenter: Clicklistener - ou une classe anonyme si vous le souhaitez:

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
            }
        });

Pour obtenir sur quel élément vous avez cliqué, vous faites correspondre l'identifiant de la vue, à savoir v.getId () == WhateverItem.getId ()

J'espère que cette approche aide!

120
Mark Keen

Je trouve ça typiquement:

  • Je dois utiliser plusieurs écouteurs car j'ai plusieurs boutons.
  • Je veux que ma logique soit dans l'activité et non dans l'adaptateur ou le viewholder.

Donc, la réponse de @ mark-keen fonctionne bien, mais avoir une interface offre plus de flexibilité:

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());
            }
        });
    }
}

Où onClickListener est défini dans votre adaptateur:

public MyAdapterListener onClickListener;

public interface MyAdapterListener {

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

Et probablement défini par votre constructeur:

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

    rows = newRows;
    onClickListener = listener;
}

Vous pouvez ensuite gérer les événements de votre activité ou de l’utilisation de votre RecyclerView:

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);
45
LordParsley

Je voulais une solution qui ne crée aucun objet extra (c'est-à-dire des auditeurs) qui devrait être récupéré ultérieurement et ne nécessite pas l'imbrication d'un détenteur de vue dans une classe d'adaptateur.

Dans la classe ViewHolder

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());
            }
        }

Points à noter: l'interface ClickHandler est définie, mais n'est pas initialisée ici. Il n'y a donc aucune hypothèse dans la méthode onClick qu'elle ait déjà été initialisée.

L’interface ClickHandler ressemble à ceci:

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

Dans l'adaptateur, définissez une instance de 'ClickHandler' dans le constructeur et remplacez onBindViewHolder pour initialiser `clickHandler 'sur le détenteur de la vue:

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;
    }

Remarque: Je sais que viewHolder.clickHandler est potentiellement configuré plusieurs fois avec la même valeur, mais cela revient moins cher que de rechercher la valeur NULL et la création de branches, et il n'y a pas de coût de mémoire, mais seulement instruction.

Enfin, lorsque vous créez l'adaptateur, vous êtes obligé de transmettre une instance ClickHandler au constructeur, comme suit:

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
    }
});

Notez que adapter est une variable membre ici, pas une variable locale

6
Tony BenBrahim

Je voulais simplement ajouter une autre solution si vous avez déjà un écouteur tactile recycleur et que vous souhaitez gérer tous les événements tactiles qu'il contient plutôt que de traiter l'événement tactile avec le bouton séparément dans le détenteur de la vue. L’essentiel pour cette version adaptée de la classe est de retourner la vue du bouton dans le rappel onItemClick () lorsqu’elle est tapée, par opposition au conteneur d’élément. Vous pouvez ensuite tester que la vue est un bouton et effectuer une action différente. Remarque: un appui long sur le bouton est interprété comme un appui long sur toute la ligne.

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) {

    }
}

Ensuite, en l'utilisant depuis activité/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) {
            }
        });
    }
4
vipes

Vous devez renvoyer true dans onInterceptTouchEvent() lorsque vous gérez un événement clic.

2
Eliyahu Shwartz

Il suffit de mettre une méthode de substitution nommée getItemId Obtenez-la par clic droit> générer> méthodes de substitution> getItemId Placez cette méthode dans la classe Adapter

0
Anurag Bhalekar

Vous pouvez d'abord vérifier si vous avez des entrées similaires. Si vous obtenez une collection de taille 0, lancez une nouvelle requête à enregistrer.

OR

manière plus professionnelle et plus rapide. créer un déclencheur de nuage (avant la sauvegarde)

découvrez cette réponse https://stackoverflow.com/a/35194514/1388852

0
Hatim