回收视图 + AppBarLayout

我正在使用新的坐标布局与 AppBarLayout 和 CollapsingToolbarLayout。在 AppBarLayout 下面,我有一个回收视图和一个内容列表。

我已经验证了,当我向上和向下滚动列表时,抛出滚动可以在回收视图上工作。但是,我也希望 AppBarLayout 在展开过程中能够平滑地滚动。

当向上滚动以展开 CollaspingToolbarLayout 时,一旦手指离开屏幕,滚动就会立即停止。如果您快速向上滚动,有时 CollapsingToolbarLayout 也会重新折叠。与使用 NestedScrollView 时相比,回收视图的此行为的功能似乎有很大不同。

我试过在回收视图上设置不同的卷轴属性,但是我还没有能够解决这个问题。

下面的视频显示了一些滚动问题。 Https://youtu.be/xmlkojostam

下面的示例显示了回收视图(CheeseDetailActivity)的问题。 Https://github.com/tylerjroach/cheesesquare

下面是使用 Chris Bane 的 NestedScrollView 的原始示例。 Https://github.com/chrisbanes/cheesesquare

55586 次浏览

我在 AppBarLayout 中添加了一个1dp 高度的视图,然后它工作得更好。这是我的布局。

  <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
tools:context="com.spof.spof.app.UserBeachesActivity">


<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">


<android.support.v7.widget.Toolbar
android:id="@+id/user_beaches_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_alignParentTop="true"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/WhiteTextToolBar"
app:layout_scrollFlags="scroll|enterAlways" />


<View
android:layout_width="match_parent"
android:layout_height="1dp" />
</android.support.design.widget.AppBarLayout>




<android.support.v7.widget.RecyclerView
android:id="@+id/user_beaches_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />

这是我的布局和卷轴,它的工作,因为它应该。

<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:id="@+id/container">


<android.support.design.widget.AppBarLayout
android:id="@+id/appbarLayout"
android:layout_height="192dp"
android:layout_width="match_parent">


<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/ctlLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="?attr/colorPrimary"
app:layout_collapseMode="parallax">


<android.support.v7.widget.Toolbar
android:id="@+id/appbar"
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
app:layout_scrollFlags="scroll|enterAlways"
app:layout_collapseMode="pin"/>


</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>


<android.support.v7.widget.RecyclerView
android:id="@+id/catalogueRV"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>


</android.support.design.widget.CoordinatorLayout>

看来 v23的更新还没有修复它。

我已经找到了一种方法来解决这个问题。诀窍在于,如果 ScrollingView 的顶级子节点接近 Adapter 中数据的开头,则重新使用 fling 事件。

public final class FlingBehavior extends AppBarLayout.Behavior {


public FlingBehavior() {
}


public FlingBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}


@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
if (target instanceof ScrollingView) {
final ScrollingView scrollingView = (ScrollingView) target;
consumed = velocityY > 0 || scrollingView.computeVerticalScrollOffset() > 0;
}
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
}

在你的布局中这样使用它:

 <android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="your.package.FlingBehavior">
<!--your views here-->
</android.support.design.widget.AppBarLayout>

编辑: Fling 事件重复消耗现在是基于 verticalScrollOffset而不是从 RecyclerView顶部的项目数量。

编辑2: 将目标检查为 ScrollingView接口实例而不是 RecyclerViewRecyclerViewNestedScrollingView都实现了它。

Kirill Boyarshinov的答案几乎是正确的。

主要的问题在于回收视图有时会给出不正确的抛出方向,所以如果你在他的回答中添加以下代码,它就能正常工作:

public final class FlingBehavior extends AppBarLayout.Behavior {
private static final int TOP_CHILD_FLING_THRESHOLD = 3;
private boolean isPositive;


public FlingBehavior() {
}


public FlingBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}


