美文网首页
ViewGroup嵌套RecyclerView设置点击事件无响应

ViewGroup嵌套RecyclerView设置点击事件无响应

作者: 我啊翔1314 | 来源:发表于2020-01-09 15:20 被阅读0次

在使用ViewGroup派生类(LinearLayout、RelativeLayout等)嵌套RecyclerView,给ViewGroup设置点击事件后,你会发现点击RecyclerView的部分无响应点击事件。
比如下面的示例布局文件:

<?xml version="1.0" encoding="utf-8"?>
<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">
    <LinearLayout
        android:id="@+id/ll"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </LinearLayout>
</FrameLayout>

MainActivity.java文件代码如下:

package com.axen.module.activity;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Toast;

import com.axen.module.R;

public class MainActivity extends AppCompatActivity {

    private LinearLayout ll;
    private RecyclerView rv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ll = findViewById(R.id.ll);
        rv = findViewById(R.id.rv);
        ll.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 点击在RecyclerView的区域将无法响应点击事件
                Toast.makeText(MainActivity.this, "我点击了LinearLayout", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

从网上寻求解答,发现大部分的博客提供的解决办法都是在父布局添加属性android:descendantFocusability="blocksDescendants"
或者给控件设置android:focusable="false"之类的,但是经过实践证明这样做没有效果。
另外一个办法是重写RecyclerView的onTouchListener事件:

package com.axen.module.activity;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Toast;

import com.axen.module.R;

public class MainActivity extends AppCompatActivity {

    private LinearLayout ll;
    private RecyclerView rv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ll = findViewById(R.id.ll);
        rv = findViewById(R.id.rv);
        ll.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 点击在RecyclerView的区域将无法响应点击事件
                Toast.makeText(MainActivity.this, "我点击了LinearLayout", Toast.LENGTH_SHORT).show();
            }
        });
        rv.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { 
                    Toast.makeText(MainActivity.this, "我点击了LinearLayout", Toast.LENGTH_SHORT).show();
                }
                return false;
            }
        });
    }
}

通过此方法可以实现响应点击效果,但是onTouch事件包含了许多操作,直接重写,可能会导致一系列问题,因此也不推荐使用该方法。
后来在阅读稀土掘金《LinearLayout包裹RecycleView点击事件不响应》一文中找到了解决办法,原来,在RecyclerView的onTouchEvent和onInterceptTouchEvent事件中,存在一个名为mLayoutFrozen的布尔值变量,当这个变量的值为true时,RecyclerView就不会拦截点击事件了,下面是RecyclerView的关键源码:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
    ....
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        if (mLayoutFrozen || mIgnoreMotionEventTillDown) {
            return false;
        }
        ...
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        if (mLayoutFrozen) {
            // When layout is frozen,  RV does not intercept the motion event.
            // A child view e.g. a button may still get the click.
            return false;
        }
      ...
    }
}

要设置mLayoutFrozen的值,可通过setLayoutFrozen方法实现:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
    ....
    /**
     * Enable or disable layout and scroll.  After <code>setLayoutFrozen(true)</code> is called,
     * Layout requests will be postponed until <code>setLayoutFrozen(false)</code> is called;
     * child views are not updated when RecyclerView is frozen, {@link #smoothScrollBy(int, int)},
     * {@link #scrollBy(int, int)}, {@link #scrollToPosition(int)} and
     * {@link #smoothScrollToPosition(int)} are dropped; TouchEvents and GenericMotionEvents are
     * dropped; {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} will not be
     * called.
     *
     * <p>
     * <code>setLayoutFrozen(true)</code> does not prevent app from directly calling {@link
     * LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition(
     * RecyclerView, State, int)}.
     * <p>
     * {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically
     * stop frozen.
     * <p>
     * Note: Running ItemAnimator is not stopped automatically,  it's caller's
     * responsibility to call ItemAnimator.end().
     *
     * @param frozen   true to freeze layout and scroll, false to re-enable.
     */
    public void setLayoutFrozen(boolean frozen) {
        if (frozen != mLayoutFrozen) {
            assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll");
            if (!frozen) {
                mLayoutFrozen = false;
                if (mLayoutWasDefered && mLayout != null && mAdapter != null) {
                    requestLayout();
                }
                mLayoutWasDefered = false;
            } else {
                final long now = SystemClock.uptimeMillis();
                MotionEvent cancelEvent = MotionEvent.obtain(now, now,
                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                onTouchEvent(cancelEvent);
                mLayoutFrozen = true;
                mIgnoreMotionEventTillDown = true;
                stopScroll();
            }
        }
    }
}

阅读注释会发现,在使用setAdapter方法的时候,mLayoutFrozen这个变量的值会默认设置为false:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
    ....
    /**
     * Set a new adapter to provide child views on demand.
     * <p>
     * When adapter is changed, all existing views are recycled back to the pool. If the pool has
     * only one adapter, it will be cleared.
     *
     * @param adapter The new adapter to set, or null to set no adapter.
     * @see #swapAdapter(Adapter, boolean)
     */
    public void setAdapter(Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        processDataSetCompletelyChanged(false);
        requestLayout();
    }
}

因此在setAdapter之后,调用setLayoutFrozen将mLayoutFrozen的值设置为true,就能够解决父布局事件不响应的问题。

相关文章

网友评论

      本文标题:ViewGroup嵌套RecyclerView设置点击事件无响应

      本文链接:https://www.haomeiwen.com/subject/wcniyctx.html