हैंडल बटन RecyclerView में एक पंक्ति के अंदर क्लिक करें


83

मैं पंक्ति क्लिक से निपटने के लिए निम्नलिखित कोड का उपयोग कर रहा हूं। ( स्रोत )

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

हालाँकि यह काम करता है, अगर मैं प्रत्येक पंक्ति पर एक डिलीट बटन कहना चाहता हूँ। मुझे यकीन नहीं है कि इसे कैसे लागू किया जाए।

मैंने बटन को हटाने के लिए OnClick श्रोता को संलग्न किया जो काम करता है (पंक्ति को हटाता है) लेकिन यह भी पूर्ण पंक्ति पर ऑनक्लिक को निकालता है।

अगर कोई सिंगल बटन क्लिक करता है तो क्या कोई मेरी पूरी मदद कर सकता है।

धन्यवाद।

जवाबों:


128

यह है कि मैं एक पुनर्नवीनीकरण दृश्य के अंदर कई ऑनक्लिक घटनाओं को कैसे संभालता हूं:

संपादित करें: कॉलबैक को शामिल करने के लिए अपडेट किया गया (जैसा कि अन्य टिप्पणियों में उल्लेख किया गया है)। मैंने संभावित मेमोरी लीक को खत्म करने WeakReferenceके ViewHolderलिए इन का उपयोग किया है ।

इंटरफ़ेस परिभाषित करें:

public interface ClickListener {

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

तब एडाप्टर:

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

फिर अपनी गतिविधि / खंड में - आप जो भी लागू कर सकते हैं: Clicklistener- या अनाम वर्ग यदि आप चाहें तो:

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

किस आइटम पर क्लिक किया गया, यह जानने के लिए आप आईडी ievgetId () == जो कुछ भी हो, से मेल खाते हैं।

आशा है कि यह दृष्टिकोण मदद करता है!


1
धन्यवाद, मैं इस पैटर्न का उपयोग केवल अपने कार्यान्वयन के लिए कर रहा था। हालाँकि मुद्दा कुछ और था। यहाँ पर एक नज़र डालें stackoverflow.com/questions/30287411/...
अश्विनी कश्मीर

1
"यह" कहाँ से खींचा जा रहा है? इस कोई अनुकूलक के भीतर जब तक आप संदर्भ से मेल है, और साथ ही साथ जब मैं ऐसा कुछ कारण यह है कि यह करने के लिए View.OnclickListener कास्टिंग है के लिए
Lion789

2
thisअपने आप को संदर्भित कर रहा है, ViewHolder (जो इस मामले में एक अलग स्थिर वर्ग है) - आप श्रोता को उस व्यूअर पर सेट कर रहे हैं, जिसके पास आपका डेटा इसमें बँधा हुआ है onBindViewHolder(), इसका एडाप्टर में संदर्भ से कोई लेना-देना नहीं है। मुझे नहीं पता कि आपको क्या समस्या हो रही है, लेकिन यह समाधान ठीक काम करता है।
मार्क कीन

1
@YasithaChinthaka क्या आपने इस विशेषता को सेट करने का प्रयास किया है: android:background="?attr/selectableItemBackground"दृश्य के लिए अपने xml में?
मार्क कीन

2
बस द्वारा ड्रॉप और धन्यवाद कहना चाहते हैं, इस समाधान का पालन करना और कार्यान्वित करना वास्तव में आसान है।
कोडगैस

51

मुझे लगता है कि आम तौर पर:

  • मुझे कई श्रोताओं का उपयोग करने की आवश्यकता है क्योंकि मेरे पास कई बटन हैं।
  • मैं चाहता हूं कि मेरा तर्क गतिविधि में हो न कि एडॉप्टर या व्यूहोल्डर में।

तो @ मार्क-कीन का उत्तर अच्छी तरह से काम करता है लेकिन एक इंटरफ़ेस होने से अधिक लचीलापन मिलता है:

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

जहाँ onClickListener आपके एडॉप्टर में परिभाषित किया गया है:

public MyAdapterListener onClickListener;

public interface MyAdapterListener {

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

और शायद अपने निर्माता के माध्यम से सेट करें:

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

    rows = newRows;
    onClickListener = listener;
}

तब आप अपनी गतिविधि में या जहाँ आपके 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);

यह एक और तरीका है, और मेरे जवाब के शीर्ष पर बनाता है (एक समान मैं खुद का उपयोग करता हूं), हालांकि आप onClickListenerस्थिर नेस्टेड व्यूहोल्डर वर्ग में कैसे गुजर रहे हैं ? जब तक मैं कुछ याद नहीं कर रहा हूँ मैं नहीं देख सकता कि आप इसे अपने ViewHolder में कैसे पास करते हैं। इसके अलावा, यदि आप सिर्फ एक इंटरफ़ेस विधि का उपयोग करते हैं, तो आप एक लैम्ब्डा अभिव्यक्ति का उपयोग कर सकते हैं, जो सब कुछ नीचे करता है।
मार्क कीन

सार्वजनिक MyAdapterListener onClickListener; ऊपर दिए गए कोड में आपके एडॉप्टर में परिभाषित एक सदस्य चर है और आपके एडेप्टर कंस्ट्रक्टर में सेट है। (वैकल्पिक रूप से, लेकिन ऊपर नहीं दिखाया गया है, आप setOnClickListener जैसे एक कस्टम सेटर का भी उपयोग कर सकते हैं।)
लॉर्ड्सपर्ले

