嵌套的RecyclerView onClickListener无法正常工作

时间:2016-03-21 14:51:34

标签: android nested android-recyclerview clicklistener

我已经实现了一个嵌套的ReyclerView(水平到垂直),我不想为整个行添加一个点击监听器,它包含在CardView元素中。

我遇到的问题是内部RecyclerView捕获所有触摸事件,而根CardView不响应onClick事件。

我也尝试让CardView拦截触摸事件,但是使用这种方法,涟漪效应(实际上是任何反馈)都无效。

有人可以推荐一个解决方案,以便在嵌套RecyclerView时如何在一行上实现点击监听器吗?

谢谢。

- LE -

这是当前的实施:

片段布局

<android.support.v7.widget.RecyclerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/list"
        android:name=".NestedRecyclerViewsFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="LinearLayoutManager"
        tools:context=".NestedRecyclerViewsFragment"
        tools:listitem="@layout/fragment_nested_recyclerview_item"
        />

碎片onCreateView()实现

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_nested_recyclerview_list, container, false);

        // Set the adapter
        if (view instanceof RecyclerView) {
            Context context = view.getContext();
            RecyclerView recyclerView = (RecyclerView) view;
            recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
            recyclerView.setHasFixedSize(true); // Improves performance - as we know the size doesn't change

            //Initialize and set the adapter
            mAdapter = new RootAdapter(context, mListener);
            recyclerView.setAdapter(mAdapter);

            final GestureDetector mGestureDetector =
                    new GestureDetector(view.getContext(), new GestureDetector.SimpleOnGestureListener() {

                        @Override
                        public boolean onSingleTapUp(MotionEvent e) {
                            return true;
                        }

                    });
            recyclerView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
                //TODO: intercept simple gestures like onClick and/or onLongClick

                @Override
                public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                    View child = rv.findChildViewUnder(e.getX(), e.getY());

                    if (child != null && mGestureDetector.onTouchEvent(e)) {
                        //TODO: handle the intercept??
                        child.callOnClick();
                        return true;
                    }

                    return super.onInterceptTouchEvent(rv, e);
                }
            });
            recyclerView.setNestedScrollingEnabled(true);
        }
        return view;
}

** Root Adapter layout **

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="@dimen/row_height"
        android:layout_gravity="center_horizontal"
        android:layout_margin="@dimen/none"
        android:padding="@dimen/none"
        app:cardCornerRadius="@dimen/none"
        tools:context=".NestedRecyclerViewActivity"
        <!-- Simple selector for API < 21 and ripple effect for APi >= 21 -->
        android:foreground="@drawable/selector_default"
        android:clickable="true"
        android:focusable="true"
        >

    <!-- Horizontal image gallery inside row item -->
    <android.support.v7.widget.RecyclerView
            android:id="@+id/child_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />

    ...

</android.support.v7.widget.CardView>

Root Adapter onBindView()实现:

...

holder.childRecyclerView.setLayoutManager(new LinearLayoutManager(holder.mView.getContext(),
                                                         LinearLayoutManager.HORIZONTAL, false));
holder.childRecyclerView.setHasFixedSize(true); // We know the image don't change size
holder.childRecyclerView.setAdapter(new ChildAdapter(items));

...

Root Adapter ViewHolder实现:

public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        private ItemClickListener mClickListener;

        /**
         * The whole view - useful if you need to place some touch listener on the entire row.
         */
        public final View mView;
        /**
         * The model associated with this view. - will be updated on bind method
         */
        public Object mItem;

        @Bind(R.id.child_recycler_view)
        RecyclerView childRecyclerView;

        ...

        public ViewHolder(View view) {
            super(view);
            ButterKnife.bind(this, view);
            mView = view;

            mView.setOnClickListener(this);
            // Set the click listener bound to the fragment or activity
            mClickListener = RootAdapter.this;
        }

        @Override
        public void onClick(View v) {
            if (null != mClickListener) {
                mClickListener.onClick(v, getAdapterPosition());
            }
        }
}

内部的RecyclerView(子代)是一个非常简单的标准实现,没有设置任何监听器。

虽然这个解决方案有效但我得到了click事件并且内部的RecyclerView滚动有效,但我遇到了没有显示点击反馈的问题。

LE:解决方案

自从我发布此问题后,我的要求发生了一些变化,不得不用PagerAdapter替换内部的RecyclerView,但我实现的解决方案也应该使用嵌套的RecyclerView。

基本上我使用自定义的ItemClickSupport类,它会在拦截后立即将点击​​事件传递回父级:

/**
 * Utility class which adds the ability to add Click Support for RecyclerViews without the need to implement click
 * listeners into the adapter or in the ViewHolder's implementation.
 * <p>
 * Use it by simply binding an click listener to the desired RecyclerView.
 * <pre><code>
 * ItemClickSupport.addTo(mRecyclerView).setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
 *      {@literal@}Override
 *      public void onItemClicked(RecyclerView recyclerView, int position, View v) {
 *          // Handle the clicked item
 *      }
 * });
 * </code></pre>
 * </p>
 * Based on <a href="http://www.littlerobots.nl/blog/Handle-Android-RecyclerView-Clicks/">Handle-Android-RecyclerView
 * -Clicks</a>, <br/><b>Hugo Visser</b>. Which is very similar with the implementation from <a
 * href="https://github.com/lucasr/twoway-view">TwoWay-View</a>.
 * <p/>
 * Created by ionut on 22.03.2016.
 */
