ViewGroup嵌套RecyclerView設(shè)置點擊事件無響應(yīng)的解決

在使用ViewGroup派生類(LinearLayout、RelativeLayout等)嵌套RecyclerView,給ViewGroup設(shè)置點擊事件后,你會發(fā)現(xiàn)點擊RecyclerView的部分無響應(yīng)點擊事件。
比如下面的示例布局文件:

<?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的區(qū)域?qū)o法響應(yīng)點擊事件
                Toast.makeText(MainActivity.this, "我點擊了LinearLayout", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

從網(wǎng)上尋求解答,發(fā)現(xiàn)大部分的博客提供的解決辦法都是在父布局添加屬性android:descendantFocusability="blocksDescendants"
或者給控件設(shè)置android:focusable="false"之類的,但是經(jīng)過實踐證明這樣做沒有效果。
另外一個辦法是重寫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的區(qū)域?qū)o法響應(yīng)點擊事件
                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;
            }
        });
    }
}

通過此方法可以實現(xiàn)響應(yīng)點擊效果,但是onTouch事件包含了許多操作,直接重寫,可能會導(dǎo)致一系列問題,因此也不推薦使用該方法。
后來在閱讀稀土掘金《LinearLayout包裹RecycleView點擊事件不響應(yīng)》一文中找到了解決辦法,原來,在RecyclerView的onTouchEvent和onInterceptTouchEvent事件中,存在一個名為mLayoutFrozen的布爾值變量,當這個變量的值為true時,RecyclerView就不會攔截點擊事件了,下面是RecyclerView的關(guān)鍵源碼:

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

要設(shè)置mLayoutFrozen的值,可通過setLayoutFrozen方法實現(xiàn):

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

閱讀注釋會發(fā)現(xiàn),在使用setAdapter方法的時候,mLayoutFrozen這個變量的值會默認設(shè)置為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之后,調(diào)用setLayoutFrozen將mLayoutFrozen的值設(shè)置為true,就能夠解決父布局事件不響應(yīng)的問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容