Подбираем с RecyclerView + AppBarLayout

171

Я использую новый CoordinatorLayout с AppBarLayout и CollapsingToolbarLayout. Ниже AppBarLayout у меня есть RecyclerView со списком контента.

Я проверил, что прокрутка броска работает на RecyclerView, когда я прокручиваю вверх и вниз по списку. Тем не менее, я также хотел бы, чтобы AppBarLayout плавно прокручивался во время расширения.

При прокрутке вверх, чтобы развернуть CollaspingToolbarLayout, прокрутка немедленно прекращается, как только вы убираете палец с экрана. Если вы прокрутите вверх быстрым движением, иногда CollapsingToolbarLayout также снова сворачивается. Такое поведение с RecyclerView, кажется, работает по-другому, чем при использовании NestedScrollView.

Я пытался установить различные свойства прокрутки в окне просмотра, но не смог понять это.

Вот видео, показывающее некоторые проблемы с прокруткой. https://youtu.be/xMLKoJOsTAM

Вот пример, показывающий проблему с RecyclerView (CheeseDetailActivity). https://github.com/tylerjroach/cheesesquare

Вот оригинальный пример, который использует NestedScrollView от Криса Бейнса. https://github.com/chrisbanes/cheesesquare

tylerjroach
источник
Я испытываю ту же самую проблему (я использую с RecyclerView). Если вы посмотрите на список в Google Play Store для любого приложения, оно, похоже, будет вести себя правильно, поэтому определенно есть решение ...
Aneem
Эй, Aneem, я знаю, что это не самое лучшее решение, но я начал экспериментировать с этой библиотекой: github.com/ksoichiro/Android-ObservableScrollView . Особенно в этом упражнении для достижения нужных мне результатов: FlexibleSpaceWithImageRecyclerViewActivity.java. Извините за неправильное написание вашего имени перед редактированием. Автозамена ..
tylerjroach
2
Та же самая проблема здесь, я закончил тем, что избегал AppBarLayout.
Renaud Cerrato
Ага. В итоге я получил именно то, что мне нужно, из библиотеки OvservableScrollView. Я уверен, что это будет исправлено в будущих версиях.
tylerjroach
8
Флэш глючит, проблема поднята (и принята).
Рено Серрато

Ответы:

114

Ответ Кирилла Бояршинова был почти правильным.

Основная проблема заключается в том, что 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;
    }
}

Я надеюсь, что это помогает.

Маноло Гарсия
источник
Вы спасли мой день! Кажется, работает абсолютно нормально! Почему ваш ответ не принят?
Зордид
9
если вы используете SwipeRefreshLayout в качестве родительского для вашего повторного просмотра, просто добавьте этот код: if (target instanceof SwipeRefreshLayout && velocityY < 0) { target = ((SwipeRefreshLayout) target).getChildAt(0); }before if (target instanceof RecyclerView && velocityY < 0) {
LucasFM
1
+1 Анализируя это исправление, я не понимаю, почему Google еще не исправил это. Код кажется довольно простым.
Гастон Флорес
3
Здравствуйте, как добиться того же самого с appbarlayout и Nestedscrollview ... Заранее спасибо ..
Гарри Шарма
1
Это не сработало для меня = / Кстати, вам не нужно перемещать класс в пакет поддержки, чтобы достичь этого, вы можете зарегистрировать DragCallback в конструкторе.
Аугусто Кармо
69

Кажется, что v23обновление еще не исправило это.

Я нашел что-то вроде взлома, чтобы исправить это сбрасыванием. Хитрость заключается в том, чтобы возобновить событие fling, если верхний дочерний элемент ScrollingView находится близко к началу данных в Adapter.

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.

EDIT2: Проверьте цель как ScrollingViewэкземпляр интерфейса вместо RecyclerView. И то RecyclerViewи другое NestedScrollingViewреализуем.

Кирилл Бояршинов
источник
Не допускается получение типов строк для ошибки layout_behavior
Vaisakh N
Я проверил это и работает лучше, человек! но какова цель TOP_CHILD_FLING_THRESHOLD? а почему это 3?
Julio_oa
@Julio_oa TOP_CHILD_FLING_THRESHOLD означает, что событие броска будет возобновлено, если представление переработчика прокручивается до элемента, положение которого ниже этого порогового значения. Кстати, я обновил ответ для использования, verticalScrollOffsetкоторый является более общим. Теперь событие броска будет возобновлено при recyclerViewпрокрутке вверх.
Кирилл Бояршинов
Здравствуйте, как добиться того же самого с appbarlayout и Nestedscrollview ... Заранее спасибо ..
Гарри Шарма
2
@Hardeep изменить target instanceof RecyclerViewна target instanceof NestedScrollViewили более для общего случая target instanceof ScrollingView. Я обновил ответ.
Кирилл Бояршинов
15

Я нашел исправление, применив OnScrollingListener к 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);
            }
        }
    }
}
Мак Синг
источник
Спасибо за ваш пост. Я перепробовал все ответы на этой странице, и, по моему опыту, это самый эффективный ответ. Но RecylerView в моем макете прокручивается внутри до того, как AppBarLayout переместился за пределы экрана, если я не прокручиваю RecyclerView с достаточной силой. Другими словами, когда я прокручиваю RecyclerView с достаточной силой, панель приложений прокручивается за пределы экрана без внутренней прокрутки RecyclerView, но когда я не прокручиваю RecyclerView с достаточной силой, внутренняя прокрутка перед прокруткой AppbarLayout за пределы экрана. Вы знаете, что вызывает это?
Мика Симмонс
В окне повторного просмотра по-прежнему поступают события касания, поэтому оно по-прежнему прокручивается, при этом поведение onNestedFling анимируется для одновременной прокрутки appbarLayout. Может быть, вы можете попробовать переопределить onInterceptTouch в поведении, чтобы изменить это. Для меня текущее поведение является приемлемым из того, что я вижу. (не уверен, что мы видим то же самое)
Мак Синг,
@MakSing, это действительно полезно, CoordinatorLayoutи ViewPagerспасибо большое за это самое ожидаемое решение. Пожалуйста, напишите GIST для того же самого, чтобы другие разработчики также могли извлечь из этого пользу. Я тоже делюсь этим решением. Спасибо еще раз.
Нитин Мисра
1
@MakSing От всех решений, это работает лучше для меня. Я скорректировал скорость, передаваемую onNestedFling, с небольшой скоростью * 0,6f ... кажется, что это дает более приятный поток.
Саберридер
Работает для меня. @MakSing В методе onScrolled вы должны вызывать onNestedFling для AppBarLayout.Behavior, а не для RecyclerViewAppBarBehavior? Кажется немного странным для меня.
Антон Малмыгин
13

