NestedScrollView 滚动后 onClick 方法无法正常工作
onClick method not working properly after NestedScrollView scrolled
我使用带 CoordinatorLayout 的 NestedScrollView 为工具栏启用滚动动画(来自 app:layout_scrollFlags="scroll|enterAlways")。
NestedScrollView 包含 LinearLayout 作为根子项,我将 2 个 TextView 放入 LinearLayout 以启用 expand/collapse 动画。一个设置为可见
另一个设置为 Gone。并通过 LinearLayout
的 onClick 事件切换可见性
通常情况下,一切都按预期工作,但是当我滚动 NestedScrollView 时
onClick 事件无法正常工作。滚动后我需要双击才能获得 expand/collapse 动画
有没有人和我有同样的问题?请帮助我
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="98dp"
android:paddingLeft="24dp"
android:paddingRight="24dp">
<android.support.v7.widget.AppCompatTextView
android:id="@+id/detail_expense_reason_trim"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="false"
android:textColor="@color/add_new_expense_text_color" />
<android.support.v7.widget.AppCompatTextView
android:id="@+id/detail_expense_reason"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="false"
android:textColor="@color/add_new_expense_text_color"
android:visibility="gone" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/detail_expense_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
@InjectView(R.id.detail_expense_reason)
AppCompatTextView originalReason;
@InjectView(R.id.detail_expense_reason_trim)
AppCompatTextView trimReason;
@InjectView(R.id.detail_expense_container)
LinearLayout expenseContainer;
// 处理事件
public void onClick() {
if (originalReason.getVisibility() == View.VISIBLE) {
originalReason.setVisibility(View.GONE);
trimReason.setVisibility(View.VISIBLE);
} else {
originalReason.setVisibility(View.VISIBLE);
trimReason.setVisibility(View.GONE);
}
}
这是NestedScrollView的一个Bug,Bug的详细信息可以在这里找到:issue。问题是 onInterceptTouchEvent(MotionEvent ev)
中的 mScroller.isFinished()
将不会 return true
在 fling 操作之后(即使 fling 停止)。因此触摸事件被拦截。
此错误已报告一段时间,但仍未修复。所以我已经为这个问题创建了自己的错误修复版本。我实现了自己的 NestedScrollView
,从 NestedScrollView
复制了所有代码并进行了以下修改:
public class NestedScrollView extends FrameLayout implements NestedScrollingParent, NestedScrollingChild {
...
private void initScrollView() {
...
// replace this line:
// mScroller = new ScrollerCompat(getContext(), null);
mScroller = ScrollerCompat.create(getContext(), null);
...
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
...
switch (action & MotionEventCompat.ACTION_MASK) {
...
case MotionEvent.ACTION_DOWN: {
...
// replace this line:
// mIsBeingDragged = !mScroller.isFinished();
mIsBeingDragged = false;
...
}
}
}
}
而这个 NestedScrollView
应该与原来的行为相同。
Bug
在 Google #issues 194398
提及。
只需要使用这个 WorkaroundNestedScrollView.java
class 扩展 NestedScrollView
就像,
WorkaroundNestedScrollView.java
public class WorkaroundNestedScrollView extends NestedScrollView {
public WorkaroundNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// Explicitly call computeScroll() to make the Scroller compute itself
computeScroll();
}
return super.onInterceptTouchEvent(ev);
}
}
在你的布局中这样使用它,
layout.xml
<com.yourpackagename.whatever.WorkaroundNestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
...
...
</com.yourpackagename.whatever.WorkaroundNestedScrollView>
您还可以找到更多 details here。
我也遇到了这个问题
public class NestedScrollView extends FrameLayout implements NestedScrollingParent,
NestedScrollingChild, ScrollingView {
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (actionMasked) {
case MotionEvent.ACTION_DOWN: {
if (getChildCount() == 0) {
return false;
}
//add this line
if (!inChild((int) ev.getX(), (int) ev.getY())) {
return false;
}
if ((mIsBeingDragged = !mScroller.isFinished())) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
}
}
并且我更改了我的xml文件,将paddingTop更改为margin_top,那么我的顶部浮动视图的OnClick事件将不会被NestedScrollView拦截
我在这里打开了另一个问题:https://issuetracker.google.com/issues/68103042 因为它对我们来说仍然是 Oreo 中的一个问题(多个设备,包括模拟器)。
我的修复(改编自 ta..@graymeter.com 在 https://issuetracker.google.com/issues/37051723 的建议)不需要修改 AOSP 代码,因为它使用了反射:
public class MyNestedScrollView extends NestedScrollView {
private static final Logger sLogger = LogFactory.getLogger(MyNestedScrollView.class);
private OverScroller mScroller;
public boolean isFling = false;
public MyNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = getOverScroller();
}
@Override
public void fling(int velocityY) {
super.fling(velocityY);
// here we effectively extend the super class functionality for backwards compatibility and just call invalidateOnAnimation()
if (getChildCount() > 0) {
ViewCompat.postInvalidateOnAnimation(this);
// Initializing isFling to true to track fling action in onScrollChanged() method
isFling = true;
}
}
@Override
protected void onScrollChanged(int l, final int t, final int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (isFling) {
if (Math.abs(t - oldt) <= 3 || t == 0 || t == (getChildAt(0).getMeasuredHeight() - getMeasuredHeight())) {
isFling = false;
// This forces the mFinish variable in scroller to true (as explained the
// mentioned link above) and does the trick
if (mScroller != null) {
mScroller.abortAnimation();
}
}
}
}
private OverScroller getOverScroller() {
Field fs = null;
try {
fs = this.getClass().getSuperclass().getDeclaredField("mScroller");
fs.setAccessible(true);
return (OverScroller) fs.get(this);
} catch (Throwable t) {
return null;
}
}
}
我在此线程上找到了相同问题的解决方案:The item inside RecyclerView can't be clicked right after scrolling
您可以通过将 layout_behavior 添加到您的 AppBarLayout 来修复您的代码。您可以在此处找到代码 Fixed AppBarLayout.Behavior 。只需添加此 class tou 你的项目并修复你的代码:
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
app:layout_behavior="yourPackageName.FixAppBarLayoutBehavior"
android:layout_height="wrap_content">
最佳解决方案:
1) 创建这个 class :
public class FixAppBarLayoutBehavior extends AppBarLayout.Behavior {
public FixAppBarLayoutBehavior() {
super();
}
public FixAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target,
int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, type);
stopNestedScrollIfNeeded(dyUnconsumed, child, target, type);
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View target, int dx, int dy, int[] consumed, int type) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
stopNestedScrollIfNeeded(dy, child, target, type);
}
private void stopNestedScrollIfNeeded(int dy, AppBarLayout child, View target, int type) {
if (type == ViewCompat.TYPE_NON_TOUCH) {
final int currOffset = getTopAndBottomOffset();
if ((dy < 0 && currOffset == 0)
|| (dy > 0 && currOffset == -child.getTotalScrollRange())) {
ViewCompat.stopNestedScroll(target, ViewCompat.TYPE_NON_TOUCH);
}
}
}}
2) 并在 xml 中使用:
<android.support.design.widget.AppBarLayout
...
app:layout_behavior="yourPackageName.FixAppBarLayoutBehavior"
...>
这是支持库中的一个问题。看到这个
https://issuetracker.google.com/u/1/issues/37070828
如果您使用的是 androidX,那么
'androidx.appcompat:appcompat:1.1.0-alpha04'.
虽然这是一个 alpha 版本,但可能会解决这个问题。
我使用带 CoordinatorLayout 的 NestedScrollView 为工具栏启用滚动动画(来自 app:layout_scrollFlags="scroll|enterAlways")。
NestedScrollView 包含 LinearLayout 作为根子项,我将 2 个 TextView 放入 LinearLayout 以启用 expand/collapse 动画。一个设置为可见 另一个设置为 Gone。并通过 LinearLayout
的 onClick 事件切换可见性通常情况下,一切都按预期工作,但是当我滚动 NestedScrollView 时 onClick 事件无法正常工作。滚动后我需要双击才能获得 expand/collapse 动画
有没有人和我有同样的问题?请帮助我
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="98dp"
android:paddingLeft="24dp"
android:paddingRight="24dp">
<android.support.v7.widget.AppCompatTextView
android:id="@+id/detail_expense_reason_trim"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="false"
android:textColor="@color/add_new_expense_text_color" />
<android.support.v7.widget.AppCompatTextView
android:id="@+id/detail_expense_reason"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="false"
android:textColor="@color/add_new_expense_text_color"
android:visibility="gone" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/detail_expense_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
@InjectView(R.id.detail_expense_reason)
AppCompatTextView originalReason;
@InjectView(R.id.detail_expense_reason_trim)
AppCompatTextView trimReason;
@InjectView(R.id.detail_expense_container)
LinearLayout expenseContainer;
// 处理事件
public void onClick() {
if (originalReason.getVisibility() == View.VISIBLE) {
originalReason.setVisibility(View.GONE);
trimReason.setVisibility(View.VISIBLE);
} else {
originalReason.setVisibility(View.VISIBLE);
trimReason.setVisibility(View.GONE);
}
}
这是NestedScrollView的一个Bug,Bug的详细信息可以在这里找到:issue。问题是 onInterceptTouchEvent(MotionEvent ev)
中的 mScroller.isFinished()
将不会 return true
在 fling 操作之后(即使 fling 停止)。因此触摸事件被拦截。
此错误已报告一段时间,但仍未修复。所以我已经为这个问题创建了自己的错误修复版本。我实现了自己的 NestedScrollView
,从 NestedScrollView
复制了所有代码并进行了以下修改:
public class NestedScrollView extends FrameLayout implements NestedScrollingParent, NestedScrollingChild {
...
private void initScrollView() {
...
// replace this line:
// mScroller = new ScrollerCompat(getContext(), null);
mScroller = ScrollerCompat.create(getContext(), null);
...
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
...
switch (action & MotionEventCompat.ACTION_MASK) {
...
case MotionEvent.ACTION_DOWN: {
...
// replace this line:
// mIsBeingDragged = !mScroller.isFinished();
mIsBeingDragged = false;
...
}
}
}
}
而这个 NestedScrollView
应该与原来的行为相同。
Bug
在 Google #issues 194398
提及。
只需要使用这个 WorkaroundNestedScrollView.java
class 扩展 NestedScrollView
就像,
WorkaroundNestedScrollView.java
public class WorkaroundNestedScrollView extends NestedScrollView {
public WorkaroundNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// Explicitly call computeScroll() to make the Scroller compute itself
computeScroll();
}
return super.onInterceptTouchEvent(ev);
}
}
在你的布局中这样使用它,
layout.xml
<com.yourpackagename.whatever.WorkaroundNestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
...
...
</com.yourpackagename.whatever.WorkaroundNestedScrollView>
您还可以找到更多 details here。
我也遇到了这个问题
public class NestedScrollView extends FrameLayout implements NestedScrollingParent,
NestedScrollingChild, ScrollingView {
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (actionMasked) {
case MotionEvent.ACTION_DOWN: {
if (getChildCount() == 0) {
return false;
}
//add this line
if (!inChild((int) ev.getX(), (int) ev.getY())) {
return false;
}
if ((mIsBeingDragged = !mScroller.isFinished())) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
}
}
并且我更改了我的xml文件,将paddingTop更改为margin_top,那么我的顶部浮动视图的OnClick事件将不会被NestedScrollView拦截
我在这里打开了另一个问题:https://issuetracker.google.com/issues/68103042 因为它对我们来说仍然是 Oreo 中的一个问题(多个设备,包括模拟器)。
我的修复(改编自 ta..@graymeter.com 在 https://issuetracker.google.com/issues/37051723 的建议)不需要修改 AOSP 代码,因为它使用了反射:
public class MyNestedScrollView extends NestedScrollView {
private static final Logger sLogger = LogFactory.getLogger(MyNestedScrollView.class);
private OverScroller mScroller;
public boolean isFling = false;
public MyNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = getOverScroller();
}
@Override
public void fling(int velocityY) {
super.fling(velocityY);
// here we effectively extend the super class functionality for backwards compatibility and just call invalidateOnAnimation()
if (getChildCount() > 0) {
ViewCompat.postInvalidateOnAnimation(this);
// Initializing isFling to true to track fling action in onScrollChanged() method
isFling = true;
}
}
@Override
protected void onScrollChanged(int l, final int t, final int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (isFling) {
if (Math.abs(t - oldt) <= 3 || t == 0 || t == (getChildAt(0).getMeasuredHeight() - getMeasuredHeight())) {
isFling = false;
// This forces the mFinish variable in scroller to true (as explained the
// mentioned link above) and does the trick
if (mScroller != null) {
mScroller.abortAnimation();
}
}
}
}
private OverScroller getOverScroller() {
Field fs = null;
try {
fs = this.getClass().getSuperclass().getDeclaredField("mScroller");
fs.setAccessible(true);
return (OverScroller) fs.get(this);
} catch (Throwable t) {
return null;
}
}
}
我在此线程上找到了相同问题的解决方案:The item inside RecyclerView can't be clicked right after scrolling
您可以通过将 layout_behavior 添加到您的 AppBarLayout 来修复您的代码。您可以在此处找到代码 Fixed AppBarLayout.Behavior 。只需添加此 class tou 你的项目并修复你的代码:
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
app:layout_behavior="yourPackageName.FixAppBarLayoutBehavior"
android:layout_height="wrap_content">
最佳解决方案:
1) 创建这个 class :
public class FixAppBarLayoutBehavior extends AppBarLayout.Behavior {
public FixAppBarLayoutBehavior() {
super();
}
public FixAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target,
int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, type);
stopNestedScrollIfNeeded(dyUnconsumed, child, target, type);
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View target, int dx, int dy, int[] consumed, int type) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
stopNestedScrollIfNeeded(dy, child, target, type);
}
private void stopNestedScrollIfNeeded(int dy, AppBarLayout child, View target, int type) {
if (type == ViewCompat.TYPE_NON_TOUCH) {
final int currOffset = getTopAndBottomOffset();
if ((dy < 0 && currOffset == 0)
|| (dy > 0 && currOffset == -child.getTotalScrollRange())) {
ViewCompat.stopNestedScroll(target, ViewCompat.TYPE_NON_TOUCH);
}
}
}}
2) 并在 xml 中使用:
<android.support.design.widget.AppBarLayout
...
app:layout_behavior="yourPackageName.FixAppBarLayoutBehavior"
...>
这是支持库中的一个问题。看到这个 https://issuetracker.google.com/u/1/issues/37070828
如果您使用的是 androidX,那么
'androidx.appcompat:appcompat:1.1.0-alpha04'.
虽然这是一个 alpha 版本,但可能会解决这个问题。