Бросание с RecyclerView + AppBarLayout
Я использую новый CoordinatorLayout с AppBarLayout и CollapsingToolbarLayout. Ниже AppBarLayout, у меня есть RecyclerView со списком контента.
Я проверил, что fling scrolling работает на RecyclerView, когда я прокручиваю вверх и вниз по списку. Тем не менее, я также хотел бы, чтобы AppBarLayout плавно прокручивался во время расширения.
при прокрутке вверх, чтобы развернуть CollaspingToolbarLayout, прокрутка сразу же останавливается после снятия пальца кино. Если вы прокручиваете вверх быстрым движением, иногда CollapsingToolbarLayout также снова сворачивается. Такое поведение с RecyclerView, по-видимому, работает совсем иначе, чем при использовании NestedScrollView.
Я пытался установить различные свойства прокрутки на recyclerview, но я не смог понять это.
вот видео, показывающее некоторые из проблем прокрутки. https://youtu.be/xMLKoJOsTAM
вот пример показывая проблему с RecyclerView (CheeseDetailActivity). https://github.com/tylerjroach/cheesesquare
вот оригинальный пример, который использует NestedScrollView от Криса Бэйнса. https://github.com/chrisbanes/cheesesquare
18 ответов:
ответ Бояршинов почти правильно.
основная проблема заключается в том, что 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) { 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
обновление еще не исправило это.Я нашел своего рода хак, чтобы исправить это с бросив. Хитрость заключается в том, чтобы восстановить событие fling, если верхний дочерний элемент ScrollingView находится близко к началу данных в адаптере.
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>
EDIT: восстановление событий Fling теперь основано на
verticalScrollOffset
вместо количества элементов сверхуRecyclerView
.EDIT2: проверить объект как
ScrollingView
экземпляр интерфейса вместоRecyclerView
. ОбаRecyclerView
иNestedScrollingView
ее реализовать.
Я нашел исправление, применив OnScrollingListener к recyclerView. теперь это работает очень хорошо. Проблема в том, что recyclerview предоставил неверное потребляемое значение, и поведение не знает, когда recyclerview прокручивается вверх.
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); } } } }
это гладкая версия Google Support Design AppBarLayout. Если вы используете AppBarLayout, вы будете знать, что у него есть проблема с fling.
compile "me.henrytao:smooth-app-bar-layout:<latest-version>"
в разделе Библиотека здесь.. https://github.com/henrytao-me/smooth-app-bar-layout
Это ошибка recyclerview . Это должно быть исправлено в v23.1.0.
смотри https://code.google.com/p/android/issues/detail?id=177729
Это мой макет и прокрутка он работает так, как должен.
<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>
мое решение до сих пор, на основе Мак Петь и Маноло Гарсия ответы.
Это не совсем идеальный. На данный момент я не знаю, как пересчитать скорость valide, чтобы избежать странного эффекта: панель приложений может расширяться быстрее, чем скорость прокрутки. Но состояние с расширенной панелью приложений и прокрученным представлением recycler не может быть достигнуто.
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
не будет прокручивать плавно, что делает его застрять.Это было потому, что, по какой-то причине,Я забыл, что положил свою
RecyclerView
наNestedScrollView
.Это глупая ошибка, но мне потребовалось время, чтобы понять это...
Я добавляю вид высоты 1dp внутри AppBarLayout, а затем он работает намного лучше. Это мой макет.
<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" />
уже некоторые довольно популярные решения здесь, но после игры с ними я придумал довольно простое решение, которое хорошо работало для меня. Мое решение также гарантирует, что
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; });
принятый ответ не работа для меня, потому что я
RecyclerView
внутриSwipeRefreshLayout
иViewPager
. Это улучшенная версия, которая ищет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; } }
ответ: это исправлено в библиотеке поддержки v26
но v26 имеет некоторые проблемы в бросании. Иногда, AppBar отскакивает назад снова, даже если бросок не слишком трудно.
Как удалить эффект подпрыгивания на панели приложений?
Если вы столкнулись с той же проблемой при обновлении до поддержки v26, вот резюме этого ответ.
решение: расширить поведение AppBar по умолчанию и заблокируйте вызов для AppBar.Поведение onNestedPreScroll () и onNestedScroll () когда AppBar трогается, пока NestedScroll еще не остановился.
Джулиан ОС прав.
ответ Маноло Гарсии не работает, если recyclerview находится ниже порога и прокручивается. Вы должны сравнить
offset
из recyclerview иvelocity to the distance
, не позиция элемента.Я сделал версию 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)); } }
Я нашел исправление с помощью Билгин Enizhttps://stackoverflow.com/a/45090239/7639018
проблема была решена с библиотеками в этом репозитории.
(https://developer.android.com/topic/libraries/support-library/setup.html)
allprojects { repositories { jcenter() maven { url "https://maven.google.com" } } }
со ссылкой на Google issue tracker, это было исправлено с Android 26.0.0-beta2 версии библиотеки поддержки
пожалуйста, обновите ваш библиотека поддержки Android версии 26.0.0-beta2.
Если какая-либо проблема сохраняется, пожалуйста, сообщите в Google issue tracker они снова откроются для изучения.
добавление другого ответа здесь, так как вышеперечисленные либо не полностью удовлетворяли мои потребности, либо не очень хорошо работали. Этот частично основан на идеях, распространенных здесь.
Так что же этот делает?
сценарий вниз бросать: Если AppBarLayout свернут, он позволяет RecyclerView бросать сам по себе, ничего не делая. В противном случае он сворачивает AppBarLayout и не позволяет RecyclerView выполнять свой бросок. Как только он рухнул (до точки что данная скорость требует), и если есть скорость слева, RecyclerView получает брошен с исходной скоростью минус то, что AppBarLayout просто потребляется коллапс.
сценарий вверх бросать: Если смещение прокрутки RecyclerView не равно нулю, оно сбрасывается с исходной скоростью. Как только это будет закончено, и если все еще остается скорость (т. е. RecyclerView прокручивается до позиции 0), AppBarLayout расширяется до такой степени, что исходная скорость минус только что потребленные требования. В противном случае AppBarLayout расширяется до такой степени, что требуется исходная скорость.
AFAIK, это поведение с отступом.
есть много размышлений, и это довольно нестандартная. Никаких проблем пока не найдено. Это также написано в Котлине, но понимание этого не должно быть проблемой. Вы можете использовать плагин IntelliJ Kotlin для компиляции его в байт-код - > и декомпилировать его обратно на Java. Чтобы использовать его, поместите его в андроид.поддержка.v7.виджет пакет и установить его в качестве Appbarlayout в CoordinatorLayout.Поведение 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 } }
Это мое решение в моем проекте.
просто остановите mScroller, когда получите Action_Downxml:
<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">
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