@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
if (velocityY > 0 && !isPositive || velocityY < 0 && isPositive) {
velocityY = velocityY * -1;
}
if (target instanceof RecyclerView && velocityY < 0) {
final RecyclerView recyclerView = (RecyclerView) target;
final View firstChild = recyclerView.getChildAt(0);
final int childAdapterPosition = recyclerView.getChildAdapterPosition(firstChild);
consumed = childAdapterPosition > TOP_CHILD_FLING_THRESHOLD;
}
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}


@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
isPositive = dy > 0;
}
}

我希望这能有所帮助。

这是一个回收视图错误。它应该是固定在 v23.1.0。

https://code.google.com/p/android/issues/detail?id=177729

enter image description here

这是 Google 支持设计 AppBarLayout 的平滑版本。如果您正在使用 AppBarLayout,那么您将知道它存在一个抛弃问题。

compile "me.henrytao:smooth-app-bar-layout:<latest-version>"

看图书馆在这里. . https://github.com/henrytao-me/smooth-app-bar-layout

我通过将 OnScrollingListener 应用于回收视图找到了解决方案。现在效果很好。问题在于,回收视图提供了错误的消耗值,并且该行为不知道何时将回收视图滚动到顶部。

package com.singmak.uitechniques.util.coordinatorlayout;


import android.content.Context;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;


import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;


/**
* Created by maksing on 26/3/2016.
*/
public final class RecyclerViewAppBarBehavior extends AppBarLayout.Behavior {


private Map<RecyclerView, RecyclerViewScrollListener> scrollListenerMap = new HashMap<>(); //keep scroll listener map, the custom scroll listener also keep the current scroll Y position.


public RecyclerViewAppBarBehavior() {
}


public RecyclerViewAppBarBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}


/**
*
* @param coordinatorLayout
* @param child The child that attached the behavior (AppBarLayout)
* @param target The scrolling target e.g. a recyclerView or NestedScrollView
* @param velocityX
* @param velocityY
* @param consumed The fling should be consumed by the scrolling target or not
* @return
*/
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
if (target instanceof RecyclerView) {
final RecyclerView recyclerView = (RecyclerView) target;
if (scrollListenerMap.get(recyclerView) == null) {
RecyclerViewScrollListener recyclerViewScrollListener = new RecyclerViewScrollListener(coordinatorLayout, child, this);
scrollListenerMap.put(recyclerView, recyclerViewScrollListener);
recyclerView.addOnScrollListener(recyclerViewScrollListener);
}
scrollListenerMap.get(recyclerView).setVelocity(velocityY);
consumed = scrollListenerMap.get(recyclerView).getScrolledY() > 0; //recyclerView only consume the fling when it's not scrolled to the top
}
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}


private static class RecyclerViewScrollListener extends RecyclerView.OnScrollListener {
private int scrolledY;
private boolean dragging;
private float velocity;
private WeakReference<CoordinatorLayout> coordinatorLayoutRef;
private WeakReference<AppBarLayout> childRef;
private WeakReference<RecyclerViewAppBarBehavior> behaviorWeakReference;


public RecyclerViewScrollListener(CoordinatorLayout coordinatorLayout, AppBarLayout child, RecyclerViewAppBarBehavior barBehavior) {
coordinatorLayoutRef = new WeakReference<CoordinatorLayout>(coordinatorLayout);
childRef = new WeakReference<AppBarLayout>(child);
behaviorWeakReference = new WeakReference<RecyclerViewAppBarBehavior>(barBehavior);
}


@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
dragging = newState == RecyclerView.SCROLL_STATE_DRAGGING;
}


public void setVelocity(float velocity) {
this.velocity = velocity;
}


public int getScrolledY() {
return scrolledY;
}


@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
scrolledY += dy;


if (scrolledY <= 0 && !dragging && childRef.get() != null && coordinatorLayoutRef.get() != null && behaviorWeakReference.get() != null) {
//manually trigger the fling when it's scrolled at the top
behaviorWeakReference.get().onNestedFling(coordinatorLayoutRef.get(), childRef.get(), recyclerView, 0, velocity, false);
}
}
}
}

我的解决方案到目前为止,基于 麦星Manolo Garcia的答案。