public class ItemClickSupport {

    private final RecyclerView mRecyclerView;
    private OnItemClickListener mOnItemClickListener;
    private OnItemLongClickListener mOnItemLongClickListener;
    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (mOnItemClickListener != null) {
                RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
                mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);
            }
        }
    };
    private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            if (mOnItemLongClickListener != null) {
                RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
                return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);
            }
            return false;
        }
    };
    private RecyclerView.OnChildAttachStateChangeListener mAttachListener =
            new RecyclerView.OnChildAttachStateChangeListener() {
                @Override
                public void onChildViewAttachedToWindow(View view) {
                    if (mOnItemClickListener != null) {
                        view.setOnClickListener(mOnClickListener);
                    }
                    if (mOnItemLongClickListener != null) {
                        view.setOnLongClickListener(mOnLongClickListener);
                    }
                }

                @Override
                public void onChildViewDetachedFromWindow(View view) {

                }
            };

    private ItemClickSupport(RecyclerView recyclerView) {
        mRecyclerView = recyclerView;
        mRecyclerView.setTag(R.id.item_click_support, this);
        mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
    }

    public static ItemClickSupport addTo(RecyclerView view) {
        ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
        if (support == null) {
            support = new ItemClickSupport(view);
        }
        return support;
    }

    public static ItemClickSupport removeFrom(RecyclerView view) {
        ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
        if (support != null) {
            support.detach(view);
        }
        return support;
    }

    public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) {
        mOnItemClickListener = listener;
        return this;
    }

    public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
        mOnItemLongClickListener = listener;
        return this;
    }

    private void detach(RecyclerView view) {
        view.removeOnChildAttachStateChangeListener(mAttachListener);
        view.setTag(R.id.item_click_support, null);
    }

    public interface OnItemClickListener {

        void onItemClicked(RecyclerView recyclerView, int position, View v);

        void onItemClicked(int position);
    }

    public interface OnItemLongClickListener {

        boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);
    }

    public static class SimpleOnItemClickListener implements OnItemClickListener {

        @Override
        public void onItemClicked(int position) {

        }

        @Override
        public void onItemClicked(RecyclerView recyclerView, int position, View v) {

        }
    }
}

创建根适配器时,我为它设置项目单击侦听器,如下所示:

ItemClickSupport.addTo(mRecyclerView).setOnItemClickListener(this);

我还通过setter方法发送内部适配器的监听器,如下所示:

rootAdapter.setOnItemClickListener(this);

rootAdapter是根适配器,this是我所在的当前片段,它实现了ItemClickSupportListener

在根适配器中,当绑定项目并创建内部适配器时,我将项目单击支持侦听器传递给内部适配器,如下所示:

innerAdapter.setOnItemClickListener(new ItemClickSupport.SimpleOnItemClickListener() {
    @Override
    public void onItemClicked(int position) {
        // Here we pass the click to the parent provided click listener.
        // We modify the position with the one of the ViewHolder so that we don't get the
        // position of the horizontal RecyclerView adapter - we are interested on the
        // vertical item actually.
        if (null != mItemClickListener) {
            mItemClickListener.onItemClicked(holder.getAdapterPosition());
        }
    }
});

mItemClickListener实际上是片段通过setter方法设置的监听器,如上所述。

在内部适配器中,在创建视图时,我在布局的根目录上设置了一个单击侦听器,我将其充气并将该事件传递回我的自定义单击侦听器:

// Detect the click events and pass them to any listeners
itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (null != mOnItemClickListener) {
            mOnItemClickListener.onItemClicked(position);
        }
    }
});

mOnItemClickListener实际上是上面描述的适配器传递的项目点击支持。

另一个重要的事情是使用FrameLayout作为适配器项视图的内容,如下所示:

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"
    android:addStatesFromChildren="true"
    android:foreground="@drawable/selector_default"
    >

这里重要的是foreground将使用选择器和标志android:addStatesFromChildren。这是为根适配器项设置的。

内部适配器项目也应使用FrameLayout作为内容视图,以便我们也可以使用前景属性设置它的选择器:

<FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:foreground="@drawable/selector_default"
        >

这是必需的,因为当内部适配器项目将捕获click事件时,将激活选择器,否则父级应对click事件做出反应并激活它的选择器。

1 个答案:

答案 0 :(得分:0)

尝试将click侦听器设置为适配器中的视图

public class NormalItem extends RecyclerView.ViewHolder {

    CardView cardView;
    TextView textView;


    public NormalItem(View itemView) {
        super(itemView);


        textView = (TextView) itemView.findViewById(R.id.textView);
        cardView = (CardView) itemView.findViewById(R.id.cardview);

        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            // to perform click on entire row do like this. 
            }
        });
       textView.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(View v){
        // perform clicks to views inside the items. 
    }
}