【问题标题】:Android toolbar elevation when scrolling滚动时的Android工具栏提升
【发布时间】:2016-03-01 13:31:50
【问题描述】:

我尝试在 google maps 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>

在这里你可以看到结果:

所以我的解决方案的问题是,工具栏的高度总是可见的。但我希望它仅在回收器视图在其后面滚动时出现。设计支持库中是否有任何东西可以实现在谷歌地图应用中看到的这种行为?

我正在使用

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

【问题讨论】:

  • 您尝试过使用 CollapsingToolbarLayout 吗?
  • 接受的答案已过时。现在有内置功能可以做到这一点。阅读我的答案以获取更多详细信息。 stackoverflow.com/a/58272283/4291272

标签: android android-layout android-recyclerview android-toolbar


【解决方案1】:

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

您只需将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

【讨论】:

  • 不幸的是 liftOnScroll 不适合我。我有一个应用栏,里面有 2 个视图。
  • 您的应用程序主题需要有一个 MaterialComponent 父级,例如 parent="Theme.MaterialComponents.Light.NoActionBar"
  • 谢谢,伙计,这对我来说是一个非常准确的答案。
  • 如果我的顶部标题左右有边距我想在滚动时缩小怎么办
  • liftOnScroll 显示滚动时的高度,但是当它一直在顶部时它会删除高度。当 appbar 一直滚动到顶部时,如何为它添加高度?
【解决方案2】:

编辑正如 cmets 中所指出的,我的答案现在已经过时了,请参阅 https://stackoverflow.com/a/58272283/4291272


无论您是否使用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 的调用可能会导致崩溃!

【讨论】:

  • 这可行,但现在使用 liftOnScroll 更容易获得所需的功能。文档链接:github.com/material-components/material-components-android/blob/…
  • 接受的答案已过时。现在有内置功能可以做到这一点。阅读我的答案以获取更多详细信息。 stackoverflow.com/a/58272283/4291272
  • @RicardoContreras liftOnScroll 并非在所有情况下都更容易。我有一个相当复杂的导航结构,而在我的情况下,liftOnScroll 实际上完全不可能实现,因为 RecyclerViews 嵌套在 Fragments 和 ViewPagers 中,而 OnScrollListener 解决方案非常简单,只需进行一些代码调整。
【解决方案3】:

这是一个很好的问题,但现有的答案都不够好。绝对不建议致电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...
  • 如果您的 Toolbar 嵌套在 AppBarLayout 中,那么您应该将其应用于 AppBarLayout,而不是对 Toolbar 应用高度。
  • 您应该将 android:elevation="0dp"app:elevation="0dp" 属性添加到您的 Toolbar 或 AppBarLayout,以便回收器视图在开始时没有高度。

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 看起来不错,谢谢。您认为仅使用 xml 是不可能的吗?
    • 我认为仅通过 XML 是不可能的。工具栏的 DefaultBahevior 与海拔无关(但与它的高度有关)。即使你找到了使用 CoordinatorLayout 的方法,我想你也需要一些代码......我会尝试只使用 CoordinatorLayout 来达到同样的效果......我会尽快分享
    • 几乎完美,canScrollVertically 是您在 OnScrollListener 中寻找的内容(请参阅我的回答)。此外,在调用 addOnScrollListener() 之前简单地返回 if toolbar == null 会使您的代码更好一点恕我直言。
    【解决方案5】:

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

    经过一些研究,我能够使用自定义行为获得相同的效果。这适用于协调器布局中的任何视图(假设存在嵌套滚动元素,例如 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>
    

    【讨论】:

      【解决方案6】:

      如果您使用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

      【讨论】:

      • 链接已损坏@Daniel Gomez Rico
      • @Sekiro 我更新了,谢谢指出
      猜你喜欢
      • 2016-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-17
      相关资源
      最近更新 更多