在使用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的布爾值變量,當(dāng)這個變量的值為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這個變量的值會默認(rèn)設(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)的問題。