代码之家  ›  专栏  ›  技术社区  ›  DarkLeafyGreen

滚动时的Android工具栏提升

  •  30
  • DarkLeafyGreen  · 技术社区  · 10 年前

    我尝试在谷歌地图android应用程序中实现搜索栏:

    enter image description here

    当回收器视图处于初始状态时,工具栏没有标高。只有当用户开始滚动时,立面才可见。搜索栏(工具栏)永远不会塌陷。以下是我试图复制的内容:

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

    在这里你可以看到结果:

    enter image description here

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

    我正在使用

    com.android.support:appcompat-v7:23.2.0
    com.android.support:design:23.2.0
    
    6 回复  |  直到 6 年前
        1
  •  44
  •   Vadim Kotov First Zero    6 年前

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

    你只需要使用 CoordinatorLayout 具有 AppBarLayout 这种设计模式称为 提升式滚动 并且可以通过设置 app:liftOnScroll="true" 在您的 应用栏布局 .

    注: 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

        2
  •  34
  •   Daniel Veihelmann    5 年前

    编辑 正如评论中指出的,我的答案现在已经过时了,请参见 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 或者 canScroll垂直滚动 可能导致撞车!

        3
  •  4
  •   Vahid Amiri    8 年前

    这是一个好问题,但现有的答案都不够好。使命感 getTop() 绝对不推荐,因为它非常不可靠。如果你看看遵循“材料设计刷新”(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中,则应将其应用于AppBarLayer,而不是将提升应用于工具栏。
    • 您应该添加 android:elevation="0dp" 和 app:elevation="0dp" 属性添加到工具栏或AppBarLayout,以便回收器视图在开始时没有提升。
        4
  •  3
  •   guipivoto SadeghAlavizadeh    10 年前

    我的碎片中有一个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);
                }
            }
        });
    }
    
        5
  •  3
  •   user3425867    8 年前

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

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

    注: 这仅适用于API 21及以上版本的ViewCompat。setElevation在棒棒糖之前似乎没有任何效果,AppBarLayout#setTargetElevation已弃用

    阴影滚动行为.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);
        }
    }
    

    我的活动_布局.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
  •  1
  •   Daniel Gomez Rico    5 年前

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

    1. 你的应用风格应该使用 MaterialCompoment 样式,如src/main/res/values/styles.xml。

    2. 设置AppBarLayout:

      • 为此组件使用任何MaterialComponents样式,如: 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