Android 滚动时工具栏提升

Android toolbar elevation when scrolling

我尝试在 google 地图 android 应用中实现搜索栏:

当回收站视图处于初始状态时,工具栏没有高度。只有当用户开始滚动时,高度才会可见。并且搜索栏(工具栏)永远不会崩溃。这是我试图复制的内容:

<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 地图应用程序中看到的这种行为?

我正在使用

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

我的片段中有一个 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);
            }
        }
    });
}

EDIT 正如评论中指出的,我的回答现在已经过时了,请参阅


无论您是否使用 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);
}

一定要给你的RecyclerView分配一个LayoutManager否则调用canScrollVertically可能会导致崩溃!

当我想做类似的事情,但对于更复杂的视图层次结构时,我在页面时发现了这个。

经过一些研究,我能够使用自定义行为获得相同的效果。这适用于协调器布局中的任何视图(假设有一个嵌套的滚动元素,例如 RecyclerView 或 NestedScrollView)

注意: 这仅适用于 API 21 及更高版本,因为 ViewCompat.setElevation 在棒棒糖之前似乎没有任何效果并且 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>

这是一个很好的问题,但是 none 的现有答案已经足够好了。绝对不建议调用 getTop(),因为它非常不可靠。如果您查看遵循 Material Design Refresh (2018) 准则的 Google 应用程序的较新版本,它们会在开始时隐藏高度,并在用户向下滚动时立即添加它,并在用户滚动时再次隐藏它,再次登顶

我使用以下方法达到了同样的效果:

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" 属性添加到您的 Toolbar 或 AppBarLayout,以便回收站视图在开始时没有高度。

已接受的答案已过时。现在有内置功能可以做到这一点。我粘贴了整个布局代码,这样可以帮助您理解。

您只需要将 CoordinatorLayoutAppBarLayout 一起使用。这种设计模式称为 Lift On Scroll,可以通过在 AppBarLayout.

上设置 app:liftOnScroll="true" 来实现

注意:liftOnScroll 属性要求您将 @string/appbar_scrolling_view_behavior layout_behavior 应用于滚动视图(例如,NestedScrollViewRecyclerView 等。 ).

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

如果您使用 CoordinatorLayout,您不需要任何额外的代码就可以自己完成这项工作,只需对样式和布局进行一些设置 XML,检查一下:

  1. 您的应用样式应使用 MaterialCompoment 样式,例如 src/main/res/values/styles.xml.

  2. 设置你的 AppBarLayout:

    • 为此组件使用任何 MaterialCompoments 样式,例如:Widget.MaterialComponents.AppBarLayout.Surface
    • 设置app:liftOnScroll="true"以启用基于滚动的自动提升。
  3. 设置滚动视图:

    • 设置app:layout_behavior="@string/appbar_scrolling_view_behavior.

https://github.com/danielgomezrico/spike-appbarlayout-toolbar-automatic-elevation