Подтвердить что ты не робот

Как реализовать липкий нижний колонтитул в recyclerview

У меня есть RecyclerView, и мне нужно следующее поведение:

  • если есть много предметов (больше подходит для экрана) - нижний колонтитул - последний элемент
  • если несколько элементов/элементов нет - нижний колонтитул расположен внизу экрана

Пожалуйста, сообщите, как я могу реализовать это поведение.

4b9b3361

Ответ 1

Вы можете использовать RecyclerView.ItemDecoration для реализации этого поведения.

public class StickyFooterItemDecoration extends RecyclerView.ItemDecoration {

    /**
     * Top offset to completely hide footer from the screen and therefore avoid noticeable blink during changing position of the footer.
     */
    private static final int OFF_SCREEN_OFFSET = 5000;

    @Override
    public void getItemOffsets(Rect outRect, final View view, final RecyclerView parent, RecyclerView.State state) {
        int adapterItemCount = parent.getAdapter().getItemCount();
        if (isFooter(parent, view, adapterItemCount)) {
            //For the first time, each view doesn't contain any parameters related to its size,
            //hence we can't calculate the appropriate offset.
            //In this case, set a big top offset and notify adapter to update footer one more time.
            //Also, we shouldn't do it if footer became visible after scrolling.
            if (view.getHeight() == 0 && state.didStructureChange()) {
                hideFooterAndUpdate(outRect, view, parent);
            } else {
                outRect.set(0, calculateTopOffset(parent, view, adapterItemCount), 0, 0);
            }
        }
    }

    private void hideFooterAndUpdate(Rect outRect, final View footerView, final RecyclerView parent) {
        outRect.set(0, OFF_SCREEN_OFFSET, 0, 0);
        footerView.post(new Runnable() {
            @Override
            public void run() {
                parent.getAdapter().notifyDataSetChanged();
            }
        });
    }

    private int calculateTopOffset(RecyclerView parent, View footerView, int itemCount) {
        int topOffset = parent.getHeight() - visibleChildsHeightWithFooter(parent, footerView, itemCount);
        return topOffset < 0 ? 0 : topOffset;
    }

    private int visibleChildsHeightWithFooter(RecyclerView parent, View footerView, int itemCount) {
        int totalHeight = 0;
        //In the case of dynamic content when adding or removing are possible itemCount from the adapter is reliable,
        //but when the screen can fit fewer items than in adapter, getChildCount() from RecyclerView should be used.
        int onScreenItemCount = Math.min(parent.getChildCount(), itemCount);
        for (int i = 0; i < onScreenItemCount - 1; i++) {
            totalHeight += parent.getChildAt(i).getHeight();
        }
        return totalHeight + footerView.getHeight();
    }

    private boolean isFooter(RecyclerView parent, View view, int itemCount) {
        return parent.getChildAdapterPosition(view) == itemCount - 1;
    }
}

Не забудьте установить match_parent для высоты RecyclerView.

Пожалуйста, посмотрите пример приложения https://github.com/JohnKuper/recyclerview-sticky-footer и как он работает http://sendvid.com/nbpj0806

Огромный недостаток этого решения заключается в том, что он работает корректно только после notifyDataSetChanged() во всем приложении (не внутри декорации). С более конкретными уведомлениями он не будет работать должным образом и поддерживать их, он требует более логичной логики. Кроме того, вы можете получить информацию из библиотеки recyclerview-stickyheaders eowise и улучшить это решение.

Ответ 2

Я использую Linearlayout с весами. Я создал несколько значений для веса нижнего колонтитула, он отлично работает.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:orientation="vertical"

<include layout="@layout/header" />

    <android.support.v7.widget.RecyclerView
    android:id="@+id/recycleView"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="0.5"
    tools:layout_height="0dp"
    tools:listitem="@layout/row" />

<TextView
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="@dimen/footer_weight"
    android:padding="@dimen/extra_padding"
    android:paddingEnd="@dimen/small_padding"
    android:paddingLeft="@dimen/small_padding"
    android:paddingRight="@dimen/small_padding"
    android:paddingStart="@dimen/small_padding"
    android:text="@string/contact"
    android:textColor="@color/grey" />

 </LinearLayout>

Ответ 3

Если вы не можете забыть о RecyclerView и использовать ListView, перейдите по ссылке Есть ли эквивалент addHeaderView для RecyclerView? у него есть все, что вам нужно. Это о заголовке, но это почти то же самое, за исключением того, что заголовок находится в начале вашего списка, а нижний колонтитул - в конце.

