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,以便回收站视图在开始时没有高度。
已接受的答案已过时。现在有内置功能可以做到这一点。我粘贴了整个布局代码,这样可以帮助您理解。
您只需要将 CoordinatorLayout
与 AppBarLayout
一起使用。这种设计模式称为 Lift On Scroll,可以通过在 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>
如果您使用 CoordinatorLayout
,您不需要任何额外的代码就可以自己完成这项工作,只需对样式和布局进行一些设置 XML,检查一下:
您的应用样式应使用 MaterialCompoment
样式,例如 src/main/res/values/styles.xml.
设置你的 AppBarLayout:
- 为此组件使用任何 MaterialCompoments 样式,例如:
Widget.MaterialComponents.AppBarLayout.Surface
。
- 设置
app:liftOnScroll="true"
以启用基于滚动的自动提升。
设置滚动视图:
- 设置
app:layout_behavior="@string/appbar_scrolling_view_behavior
.
https://github.com/danielgomezrico/spike-appbarlayout-toolbar-automatic-elevation
我尝试在 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,以便回收站视图在开始时没有高度。
已接受的答案已过时。现在有内置功能可以做到这一点。我粘贴了整个布局代码,这样可以帮助您理解。
您只需要将 CoordinatorLayout
与 AppBarLayout
一起使用。这种设计模式称为 Lift On Scroll,可以通过在 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>
如果您使用 CoordinatorLayout
,您不需要任何额外的代码就可以自己完成这项工作,只需对样式和布局进行一些设置 XML,检查一下:
您的应用样式应使用
MaterialCompoment
样式,例如 src/main/res/values/styles.xml.设置你的 AppBarLayout:
- 为此组件使用任何 MaterialCompoments 样式,例如:
Widget.MaterialComponents.AppBarLayout.Surface
。 - 设置
app:liftOnScroll="true"
以启用基于滚动的自动提升。
- 为此组件使用任何 MaterialCompoments 样式,例如:
设置滚动视图:
- 设置
app:layout_behavior="@string/appbar_scrolling_view_behavior
.
- 设置
https://github.com/danielgomezrico/spike-appbarlayout-toolbar-automatic-elevation