Клацніть на кнопці обробки всередині рядка в 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 для видалення, який працює (видаляє рядок), але він також запускає onclick у повному рядку.

Хто-небудь може мені допомогти, як уникнути клацання повного рядка, якщо натиснути одну кнопку.

Дякую.

Відповіді:


128

ось як я обробляю кілька подій onClick всередині recyclerView:

Редагувати: оновлено для включення зворотних дзвінків (як зазначено в інших коментарях). Я використовував WeakReferencein 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 () == whateverItem.getId ()

Сподіваюся, такий підхід допомагає!


1
Дякую, я використовував цей шаблон лише для моєї реалізації. Однак питання полягало в іншому. Подивіться тут stackoverflow.com/questions/30287411 / ...
Ashwani До

1
Звідки витягують "це"? В адаптері цього немає, якщо ви не збігаєтеся з контекстом, і коли я це роблю з якихось причин, це Casting View.OnclickListener і до нього
Lion789

2
thisпосилається на себе, ViewHolder (що в цьому випадку є окремим статичним класом) - ви встановлюєте слухача на Viewholder, до якого прив'язані ваші дані onBindViewHolder(), це не має нічого спільного з контекстом в адаптері. Я не знаю, які проблеми у вас точно виникають, але це рішення чудово працює.
Mark Keen

1
@YasithaChinthaka Ви пробували встановити цей атрибут: android:background="?attr/selectableItemBackground"у своєму xml для подання?
Mark Keen

2
Просто хочу зайти і сказати спасибі, це рішення дійсно легко дотримуватися та впроваджувати.
CodeGeass

51

Я вважаю, що зазвичай:

  • Мені потрібно використовувати кілька слухачів, оскільки у мене є кілька кнопок.
  • Я хочу, щоб моя логіка була в діяльності, а не в адаптері чи переглядачі.

Отже, відповідь @ mark-keen працює добре, але наявність інтерфейсу забезпечує більшу гнучкість:

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

Тоді ви можете обробляти події у вашому Activity або там, де використовується ваш 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? Якщо я чогось не пропускаю, я не бачу, як ви передаєте це своєму ViewHolder. Крім того, якщо ви просто використовуєте один метод інтерфейсу, ви можете використовувати вираз Лямбда, який стискає все.
Mark Keen

загальнодоступний MyAdapterListener onClickListener; є змінною члена, визначеною у вашому адаптері в коді вище та встановленою у вашому конструкторі адаптера. (В іншому випадку, але не показано вище, ви також можете використовувати власний сеттер, такий як setOnClickListener.)
LordParsley

5
Я просто запитував, як ви отримуєте доступ до змінної члена / екземпляра у своєму класі Adapter із статичного вкладеного класу (ViewHolder).
Mark Keen

Як і Марк, я не зміг вкластися всередину адаптера. див. мою відповідь про те, як уникнути гніздування
Тоні БенБрахім,

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

В адаптері встановіть екземпляр 'ClickHandler' у конструкторі та перевизначте onBindViewHolder, щоб ініціалізувати '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тут є змінною-членом, а не локальною змінною


Дякую за відповідь :) Одне, що я хочу додати тут, не використовуйте adapter.getItem (position), а не yourmodel.get (position)
Khubaib Raza

5

Просто хотів додати ще одне рішення, якщо у вас вже є прослуховувач дотику до утилізатора і ви хочете обробляти всі події дотику в ньому, а не мати справу з подією дотику кнопок окремо у тримачі перегляду. Ключовим, що робить ця адаптована версія класу, є повернення подання кнопки у зворотному виклику onItemClick (), коли він натискається, на відміну від контейнера елементів. Потім ви можете перевірити, чи вигляд є кнопкою, і виконати іншу дію. Зверніть увагу, тривале натискання кнопки інтерпретується як тривале натискання на весь рядок.

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

Вам потрібно повернути true всередині, onInterceptTouchEvent()коли ви обробляєте подію кліку.


1
Привіт, чи можете ви бути більш докладно про це. Я використовую наступний код для прив'язки кнопки видалення btnDelete = (ImageButton) itemView.findViewById (R.id.btnDelete); btnDelete.setOnClickListener (новий View.OnClickListener () {@Override public void onClick (View view) {remove (getLayoutPosition ());}});
Ashwani K,

Подібно до onTouchEvent (), повернене значення вказує, чи оброблялася подія чи ні, і коли її не подія передається в повний рядок.
Еліяху Шварц,

0

Ви можете спершу перевірити, чи є у вас подібні записи, якщо ви отримаєте колекцію розміром 0, розпочніть новий запит для збереження.

АБО

більш професійний і швидкий спосіб. створити хмарний тригер (перед збереженням)

перевірте цю відповідь https://stackoverflow.com/a/35194514/1388852


0

Просто помістіть метод перевизначення з іменем getItemId Отримайте його клацанням правою кнопкою миші> згенеруйте> методи перевизначення> getItemId Помістіть цей метод у клас Adapter

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.