【问题标题】:Handle Button click inside a row in RecyclerView在 RecyclerView 中的一行内单击处理按钮
【发布时间】:2015-05-17 06:34:59
【问题描述】:

我正在使用以下代码来处理行点击。 (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) {
    }
}

但是,如果我想在每一行上说一个删除按钮,这可行。我不确定如何用这个来实现。

我将 OnClick 侦听器附加到删除按钮,该按钮有效(删除行),但它也会触发整行的 onclick。

如果单击单个按钮,任何人都可以帮助我避免全行单击。

谢谢。

【问题讨论】:

    标签: android android-recyclerview


    【解决方案1】:

    这就是我在 recyclerView 中处理多个 onClick 事件的方式:

    编辑: 更新为包含回调(如其他 cmets 中所述)。我在ViewHolder 中使用了WeakReference 来消除潜在的内存泄漏。

    定义接口:

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

    要获取点击了哪个项目,您需要匹配视图 ID,即 v.getId() == whateverItem.getId()

    希望这种方法有所帮助!

    【讨论】:

    • 谢谢,我只是在我的实现中使用这个模式。然而问题是另一回事。看看这里stackoverflow.com/questions/30287411/…
    • “this”是从哪里拉出来的?除非您匹配上下文,否则适配器中没有 this,当我出于某种原因这样做时,它也会向它投射 View.OnclickListener
    • this 正在引用自身,即 ViewHolder(在这种情况下是一个单独的静态类)-您正在将侦听器设置在 Viewholder 上,该 Viewholder 将您的数据绑定到 onBindViewHolder(),它与适配器中的上下文无关。我不知道你到底遇到了什么问题,但这个解决方案很好用。
    • @YasithaChinthaka 您是否尝试在您的 xml 中为视图设置此属性:android:background="?attr/selectableItemBackground"
    • 只是想顺便说声谢谢,这个解决方案真的很容易遵循和实施。
    【解决方案2】:

    我发现通常:

    • 我需要使用多个侦听器,因为我有多个按钮。
    • 我希望我的逻辑在活动中,而不是在适配器或视图中。

    所以@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。此外,如果您只使用一种接口方法,您可以使用 Lambda 表达式,它将所有内容压缩。
    • public MyAdapterListener onClickListener;是上面代码中适配器中定义的成员变量,并在适配器构造函数中设置。 (或者,但上面未显示,您也可以使用自定义设置器,如 setOnClickListener。)
    • 我只是问您如何从静态嵌套类 (ViewHolder) 中访问适配器类中的成员/实例变量。
    • 像马克一样,我无法嵌套在适配器内。请参阅我关于如何避免嵌套的答案
    • @MarkKeen ..跟我的问题一模一样。
    【解决方案3】:

    我想要一个解决方案,它不会创建任何额外对象(即侦听器),这些对象(即侦听器)稍后必须被垃圾收集,并且不需要在适配器类中嵌套视图持有者。

    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 可能会被多次设置为完全相同的值,但这比检查 null 和分支更便宜,而且没有内存成本,只是一条额外的指令。

    最后,当你创建适配器时,你不得不将ClickHandlerinstance 传递给构造函数,如下所示:

    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)
    【解决方案4】:

    如果您已经有一个回收器触摸侦听器并且想要处理其中的所有触摸事件而不是在视图持有者中单独处理按钮触摸事件,则只想添加另一个解决方案。这个类的改编版本所做的关键是在点击时返回 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) {
                }
            });
        }
    

    【讨论】:

      【解决方案5】:

      你需要在处理点击事件时在onInterceptTouchEvent()内部返回true。

      【讨论】:

      • 嗨,你能详细说明一下吗?我正在使用以下代码绑定删除按钮 btnDelete = (ImageButton) itemView.findViewById(R.id.btnDelete); btnDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { remove(getLayoutPosition()); } });
      • 和onTouchEvent()一样,返回值表示事件是否已被处理,当未处理时,事件传递到整行。
      【解决方案6】:

      你可以先检查是否有类似的条目,如果你得到一个大小为0的集合,开始一个新的查询保存。

      更专业、更快捷的方式。 创建云触发器(保存前)

      看看这个答案 https://stackoverflow.com/a/35194514/1388852

      【讨论】:

        【解决方案7】:

        只需放置一个名为 getItemId 的覆盖方法 通过右键单击>生成>覆盖方法>getItemId获取它 把这个方法放到Adapter类中

        【讨论】:

          猜你喜欢
          • 2019-12-26
          • 2017-06-18
          • 1970-01-01
          • 1970-01-01
          • 2017-07-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-12-29
          相关资源
          最近更新 更多