Nút xử lý nhấp vào bên trong một hàng trong RecyclerView


81

Tôi đang sử dụng mã sau để xử lý các nhấp chuột vào hàng. ( nguồn )

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

Tuy nhiên, điều này hoạt động, nếu tôi muốn nói một nút xóa trên mỗi hàng. Tôi không chắc chắn về cách thực hiện điều đó với điều này.

Tôi đã đính kèm trình nghe OnClick để xóa nút hoạt động (xóa hàng) nhưng nó cũng kích hoạt onclick trên toàn bộ hàng.

Ai có thể giúp tôi làm thế nào để tránh nhấp toàn bộ hàng nếu một nút được nhấp vào.

Cảm ơn.

Câu trả lời:


128

đây là cách tôi xử lý nhiều sự kiện onClick bên trong một tái chếView:

Chỉnh sửa: Đã cập nhật để bao gồm các cuộc gọi lại (như đã đề cập trong các nhận xét khác). Tôi đã sử dụng a WeakReferencein the ViewHolderđể loại bỏ khả năng bị rò rỉ bộ nhớ.

Xác định giao diện:

public interface ClickListener {

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

Sau đó, Bộ điều hợp:

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

Sau đó, trong hoạt động / phân đoạn của bạn - bất cứ điều gì bạn có thể triển khai: Clicklistener - hoặc lớp ẩn danh nếu bạn muốn như vậy:

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

Để nhận được mục nào đã được nhấp vào, bạn đối sánh với id chế độ xem ievgetId () == anythingItem.getId ()

Hy vọng cách tiếp cận này sẽ giúp!


1
Cảm ơn, tôi chỉ sử dụng mẫu này để triển khai. Tuy nhiên vấn đề là một cái gì đó khác. Hãy xem tại đây stackoverflow.com/questions/30287411/…
Ashwani K

1
"Cái này" được lấy từ đâu? Không có này trong vòng adapter trừ khi bạn kết hợp lên bối cảnh, và khi tôi làm điều đó vì một lý do nó được đúc View.OnclickListener để nó là tốt
Lion789

2
thisđang tham chiếu đến chính nó, ViewHolder (trong trường hợp này là một lớp tĩnh riêng biệt) - bạn đang đặt trình lắng nghe trên Viewholder có dữ liệu của bạn liên kết với nó onBindViewHolder(), nó không liên quan gì đến ngữ cảnh trong Bộ điều hợp. Tôi không biết chính xác bạn đang gặp phải vấn đề gì, nhưng giải pháp này hoạt động tốt.
Mark Keen

1
@YasithaChinthaka Bạn đã thử đặt thuộc tính này: android:background="?attr/selectableItemBackground"trong xml của mình cho chế độ xem chưa?
Mark Keen

2
Chỉ muốn ghé qua và nói lời cảm ơn, giải pháp này thực sự dễ thực hiện và dễ thực hiện.
CodeGeass

50

Tôi thấy rằng thường:

  • Tôi cần sử dụng nhiều trình nghe vì tôi có một số nút.
  • Tôi muốn logic của mình nằm trong hoạt động chứ không phải bộ điều hợp hoặc trình xem.

Vì vậy, câu trả lời của @ mark-keen hoạt động tốt nhưng có giao diện mang lại sự linh hoạt hơn:

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

Nơi onClickListener được xác định trong bộ điều hợp của bạn:

public MyAdapterListener onClickListener;

public interface MyAdapterListener {

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

Và có thể được đặt thông qua hàm tạo của bạn:

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