5
मैं सिर्फ यह पूछ रहा था कि आप अपने एडेप्टर वर्ग में एक स्थिर नेस्टेड क्लास (ViewHolder) से किसी सदस्य / इंस्टेंस चर का उपयोग कैसे कर रहे हैं।
मार्क कीन

मार्क की तरह, मैं एडॉप्टर के अंदर घोंसला बनाने में सक्षम नहीं था। घोंसला बनाने से बचने के बारे में मेरा जवाब देखें
टोनी बेनब्राहिम

@MarkKeen .. बिल्कुल वही सवाल जो मेरे पास था।
user2695433

6

मैं एक ऐसा समाधान चाहता था जिससे कोई अतिरिक्त वस्तु न बने (यानी श्रोता) जिसे बाद में कचरा एकत्र करना पड़े, और उसे एडॉप्टर क्लास के अंदर व्यू होल्डर को नेस्ट करने की आवश्यकता नहीं थी।

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

नोट करने के लिए बिंदु: ClickHandlerइंटरफ़ेस परिभाषित है, लेकिन यहां आरंभीकृत नहीं है, इसलिए इसमें कोई धारणा नहीं हैonClick पद्धति कि यह कभी भी आरंभिक था।

ClickHandlerइंटरफ़ेस इस तरह दिखता है:

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

एडेप्टर में, व्यूअर में onBindViewHolder`ClickHandler 'को इनिशियलाइज़ करने के लिए, कंस्ट्रक्टर में' ClickHandler 'का एक उदाहरण सेट करें और ओवरराइड करें :

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

नोट: मुझे पता है कि viewHolder.clickHandler संभावित रूप से सटीक एक ही मूल्य के साथ कई बार सेट हो रहा है, लेकिन यह नल और ब्रांचिंग के लिए जाँच करने की तुलना में सस्ता है, और कोई अतिरिक्त लागत नहीं है।

अंत में, जब आप एडॉप्टर बनाते हैं, तो आपको ClickHandlerकंस्ट्रक्टर को एक उदाहरण देने के लिए मजबूर किया जाता है , जैसे:

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

ध्यान दें कि adapterयहाँ एक सदस्य चर है, स्थानीय चर नहीं


आपके जवाब के लिए थैंक्यू :) एक बात जो मैं यहां जोड़ना चाहता हूं वह है अनुकूलक का उपयोग न करें। स्थिति (स्थिति) के बजाय yourmodel.get (स्थिति) का उपयोग करें
Khubaib Raza

5

बस एक और समाधान जोड़ना चाहते हैं यदि आपके पास पहले से ही एक पुनर्नवीनीकरण स्पर्श श्रोता है और दृश्य धारक में अलग से बटन स्पर्श घटना से निपटने के बजाय इसमें सभी स्पर्श घटनाओं को संभालना चाहते हैं। क्लास के इस अनुकूलित संस्करण की प्रमुख बात यह है कि जब आइटम टैप किया जाता है, तब टैप किया जाता है, जब इसे टैप किया गया होता है, तब इटिनेम पर क्लिक करें। फिर आप एक बटन होने के दृश्य के लिए परीक्षण कर सकते हैं, और एक अलग कार्रवाई कर सकते हैं। ध्यान दें, बटन पर लंबी टैपिंग की व्याख्या पूरी पंक्ति पर एक लंबे टैप के रूप में की जाती है।

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

    }
}

फिर गतिविधि / टुकड़े से इसका उपयोग करना:

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

onInterceptTouchEvent()जब आप क्लिक ईवेंट को हैंडल करते हैं, तो आपको अंदर लौटने की आवश्यकता होती है ।


1
नमस्ते, क्या आप इस पर अधिक विस्तृत हो सकते हैं। मैं बटन btnDelete = (ImageButton) itemView.findViewById (R.id.btnDelete) को हटाने के लिए बाइंड करने के लिए निम्नलिखित कोड का उपयोग कर रहा हूं; btnDelete.setOnClickListener (नया दृश्य ।nClickListener () {@Override सार्वजनिक शून्य onClick (दृश्य देखें) {निकालें (getLayoutPosition ());}}});
अश्विनी के

OnTouchEvent () की तरह रिटर्न मान इंगित करता है कि ईवेंट को संभाला गया है या नहीं, और जब इसकी घटना पूर्ण पंक्ति में नहीं गई है।
एलियाहू शवार्ट्ज

0

आप देख सकते हैं कि क्या आपके पास पहले से कोई समान प्रविष्टियां हैं, यदि आपको आकार 0 के साथ एक संग्रह मिलता है, तो सहेजने के लिए एक नई क्वेरी शुरू करें।

या

अधिक पेशेवर और तेज़ तरीका। क्लाउड ट्रिगर बनाएं (सहेजने से पहले)

इस उत्तर की जाँच करें https://stackoverflow.com/a/35194514/1388852


0

बस getItemId नाम की एक ओवरराइड विधि डालें, इसे राइट क्लिक करके प्राप्त करें> जनरेट करें ओवरराइड विधियों> getItemId इस विधि को एडॉप्टर क्लास में रखें

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.