Это было исправлено с момента поддержки дизайна 26.0.0.

compile 'com.android.support:design:26.0.0'
Xiaozou
источник
2
Это должно двигаться вверх. Это описано здесь на случай, если кто-то заинтересован в деталях.
Крис Динон
1
Теперь, кажется, есть проблема со строкой состояния, когда при прокрутке вниз строка состояния немного опускается при прокрутке ... это очень раздражает!
коробка
2
@Xiaozou Я использую 26.1.0 и до сих пор есть проблемы с киданием. Быстрое бросание иногда приводит к противоположному движению (скорость движения противоположна / неправильна, как это видно в методе onNestedFling). Воспроизведено в Xiaomi Redmi Note 3 и Galaxy S3
dor506
@ dor506 stackoverflow.com/a/47298312/782870 Я не уверен , что если у нас есть один и тот же вопрос , когда вы говорите , противоположный результат движения. Но я разместил ответ здесь. Надеюсь , что это помогает :)
VIDA
5

Это гладкая версия Google Support Design AppBarLayout. Если вы используете AppBarLayout, вы будете знать, что у него есть проблема с fling.

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

Смотрите библиотеку здесь .. https://github.com/henrytao-me/smooth-app-bar-layout

Мансух Ахир
источник
4

Это ошибка повторного просмотра. Это должно быть исправлено в v23.1.0.

посмотрите https://code.google.com/p/android/issues/detail?id=177729

введите описание изображения здесь

dupengtao
источник
9
v23.4.0 - все еще не исправлено
Артур
3
Все еще не исправлено в v25.1.0
0xcaff
v25.3.1, все еще выглядит плохо.
Бава
1
Это наконец исправлено v26.0.1!
17
2

Это мой макет и свиток. Он работает как надо.

<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>
Луис Пе
источник
2

Мое решение пока основано на Мак Синге и Маноло Гарсии ответах .

Это не совсем идеально. Пока я не знаю, как пересчитать правильную скорость, чтобы избежать странного эффекта: панель приложения может расширяться быстрее, чем скорость прокрутки. Но состояние с расширенной панелью приложений и прокручиваемым видом переработчика не может быть достигнуто.

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

    }

}
Zxcv
источник
Вы можете получить текущую скорость RecyclelerView (по состоянию на 25.1.0), используя отражение: Field viewFlingerField = recyclerView.getClass().getDeclaredField("mViewFlinger"); viewFlingerField.setAccessible(true); Object flinger = viewFlingerField.get(recyclerView); Field scrollerField = flinger.getClass().getDeclaredField("mScroller"); scrollerField.setAccessible(true); ScrollerCompat scroller = (ScrollerCompat) scrollerField.get(flinger); velocity = Math.signum(mVelocity) * Math.abs(scroller.getCurrVelocity());
Николас
2

В моем случае у меня возникла проблема, когда RecyclerView не будет плавно прокручиваться, заставляя его застрять.