    rows = newRows;
    onClickListener = listener;
}

Sau đó, bạn có thể xử lý các sự kiện trong Hoạt động của mình hoặc bất cứ nơi nào RecyclerView của bạn đang được sử dụng:

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

Đây là một cách khác và được xây dựng dựa trên câu trả lời của tôi (một câu trả lời tương tự mà tôi tự sử dụng), tuy nhiên làm cách nào để bạn chuyển onClickListenerđến lớp Viewholder lồng nhau tĩnh? Trừ khi tôi thiếu thứ gì đó, tôi không thể xem cách bạn chuyển nó đến ViewHolder của bạn. Ngoài ra, nếu bạn chỉ sử dụng một phương thức giao diện, bạn có thể sử dụng biểu thức Lambda, biểu thức này sẽ cô đọng mọi thứ.
Mark Keen

public MyAdapterListener onClickListener; là một biến thành viên được xác định trong bộ điều hợp của bạn trong đoạn mã trên và được đặt trong hàm tạo bộ điều hợp của bạn. (Ngoài ra, nhưng không hiển thị ở trên, bạn cũng có thể sử dụng một setter tùy chỉnh như setOnClickListener.)
LordParsley

5
Tôi chỉ hỏi cách bạn đang truy cập một biến thành viên / cá thể trong lớp Bộ điều hợp của bạn từ bên trong một lớp lồng nhau tĩnh (ViewHolder).
Mark Keen

Giống như Mark, tôi không thể lồng vào bên trong bộ điều hợp. xem câu trả lời của tôi về cách tránh phải làm tổ
Tony

@MarkKeen .. chính xác là câu hỏi tôi đã có.
user2695433

6

Tôi muốn một giải pháp không tạo ra bất kỳ đối tượng bổ sung nào (tức là người nghe) sẽ phải được thu thập sau này và không yêu cầu lồng một trình giữ chế độ xem bên trong một lớp bộ điều hợp.

Trong ViewHolderlớp

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

Các điểm cần lưu ý: ClickHandlergiao diện được xác định, nhưng không được khởi tạo ở đây, vì vậy không có giả định trongonClick phương thức rằng nó đã từng được khởi tạo.

Các ClickHandlergiao diện trông như thế này:

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

Trong bộ điều hợp, hãy đặt một phiên bản của 'ClickHandler' trong hàm tạo và ghi đè onBindViewHolder, để khởi tạo `clickHandler 'trên trình giữ chế độ xem:

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

Lưu ý: Tôi biết rằng viewHolder.clickHandler có khả năng được đặt nhiều lần với cùng một giá trị chính xác, nhưng điều này rẻ hơn so với việc kiểm tra null và phân nhánh và không tốn bộ nhớ, chỉ cần thêm một hướng dẫn.

Cuối cùng, khi bạn tạo bộ điều hợp, bạn buộc phải chuyển một ClickHandlerthể hiện cho hàm tạo, như vậy:

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

Lưu ý rằng đây adapterlà một biến thành viên, không phải là một biến cục bộ


Thankyou cho câu trả lời của bạn :) Một điều tôi muốn thêm ở đây không sử dụng adapter.getItem (vị trí) chứ không phải sử dụng yourmodel.get (vị trí)
Khubaib Raza

5

Chỉ muốn thêm một giải pháp khác nếu bạn đã có một trình nghe cảm ứng tái chế và muốn xử lý tất cả các sự kiện chạm trong đó thay vì xử lý riêng sự kiện chạm nút trong giá đỡ xem. Điều quan trọng mà phiên bản thích ứng của lớp này thực hiện là trả lại chế độ xem nút trong lệnh gọi lại onItemClick () khi nó được nhấn, trái ngược với vùng chứa vật phẩm. Sau đó, bạn có thể kiểm tra chế độ xem là một nút và thực hiện một hành động khác. Lưu ý, nhấn lâu vào nút được hiểu là nhấn lâu trên toàn bộ hàng.

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

    }
}

Sau đó, sử dụng nó từ hoạt động / phân mảnh:

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

Bạn cần trả về true bên trong onInterceptTouchEvent()khi bạn xử lý sự kiện nhấp chuột.


1
Xin chào, bạn có thể nói rõ hơn về điều này. Tôi đang sử dụng mã sau để liên kết với nút xóa btnDelete = (ImageButton) itemView.findViewById (R.id.btnDelete); btnDelete.setOnClickListener (new View.OnClickListener () {@Override public void onClick (View view) {remove (getLayoutPosition ());}});
Ashwani K

Giống như onTouchEvent (), giá trị trả về cho biết liệu sự kiện đã được xử lý hay chưa và khi nào thì sự kiện đó không phải là sự kiện được chuyển đến hàng đầy đủ.
Eliyahu Shwartz

0

Trước tiên, bạn có thể kiểm tra xem mình có mục nào tương tự không, nếu bạn nhận được bộ sưu tập có kích thước 0, hãy bắt đầu một truy vấn mới để lưu.

HOẶC LÀ

cách chuyên nghiệp hơn và nhanh hơn. tạo trình kích hoạt đám mây (trước khi lưu)

hãy xem câu trả lời này https://stackoverflow.com/a/35194514/1388852


0

Chỉ cần đặt một phương thức ghi đè có tên getItemId Lấy nó bằng cách nhấp chuột phải> tạo> ghi đè các phương thức> getItemId Đặt phương thức này vào lớp Bộ điều hợp

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.