这并不完美。现在我不知道如何重新计算有效速度来避免一个奇怪的效果: 应用程序条可以比滚动速度扩展得更快。 但是无法到达具有扩展应用程序栏和滚动回收器视图的状态。

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;


import java.lang.ref.WeakReference;


public class FlingAppBarLayoutBehavior
extends AppBarLayout.Behavior {


// The minimum I have seen for a dy, after the recycler view stopped.
private static final int MINIMUM_DELTA_Y = -4;


@Nullable
RecyclerViewScrollListener mScrollListener;


private boolean isPositive;


public FlingAppBarLayoutBehavior() {
}


public FlingAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}


public boolean callSuperOnNestedFling(
CoordinatorLayout coordinatorLayout,
AppBarLayout child,
View target,
float velocityX,
float velocityY,
boolean consumed) {
return super.onNestedFling(
coordinatorLayout,
child,
target,
velocityX,
velocityY,
consumed
);
}


@Override
public boolean onNestedFling(
CoordinatorLayout coordinatorLayout,
AppBarLayout child,
View target,
float velocityX,
float velocityY,
boolean consumed) {


if (velocityY > 0 && !isPositive || velocityY < 0 && isPositive) {
velocityY = velocityY * -1;
}


if (target instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) target;


if (mScrollListener == null) {
mScrollListener = new RecyclerViewScrollListener(
coordinatorLayout,
child,
this
);
recyclerView.addOnScrollListener(mScrollListener);
}


mScrollListener.setVelocity(velocityY);
}


return super.onNestedFling(
coordinatorLayout,
child,
target,
velocityX,
velocityY,
consumed
);
}


@Override
public void onNestedPreScroll(
CoordinatorLayout coordinatorLayout,
AppBarLayout child,
View target,
int dx,
int dy,
int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
isPositive = dy > 0;
}


private static class RecyclerViewScrollListener
extends RecyclerView.OnScrollListener {


@NonNull
private final WeakReference<AppBarLayout> mAppBarLayoutWeakReference;


@NonNull
private final WeakReference<FlingAppBarLayoutBehavior> mBehaviorWeakReference;


@NonNull
private final WeakReference<CoordinatorLayout> mCoordinatorLayoutWeakReference;


private int mDy;


private float mVelocity;


public RecyclerViewScrollListener(
@NonNull CoordinatorLayout coordinatorLayout,
@NonNull AppBarLayout child,
@NonNull FlingAppBarLayoutBehavior barBehavior) {
mCoordinatorLayoutWeakReference = new WeakReference<>(coordinatorLayout);
mAppBarLayoutWeakReference = new WeakReference<>(child);
mBehaviorWeakReference = new WeakReference<>(barBehavior);
}


@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (mDy < MINIMUM_DELTA_Y
&& mAppBarLayoutWeakReference.get() != null
&& mCoordinatorLayoutWeakReference.get() != null
&& mBehaviorWeakReference.get() != null) {


// manually trigger the fling when it's scrolled at the top
mBehaviorWeakReference.get()
.callSuperOnNestedFling(
mCoordinatorLayoutWeakReference.get(),
mAppBarLayoutWeakReference.get(),
recyclerView,
0,
mVelocity, // TODO find a way to recalculate a correct velocity.
false
);


}
}
}


@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
mDy = dy;
}


public void setVelocity(float velocity) {
mVelocity = velocity;
}


}


}

在我的情况下,我得到的问题,抛出的 RecyclerView不会顺利滚动它,使它得到卡住。

这是因为,出于某种原因,我忘了我把我的 ABC0放在 NestedScrollView里了

这是个愚蠢的错误,但我花了一段时间才想明白..。

这里已经有一些相当流行的解决方案,但是在使用它们之后,我想出了一个相当简单的解决方案,对我来说效果很好。我的解决方案还确保只有当可滚动内容到达顶部时,才能扩展 AppBarLayout,这比其他解决方案更有优势。