Ответ 4

Я знаю, что это старый вопрос, но я добавлю ответ для тех, кто будет искать такое решение в будущем. ВОЗМОЖНО, чтобы сохранить последний элемент внизу экрана, если у вас есть только несколько элементов или нет, и прокрутите последний элемент с помощью recyclerview, когда у вас много элементов.

Как достичь. Ваш адаптер RecyclerView должен применять несколько типов просмотров: представления, которые должны отображаться как элемент списка; вид, который должен отображаться как нижний колонтитул; пустой вид. Вы можете проверить, как разместить элементы с разными представлениями в RecyclerView здесь: fooobar.com/questions/12073/... Найдите пустой вид между вашим основным списком и представлением нижнего колонтитула. Затем в onBindViewHolder для пустого представления проверьте, все ли просмотры вашего основного списка и нижнего колонтитула. Если да - установите пустую высоту представления в ноль, в противном случае установите ее на высоту, которая, как представляется, не будет выполняться элементами и нижним колонтитулом. Все это. Вы также можете динамически обновлять эту высоту, когда вы удаляете/добавляете строки. Просто вызывается notifyItemChanged для вашего пустого элемента пространства после обновления списка.

Вы также установили бы высоту RecyclerView на match_parent или точную высоту, а не wrap_content!

Надеюсь, что это поможет.

Ответ 5

Импровизация на Дмитрия Коробейникова и решение проблемы вызова уведомления о наборе данных изменились

public class StickyFooterItemDecoration extends RecyclerView.ItemDecoration {

  @Override
  public void getItemOffsets(Rect outRect, final View view, final RecyclerView parent,
      RecyclerView.State state) {

    int position = parent.getChildAdapterPosition(view);
    int adapterItemCount = parent.getAdapter().getItemCount();
    if (adapterItemCount == RecyclerView.NO_POSITION || (adapterItemCount - 1) != position) {
      return;
    }
    outRect.top = calculateTopOffset(parent, view, adapterItemCount);
  }


  private int calculateTopOffset(RecyclerView parent, View footerView, int itemCount) {
    int topOffset =
        parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom()
            - visibleChildHeightWithFooter(parent, footerView, itemCount);
    return topOffset < 0 ? 0 : topOffset;
  }



  private int visibleChildHeightWithFooter(RecyclerView parent, View footerView, int itemCount) {
    int totalHeight = 0;
    int onScreenItemCount = Math.min(parent.getChildCount(), itemCount);
    for (int i = 0; i < onScreenItemCount - 1; i++) {
      RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) parent.getChildAt(i)
          .getLayoutParams();
      int height =
          parent.getChildAt(i).getHeight() + layoutParams.topMargin
              + layoutParams.bottomMargin;
      totalHeight += height;
    }
    int footerHeight = footerView.getHeight();
    if (footerHeight == 0) {
      fixLayoutSize(footerView, parent);
      footerHeight = footerView.getHeight();
    }
    footerHeight = footerHeight + footerView.getPaddingBottom() + footerView.getPaddingTop();

    return totalHeight + footerHeight;
  }

  private void fixLayoutSize(View view, ViewGroup parent) {
    // Check if the view has a layout parameter and if it does not create one for it
    if (view.getLayoutParams() == null) {
      view.setLayoutParams(new ViewGroup.LayoutParams(
          ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
    }

    // Create a width and height spec using the parent as an example:
    // For width we make sure that the item matches exactly what it measures from the parent.
    //  IE if layout says to match_parent it will be exactly parent.getWidth()
    int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
    // For the height we are going to create a spec that says it doesn't really care what is calculated,
    //  even if its larger than the screen
    int heightSpec = View.MeasureSpec
        .makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);

    // Get the child specs using the parent spec and the padding the parent has
    int childWidth = ViewGroup.getChildMeasureSpec(widthSpec,
        parent.getPaddingLeft() + parent.getPaddingRight(), view.getLayoutParams().width);
    int childHeight = ViewGroup.getChildMeasureSpec(heightSpec,
        parent.getPaddingTop() + parent.getPaddingBottom(), view.getLayoutParams().height);

    // Finally we measure the sizes with the actual view which does margin and padding changes to the sizes calculated
    view.measure(childWidth, childHeight);

    // And now we setup the layout for the view to ensure it has the correct sizes.
    view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
  }
}