Высота панели инструментов 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