private int mScrolled;
private int mPreviousDy;
private AppBarLayout mAppBar;


myRecyclerView.addOnScrollListener(new OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mScrolled += dy;
// scrolled to the top with a little more velocity than a slow scroll e.g. flick/fling.
// Adjust 10 (vertical change of event) as you feel fit for you requirement
if(mScrolled == 0 && dy < -10 && mPrevDy < 0) {
mAppBar.setExpanded(true, true);
}
mPreviousDy = dy;
});

这个被接受的答案对我不起作用,因为我在一个 SwipeRefreshLayout和一个 ViewPager中都有 RecyclerView。这是一个改进版本,它在层次结构中寻找 RecyclerView,并且应该适用于任何布局:

public final class FlingBehavior extends AppBarLayout.Behavior {
private static final int TOP_CHILD_FLING_THRESHOLD = 3;
private boolean isPositive;


public FlingBehavior() {
}


public FlingBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}


@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
if (velocityY > 0 && !isPositive || velocityY < 0 && isPositive) {
velocityY = velocityY * -1;
}
if (!(target instanceof RecyclerView) && velocityY < 0) {
RecyclerView recycler = findRecycler((ViewGroup) target);
if (recycler != null){
target = recycler;
}
}
if (target instanceof RecyclerView && velocityY < 0) {
final RecyclerView recyclerView = (RecyclerView) target;
final View firstChild = recyclerView.getChildAt(0);
final int childAdapterPosition = recyclerView.getChildAdapterPosition(firstChild);
consumed = childAdapterPosition > TOP_CHILD_FLING_THRESHOLD;
}
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}


@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
isPositive = dy > 0;
}


@Nullable
private RecyclerView findRecycler(ViewGroup container){
for (int i = 0; i < container.getChildCount(); i++) {
View childAt = container.getChildAt(i);
if (childAt instanceof RecyclerView){
return (RecyclerView) childAt;
}
if (childAt instanceof ViewGroup){
return findRecycler((ViewGroup) childAt);
}
}
return null;
}
}

在这里添加另一个答案,因为上面的答案要么没有完全满足我的需要,要么没有很好地工作。这一个部分是基于这里传播的思想。

这个是做什么的?

向下抛掷的情景: 如果 AppBarLayout 崩溃了,那么它就会自动让珊瑚视图抛出,而不需要做任何事情。否则,它将折叠 AppBarLayout 并阻止回收视图进行抛出操作。一旦它崩溃(达到给定速度要求的点) ,如果还有速度剩余,回收视图就会以原始速度抛出,减去 AppBarLayout 刚刚消耗的崩溃速度。

情景向上抛掷: 如果回收视图的滚动偏移量不是零,它将以原始速度抛出。一旦这个过程结束,并且还有剩余的速度(例如,回收视图滚动到位置0) ,AppBarLayout 就会扩展到原始速度减去刚刚消耗的需求。 否则,AppBarLayout 将扩展到原始速度所要求的点。

AFAIK,这是预定行为。

有很多的反射涉及,并且它是相当自定义的。没有问题,但尚未发现。 它也是在 Kotlin 写成的,但理解它应该没有问题。 您可以使用 IntelliJ Kotlin 插件将其编译为 bytecode-> 并将其反编译回 Java。 要使用它,请将其放在 android.support. v7.widget 包中,并将其设置为 AppBarLayout 的坐标布局。LayoutParams 在代码中的行为(或者添加 xml 应用构造函数之类的)

