简体   繁体   English

处理按钮在RecyclerView中的行内单击

[英]Handle Button click inside a row in RecyclerView

I am using following code for handling row clicks. 我正在使用以下代码来处理行点击。 ( 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) {
    }
}

This works however, if I want to have say a delete button on each row. 但是,如果我想在每一行上说一个删除按钮,这是有效的。 I am not sure to how to implement that with this. 我不确定如何实现这一点。

I attached OnClick listener to delete button which works (deletes the row) but it also fires the onclick on full row. 我附加了OnClick监听器来删除工作的按钮(删除行),但它也会触发整行上的onclick。

Can anybody help me in how to avoid full row click if a single button is clicked. 如果单击一个按钮,任何人都可以帮助我避免完全行单击。

Thanks. 谢谢。

this is how I handle multiple onClick events inside a recyclerView: 这就是我在recyclelerView中处理多个onClick事件的方法:

Edit : Updated to include callbacks (as mentioned in other comments). 编辑:已更新以包含回调(如其他注释中所述)。 I have used a WeakReference in the ViewHolder to eliminate a potential memory leak. 我在ViewHolder使用了WeakReference来消除潜在的内存泄漏。

Define interface : 定义界面:

public interface ClickListener {

    void onPositionClicked(int position);

    void onLongClicked(int position);
}

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

Then in your activity/fragment - whatever you can implement : Clicklistener - or anonymous class if you wish like so : 然后在你的活动/片段中 - 你可以实现的任何东西: 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
            }
        });

To get which item was clicked you match the view id ievgetId() == whateverItem.getId() 要获取单击的项目,您将匹配视图ID ievgetId()== whateverItem.getId()

Hope this approach helps! 希望这种方法有帮助!

I find that typically: 我发现通常:

  • I need to use multiple listeners because I have several buttons. 我需要使用多个监听器,因为我有几个按钮。
  • I want my logic to be in the activity and not the adapter or viewholder. 我希望我的逻辑在活动中,而不是适配器或视图。

So @mark-keen's answer works well but having an interface provides more flexibility: 所以@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());
            }
        });
    }
}

Where onClickListener is defined in your adapter: 在适配器中定义onClickListener的位置:

public MyAdapterListener onClickListener;

public interface MyAdapterListener {

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

And probably set through your constructor: 并且可能通过构造函数设置:

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

    rows = newRows;
    onClickListener = listener;
}

Then you can handle the events in your Activity or wherever your RecyclerView is being used: 然后,您可以处理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);

I wanted a solution that did not create any extra objects (ie listeners) that would have to be garbage collected later, and did not require nesting a view holder inside an adapter class. 我想要一个没有创建任何额外对象(即监听器)的解决方案,这些对象必须在以后进行垃圾收集,并且不需要在适配器类中嵌套视图持有者。

In the ViewHolder class 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 to note: the ClickHandler interface is defined, but not initialized here, so there is no assumption in the onClick method that it was ever initialized. 注意事项: ClickHandler接口已定义,但未在此处初始化,因此onClick方法中没有假设它已初始化。

The ClickHandler interface looks like this: ClickHandler界面如下所示:

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

In the adapter, set an instance of 'ClickHandler' in the constructor, and override onBindViewHolder , to initialize `clickHandler' on the view holder: 在适配器中,在构造函数中设置一个'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;
    }

Note: I know that viewHolder.clickHandler is potentially getting set multiple times with the exact same value, but this is cheaper than checking for null and branching, and there is no memory cost, just an extra instruction. 注意:我知道viewHolder.clickHandler可能会使用完全相同的值多次设置,但这比检查null和分支更便宜,而且没有内存开销,只需要额外的指令。

Finally, when you create the adapter, you are forced to pass a ClickHandler instance to the constructor, as so: 最后,在创建适配器时,您必须将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
    }
});

Note that adapter is a member variable here, not a local variable 请注意, adapter在此处是成员变量,而不是局部变量

Just wanted to add another solution if you already have a recycler touch listener and want to handle all of the touch events in it rather than dealing with the button touch event separately in the view holder. 如果您已经拥有一个回收器触摸侦听器并想要处理其中的所有触摸事件而不是在视图持有者中单独处理按钮触摸事件,那么只是想添加另一个解决方案。 The key thing this adapted version of the class does is return the button view in the onItemClick() callback when it's tapped, as opposed to the item container. 这个改编版本的关键是在点击时返回onItemClick()回调中的按钮视图,而不是项容器。 You can then test for the view being a button, and carry out a different action. 然后,您可以测试作为按钮的视图,并执行不同的操作。 Note, long tapping on the button is interpreted as a long tap on the whole row still. 请注意,长按此按钮会被解释为长按整个行。

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

    }
}

Then using it from activity / fragment: 然后从activity / 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) {
            }
        });
    }

处理单击事件时,需要在onInterceptTouchEvent()内返回true。

You can check if you have any similar entries first, if you get a collection with size 0, start a new query to save. 您可以先检查是否有任何类似的条目,如果您获得大小为0的集合,则启动要保存的新查询。

OR 要么

more professional and faster way. 更专业,更快捷的方式。 create a cloud trigger (before save) 创建云触发器(保存前)

check out this answer https://stackoverflow.com/a/35194514/1388852 看看这个答案https://stackoverflow.com/a/35194514/1388852

只需放置一个名为getItemId的覆盖方法,右键单击> generate> override methods> getItemId将此方法放入Adapter类中

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM