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

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



    

        

    

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 setLayoutFrozen(true) is called,
     * Layout requests will be postponed until setLayoutFrozen(false) 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.
     *
     * 

* setLayoutFrozen(true) does not prevent app from directly calling {@link * LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition( * RecyclerView, State, int)}. *

* {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically * stop frozen. *

* 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.
     * 

* 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设置点击事件无响应的解决)