Это было потому, что по какой-то причине я забыл, что я положил RecyclerViewвNestedScrollView .

Это глупая ошибка, но мне понадобилось время, чтобы понять это ...

Фарбод Саламат-Заде
источник
1

Я добавляю вид 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" />

Jachumbelechao к Mantekilla
источник
Это работает, только если вы прокрутите вверх. Не тогда, когда вы прокрутите вниз
Артур
Для меня хорошо работает в обоих направлениях. Вы добавили 1dp представление в appbarlayout ?. Я только протестировал его в Android Lollipop и Kitkat.
Jachumbelechao к Mantekilla
Ну, я также использую CollapsingToolbarLayout, который оборачивает панель инструментов. Я положил 1dp вид внутри этого. Это вроде как AppBarLayout -> CollapsingToolbarLayout -> Панель инструментов + 1dp view
Артур
Я не знаю, хорошо ли это работает с CollapsingToolbarLayout. Я только протестировал с этим кодом. Вы пытались поместить представление 1dp вне CollapsingToolbarLayout?
Jachumbelechao в Mantekilla
Да. Прокрутка вверх работает, прокрутка вниз не расширяет панель инструментов.
Артур
1

Здесь уже есть несколько довольно популярных решений, но, поиграв с ними, я пришел к более простому решению, которое хорошо мне подошло. Мое решение также гарантирует, что 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;
    });
Rossco
источник
что такое mPrevDy
ARR.s
1

Принятый ответ не работал для меня, потому что у меня было 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;
    }
}
Dmide
источник
1

Ответ: это исправлено в библиотеке поддержки v26

но v26 имеет некоторую проблему в кидании. Иногда AppBar отскакивает снова, даже если бросать не слишком сложно.

Как удалить эффект отскакивания на панели приложений?

Если вы столкнулись с той же проблемой при обновлении до версии v26, вот краткое изложение этого ответа .

Решение : Расширьте поведение AppBar по умолчанию и заблокируйте вызов для onBestedPreScroll () и onNestedScroll () AppBar.Behavior, когда к AppBar дотронулись, а NestedScroll еще не остановился.

Вида
источник
0

Джулиан Ос прав.

Ответ Маноло Гарсии не сработает, если просмотр у переработчика ниже порога и прокрутки. Вы должны сравнить offsetмнение переработчика и 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));
    }

}
정성민
источник
не может восстановитьBaseApplication
ARR.s
@ ARR.s извините, вы просто замените его своим контекстом, как показано ниже.
정성민
YOUR_CONTEXT.getResources (). GetDisplayMetrics (). Density * 160.0f;
정성민
0

Что касается трекера проблем Google , то это было исправлено в версии библиотеки поддержки Android 26.0.0-beta2

Пожалуйста, обновите вашу версию библиотеки поддержки Android 26.0.0-beta2.

Если какая-либо проблема не устранена, сообщите на трекер проблем Google, что они снова откроются для изучения.

Prags
источник
0

Добавление еще одного ответа здесь, так как вышеперечисленные, либо не полностью соответствовали моим потребностям, либо не очень хорошо работали. Этот частично основан на распространенных здесь идеях.

Так что же делает этот?

Сценарий сброса вниз: если AppBarLayout свернут, он позволяет RecyclerView переключаться самостоятельно, ничего не делая. В противном случае он сворачивает AppBarLayout и не позволяет RecyclerView выполнить его сброс. Как только он свернут (до точки, требуемой для данной скорости), и если остается оставшаяся скорость, RecyclerView сбрасывается с первоначальной скоростью за вычетом того, что AppBarLayout только что потребовал разрушения.

Сценарий вверх: если смещение прокрутки в RecyclerView не равно нулю, оно сбрасывается с исходной скоростью. Как только это закончится, и если еще останется скорость (т. Е. RecyclerView прокручивается в положение 0), AppBarLayout расширяется до точки, в которой исходная скорость минус только что потребленная потребность. В противном случае AppBarLayout расширяется до точки, требуемой исходной скоростью.

AFAIK, это поведение с отступом.

Здесь много размышлений, и это довольно обычай. Пока проблем не найдено. Это также написано на Kotlin, но понимание этого не должно быть проблемой. Вы можете использовать плагин IntelliJ Kotlin, чтобы скомпилировать его в байт-код -> и декомпилировать обратно в Java. Чтобы использовать его, поместите его в пакет android.support.v7.widget и установите его как поведение CoordinatorLayout.LayoutParams в AppBarLayout в коде (или добавьте конструктор, применимый к 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
    }

}
Джулиан Ос
источник
Как это установить?
ARR.s
0

это мое решение в моем проекте.
просто остановите mScroller, когда получите Action_Down

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">

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
shaopx
источник
0

для androidx,

Если в вашем файле манифеста есть строка android: hardwareAccelerated = "false", удалите ее.

Jetwiz
источник