Высота панели инструментов Android при прокрутке

Я пытаюсь реализовать панель поиска, например, в google maps android app:

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

Когда представление recycler находится в исходном состоянии, панель инструментов не имеет высоты. Только когда пользователи начинают прокрутку, высота становится видимой. И панель поиска (панель инструментов) никогда не рушится. Вот что я пытался воспроизвести:

<android.support.design.widget.CoordinatorLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="64dp">

            <!-- content -->

        </android.support.v7.widget.Toolbar>

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

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

И здесь вы можете увидеть результат:

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

Таким образом, проблема с моим решением заключается в том, что высота панели инструментов всегда видна. Но я хочу, чтобы он появлялся только тогда, когда просмотр ресайклера прокручивается за ним. Есть ли что-нибудь из библиотеки поддержки дизайна, которая позволяет такое поведение, как показано в приложении Google Maps?

Я использую

com.android.support:appcompat-v7:23.2.0
com.android.support:design:23.2.0

Ответ 1

Независимо от того, используете ли вы CoordinatorLayout или нет, RecyclerView.OnScrollListener кажется правильным путем для определения высоты. Однако, по моему опыту, recyclerview.getChild(0).getTop() не является надежным, и его нельзя использовать для определения состояния прокрутки. Вместо этого это то, что работает:

private static final int SCROLL_DIRECTION_UP = -1;
// ...
// Put this into your RecyclerView.OnScrollListener > onScrolled() method
if (recyclerview.canScrollVertically(SCROLL_DIRECTION_UP)) {
   // Remove elevation
   toolbar.setElevation(0f);
} else {
   // Show elevation
   toolbar.setElevation(50f);
}

Обязательно назначьте LayoutManager вашему RecyclerView, или вызов canScrollVertically может вызвать сбой!

Ответ 2

Это хороший вопрос, но ни один из существующих ответов не достаточно хорош. Вызывать getTop() абсолютно не рекомендуется, так как это очень ненадежно. Если вы посмотрите на новые версии приложений Google, которые следуют рекомендациям Material Design Refresh (2018), они вначале скрывают высоту и сразу добавляют ее, когда пользователь прокручивает страницу вниз, и снова скрывают ее, когда пользователь прокручивает и снова достигает вершины.

Мне удалось добиться того же эффекта, используя следующее:

val toolbar: android.support.v7.widget.Toolbar? = activity?.findViewById(R.id.toolbar);

recyclerView?.addOnScrollListener(object: RecyclerView.OnScrollListener() {
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy);

        if(toolbar == null) {
            return;
        }

        if(!recyclerView.canScrollVertically(-1)) {
            // we have reached the top of the list
            toolbar.elevation = 0f
        } else {
            // we are not at the top yet
            toolbar.elevation = 50f
        }
    }
});

Это прекрасно работает с вертикальными видами переработчика (даже с видом вкладок или другими видами переработчика внутри них);

Несколько важных замечаний:

  • Здесь я делаю это внутри фрагмента отсюда activity?.findViewById...
  • Если ваша панель инструментов вложена в AppBarLayout, то вместо применения повышения к панели инструментов ее следует применить к AppBarLayout.
  • Вы должны добавить атрибуты android:elevation="0dp" и app:elevation="0dp" на панель инструментов или в AppBarLayout, чтобы представление рециркулятора не имело высоты в начале.

Ответ 3

У меня есть RecyclerView в моем фрагменте. Я мог бы добиться аналогичного эффекта, используя следующий код:

Это не самый умный способ, и вы можете ждать лучших ответов.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    // Initial Elevation
    final Toolbar toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
    if(toolbar!= null)
        toolbar.setElevation(0);

    // get initial position
    final int initialTopPosition = mRecyclerView.getTop();

    // Set a listener to scroll view
    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

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

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            if(toolbar!= null && mRecyclerView.getChildAt(0).getTop() < initialTopPosition ) {
                toolbar.setElevation(50);
            } else {
                toolbar.setElevation(0);
            }
        }
    });
}

Ответ 4

Я нашел это когда страница, когда я хотел сделать что-то подобное, но для более сложной View Hierarchy.

После некоторых исследований я смог получить тот же эффект, используя нестандартное поведение. Это работает для любого представления в макете координатора (учитывая наличие вложенного элемента прокрутки, такого как RecyclerView или NestedScrollView)

Примечание: Это работает только для API 21 и выше, так как ViewCompat.setElevation, похоже, не оказывает никакого эффекта до lollipop, а AppBarLayout # setTargetElevation устарела

ShadowScrollBehavior.java

public class ShadowScrollBehavior extends AppBarLayout.ScrollingViewBehavior
        implements View.OnLayoutChangeListener {

    int totalDy = 0;
    boolean isElevated;
    View child;

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

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child,
                                   View dependency) {
        parent.addOnLayoutChangeListener(this);
        this.child = child;
        return super.layoutDependsOn(parent, child, dependency);
    }

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                       @NonNull View child, @NonNull View directTargetChild,
                                       @NonNull View target, int axes, int type) {
        // Ensure we react to vertical scrolling
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL ||
                super.onStartNestedScroll(coordinatorLayout, child, directTargetChild,
                        target, axes, type);
    }

    @Override
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                  @NonNull View child, @NonNull View target,
                                  int dx, int dy, @NonNull int[] consumed, int type) {
        totalDy += dy;
        if (totalDy <= 0) {
            if (isElevated) {
                ViewGroup parent = (ViewGroup) child.getParent();
                if (parent != null) {
                    TransitionManager.beginDelayedTransition(parent);
                    ViewCompat.setElevation(child, 0);
                }
            }
            totalDy = 0;
            isElevated = false;
        } else {
            if (!isElevated) {
                ViewGroup parent = (ViewGroup) child.getParent();
                if (parent != null) {
                    TransitionManager.beginDelayedTransition(parent);
                    ViewCompat.setElevation(child, dp2px(child.getContext(), 4));
                }
            }
            if (totalDy > target.getBottom())
                totalDy = target.getBottom();
            isElevated = true;
        }
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
    }


    private float dp2px(Context context, int dp) {
        Resources r = context.getResources();
        float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
        return px;
    }


    @Override
    public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
        totalDy = 0;
        isElevated = false;
        ViewCompat.setElevation(child, 0);
    }
}

my_activity_layout.xml

<android.support.design.widget.CoordinatorLayout
    android:fitsSystemWindows="true"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_height="match_parent"
        android:layout_width="match_parent" />

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        app:layout_behavior="com.myapp.ShadowScrollBehavior">


        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_height="64dp"
            android:layout_width="match_parent">

            <!-- content -->

        </android.support.v7.widget.Toolbar>

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

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

Ответ 5

Принятый ответ устарел. Теперь для этого есть встроенный функционал. Я вставляю весь код макета, чтобы он помог вам понять.

Вам просто нужно использовать CoordinatorLayout с AppBarLayout. Этот шаблон дизайна называется "Lift On Scroll" и может быть реализован установкой app: liftOnScroll = "true" в вашем AppBarLayout.

Примечание. Атрибут liftOnScroll требует, чтобы вы применили @string/appbar_scrolling_view_behavior layout_behavior к вашему представлению с прокруткой (например, NestedScrollView, RecyclerView и т.д.).

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.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"
    tools:context=".MainActivity"
    android:background="@color/default_background">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:liftOnScroll="true">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/default_background" />

    </com.google.android.material.appbar.AppBarLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/appbar"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:orientation="vertical" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

ссылался на эту документацию https://github.com/material-components/material-components-android/blob/master/docs/components/AppBarLayout.md