/*
* Copyright 2017 Julian Ostarek
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package android.support.v7.widget


import android.support.design.widget.AppBarLayout
import android.support.design.widget.CoordinatorLayout
import android.support.v4.widget.ScrollerCompat
import android.view.View
import android.widget.OverScroller


class SmoothScrollBehavior(recyclerView: RecyclerView) : AppBarLayout.Behavior() {
// We're using this SplineOverScroller from deep inside the RecyclerView to calculate the fling distances
private val splineOverScroller: Any
private var isPositive = false


init {
val scrollerCompat = RecyclerView.ViewFlinger::class.java.getDeclaredField("mScroller").apply {
isAccessible = true
}.get(recyclerView.mViewFlinger)
val overScroller = ScrollerCompat::class.java.getDeclaredField("mScroller").apply {
isAccessible = true
}.get(scrollerCompat)
splineOverScroller = OverScroller::class.java.getDeclaredField("mScrollerY").apply {
isAccessible = true
}.get(overScroller)
}


override fun onNestedFling(coordinatorLayout: CoordinatorLayout?, child: AppBarLayout, target: View?, velocityX: Float, givenVelocity: Float, consumed: Boolean): Boolean {
// Making sure the velocity has the correct sign (seems to be an issue)
var velocityY: Float
if (isPositive != givenVelocity > 0) {
velocityY = givenVelocity * - 1
} else velocityY = givenVelocity


if (velocityY < 0) {
// Decrement the velocity to the maximum velocity if necessary (in a negative sense)
velocityY = Math.max(velocityY, - (target as RecyclerView).maxFlingVelocity.toFloat())


val currentOffset = (target as RecyclerView).computeVerticalScrollOffset()
if (currentOffset == 0) {
super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, false)
return true
} else {
val distance = getFlingDistance(velocityY.toInt()).toFloat()
val remainingVelocity = - (distance - currentOffset) * (- velocityY / distance)
if (remainingVelocity < 0) {
(target as RecyclerView).addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
recyclerView.post { recyclerView.removeOnScrollListener(this) }
if (recyclerView.computeVerticalScrollOffset() == 0) {
super@SmoothScrollBehavior.onNestedFling(coordinatorLayout, child, target, velocityX, remainingVelocity, false)
}
}
}
})
}
return false
}
}
// We're not getting here anyway, flings with positive velocity are handled in onNestedPreFling
return false
}


override fun onNestedPreFling(coordinatorLayout: CoordinatorLayout?, child: AppBarLayout, target: View?, velocityX: Float, givenVelocity: Float): Boolean {
// Making sure the velocity has the correct sign (seems to be an issue)
var velocityY: Float
if (isPositive != givenVelocity > 0) {
velocityY = givenVelocity * - 1
} else velocityY = givenVelocity


if (velocityY > 0) {
// Decrement to the maximum velocity if necessary
velocityY = Math.min(velocityY, (target as RecyclerView).maxFlingVelocity.toFloat())


val topBottomOffsetForScrollingSibling = AppBarLayout.Behavior::class.java.getDeclaredMethod("getTopBottomOffsetForScrollingSibling").apply {
isAccessible = true
}.invoke(this) as Int
val isCollapsed = topBottomOffsetForScrollingSibling == - child.totalScrollRange


// The AppBarlayout is collapsed, we'll let the RecyclerView handle the fling on its own
if (isCollapsed)
return false


// The AppbarLayout is not collapsed, we'll calculate the remaining velocity, trigger the appbar to collapse and fling the RecyclerView manually (if necessary) as soon as that is done
val distance = getFlingDistance(velocityY.toInt())
val remainingVelocity = (distance - (child.totalScrollRange + topBottomOffsetForScrollingSibling)) * (velocityY / distance)


if (remainingVelocity > 0) {
(child as AppBarLayout).addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
// The AppBarLayout is now collapsed
if (verticalOffset == - appBarLayout.totalScrollRange) {
(target as RecyclerView).mViewFlinger.fling(velocityX.toInt(), remainingVelocity.toInt())
appBarLayout.post { appBarLayout.removeOnOffsetChangedListener(this) }
}
}
})
}


// Trigger the expansion of the AppBarLayout
super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, false)
// We don't let the RecyclerView fling already
return true
} else return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY)
}


override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout?, child: AppBarLayout?, target: View?, dx: Int, dy: Int, consumed: IntArray?) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed)
isPositive = dy > 0
}


private fun getFlingDistance(velocity: Int): Double {
return splineOverScroller::class.java.getDeclaredMethod("getSplineFlingDistance", Int::class.javaPrimitiveType).apply {
isAccessible = true
}.invoke(splineOverScroller, velocity) as Double
}


}

自从支持设计26.0.0以来,它已经被修复了。

compile 'com.android.support:design:26.0.0'

Julian Os 是对的。

如果回收视图低于阈值和滚动条,Manolo Garcia 的回答 就不起作用。你必须比较 offset的回收视图和 velocity to the distance,而不是项目的立场。

我根据 Julian 的 Kotlin 代码,减去反射,做了个 java 版本。

public final class FlingBehavior extends AppBarLayout.Behavior {


private boolean isPositive;


private float mFlingFriction = ViewConfiguration.getScrollFriction();


private float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
private final float INFLEXION = 0.35f;
private float mPhysicalCoeff;


public FlingBehavior(){
init();
}


public FlingBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}


private void init(){
final float ppi = BaseApplication.getInstance().getResources().getDisplayMetrics().density * 160.0f;
mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* ppi
* 0.84f; // look and feel tuning
}


@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {


if (velocityY > 0 && !isPositive || velocityY < 0 && isPositive) {
velocityY = velocityY * -1;
}
if (target instanceof RecyclerView && velocityY < 0) {
RecyclerView recyclerView = (RecyclerView) target;


double distance = getFlingDistance((int) velocityY);
if (distance < recyclerView.computeVerticalScrollOffset()) {
consumed = true;
} else {
consumed = false;
}
}
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}


@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
isPositive = dy > 0;
}


public double getFlingDistance(int velocity){
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
}


private double getSplineDeceleration(int velocity) {
return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
}


}

我已经找到了修复 Eniz Bilgin https://stackoverflow.com/a/45090239/7639018

这个问题已经用这个库中的库解决了。

(https://developer.android.com/topic/libraries/support-library/setup.html)

allprojects {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
}
}

解答: 它在支持库 v26中是固定的

但是 v26有一些抛弃的问题。有时,即使抛弃并不太难,AppBar 也会反弹回来。

如何删除应用程序条上的弹跳效果?

如果在更新以支持 v26时遇到同样的问题,以下是这个 回答的摘要。

解决方案 : 扩展 AppBar 的默认行为并阻止对 在 AppBar 时,行为的 onNestedPreScroll ()和 onNestedScroll () 当嵌套滚动还没有停止的时候。

关于 谷歌问题追踪器,它已经用 Android 26.0.0-beta 2版本的支持库修复了

请更新你的 Android 支持库版本26.0.0-beta 2。

如果任何问题仍然存在,请在 谷歌问题追踪器报告,他们将重新开放检查。

这是我在项目中的解决方案。
只要在下载 Action _ Down 时停止 mScroll

Xml:

    <android.support.design.widget.AppBarLayout
android:id="@+id/smooth_app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
app:elevation="0dp"
app:layout_behavior="com.sogou.groupwenwen.view.topic.FixAppBarLayoutBehavior">

Java:

    public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
if (ev.getAction() == ACTION_DOWN) {
Object scroller = getSuperSuperField(this, "mScroller");
if (scroller != null && scroller instanceof OverScroller) {
OverScroller overScroller = (OverScroller) scroller;
overScroller.abortAnimation();
}
}


return super.onInterceptTouchEvent(parent, child, ev);
}


private Object getSuperSuperField(Object paramClass, String paramString) {
Field field = null;
Object object = null;
try {
field = paramClass.getClass().getSuperclass().getSuperclass().getDeclaredField(paramString);
field.setAccessible(true);
object = field.get(paramClass);
} catch (Exception e) {
e.printStackTrace();
}
return object;
}


//or check the raw file:
//https://github.com/shaopx/CoordinatorLayoutExample/blob/master/app/src/main/java/com/spx/coordinatorlayoutexample/util/FixAppBarLayoutBehavior.java

对于安卓来说,

如果您的清单文件有一个 android: hardwareSpeed = “ false”行,请删除它。