Вставка элементов RecyclerView в нулевую позицию - всегда оставаться прокрученными вверх - PullRequest
0 голосов
/ 11 мая 2018

У меня довольно стандартный RecyclerView с вертикальной LinearLayoutManager. Я продолжаю вставлять новые элементы сверху и звоню notifyItemInserted(0).

Я хочу, чтобы список оставался прокручиваемым до верха ; всегда отображать 0-ю позицию.

С моей точки зрения, LayoutManager ведет себя по-разному в зависимости от количества предметов.

Хотя все элементы помещаются на экране, они выглядят и ведут себя так, как я ожидал: новый элемент всегда появляется сверху и сдвигает все под ним.

Behavior with few items: Addition shifts other items down, first (newest) item is always visible

Однако, как только нет. элементов превышает границы RecyclerView, новые элементы добавляются выше видимого в данный момент, но видимые элементы остаются в поле зрения . Пользователь должен прокрутить, чтобы увидеть новый элемент.

Behavior with many items: New items are added above the top boundary, user has to scroll to reveal them

Это поведение вполне понятно и хорошо для многих приложений, но не для «живого потока», где видеть самую последнюю вещь важнее, чем «не отвлекать» пользователя с помощью автопрокрутки.


Я знаю, что этот вопрос является почти дубликатом Добавление нового элемента в верхнюю часть RecyclerView ... но все предлагаемые ответы - просто обходные пути (большинство из них довольно хорошо, правда).

Я ищу способ на самом деле изменить это поведение . Я хочу, чтобы LayoutManager действовал одинаково, независимо от количества элементов . Я хочу, чтобы он всегда сдвигал все элементы (так же, как это происходит с первыми несколькими дополнениями), чтобы не останавливать смещение элементов в какой-то момент, и компенсировать это путем плавной прокрутки списка вверх.

В основном, нет smoothScrollToPosition, нет RecyclerView.SmoothScroller. Подклассы LinearLayoutManager это хорошо. Я уже копаюсь в его коде, но пока безуспешно, поэтому решил спросить, не сталкивался ли кто-нибудь с этим. Спасибо за любые идеи!


РЕДАКТИРОВАТЬ: Чтобы прояснить, почему я отклоняю ответы на связанный вопрос: В основном меня беспокоит плавность анимации.

Обратите внимание, что в первом GIF-файле ItemAnimator перемещает другие элементы при добавлении нового, анимации постепенного появления и перемещения имеют одинаковую длительность. Но когда я «перемещаю» предметы с помощью плавной прокрутки, я не может легко контролировать скорость прокрутки . Даже с длительностью по умолчанию ItemAnimator это выглядит не так хорошо, но в моем конкретном случае мне даже нужно было уменьшить длительность ItemAnimator, что делает его еще хуже:

Insert

Ответы [ 2 ]

0 голосов
/ 30 сентября 2018

Используйте adapter.notifyDataSetChanged() вместо adater.notifyItemInserted(0). Это приведет к прокрутке recylerView в нулевую позицию, если текущая позиция прокрутки равна единице (old zero).

0 голосов
/ 12 мая 2018

Когда элемент добавляется в верхнюю часть RecyclerView и элемент может помещаться на экране, элемент прикрепляется к держателю вида, и RecyclerView проходит фазу анимации для перемещения элементов вниз, чтобы отобразить новый элемент.вверху.

Если новый элемент нельзя отобразить без прокрутки, держатель вида не создается, поэтому анимировать нечего.Единственный способ вывести новый элемент на экран, когда это происходит, - это прокрутка, которая приводит к созданию держателя вида, так что вид может быть размещен на экране.(По-видимому, существует крайний случай, когда представление частично отображается и создается держатель представления, но я игнорирую этот конкретный экземпляр, поскольку он не уместен.)

Итак, проблема в том, что два разныхдействия, анимация добавленного вида и прокрутка добавленного вида должны быть сделаны так, чтобы они выглядели одинаково для пользователя.Мы могли бы погрузиться в базовый код и точно выяснить, что происходит с точки зрения создания держателя представления, синхронизации анимации и т. Д. Но даже если мы сможем дублировать действия, он может сломаться, если базовый код изменится.Это то, чему вы сопротивляетесь.

Альтернативой является добавление заголовка в нулевую позицию RecyclerView.Вы всегда будете видеть анимацию при отображении этого заголовка и добавлении новых элементов в позицию 1. Если вам не нужен заголовок, вы можете сделать его нулевой высотой, и он не будет отображаться.Следующее видео демонстрирует эту технику:

[video]

Это код для демонстрации.Он просто добавляет фиктивную запись в позиции 0 предметов.Если фиктивная запись вам не по вкусу, есть другие способы подойти к этому.Вы можете искать способы добавления заголовков к RecyclerView.

(Если вы используете полосу прокрутки, она будет работать неправильно, как вы, вероятно, можете сказать из демо. Чтобы исправить это на 100%, вы будетеприходится принимать на себя большую часть вычислений высоты и размещения полосы прокрутки. Пользовательский computeVerticalScrollOffset() для LinearLayoutManager заботится о размещении полосы прокрутки вверху, когда это необходимо. (Код был введен после снятия видео.) Однако полоса прокруткискачки при прокрутке вниз. Эту проблему можно решить с помощью лучшего вычисления размещения. См. этот вопрос переполнения стека для получения дополнительной информации о полосах прокрутки в контексте элементов различной высоты.)

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private TheAdapter mAdapter;
    private final ArrayList<String> mItems = new ArrayList<>();
    private int mItemCount = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    LinearLayoutManager layoutManager =
        new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) {
            @Override
            public int computeVerticalScrollOffset(RecyclerView.State state) {
                if (findFirstCompletelyVisibleItemPosition() == 0) {
                    // Force scrollbar to top of range. When scrolling down, the scrollbar
                    // will jump since RecyclerView seems to assume the same height for
                    // all items.
                    return 0;
                } else {
                    return super.computeVerticalScrollOffset(state);
                }
            }
        };
        recyclerView.setLayoutManager(layoutManager);

        for (mItemCount = 0; mItemCount < 6; mItemCount++) {
            mItems.add(0, "Item # " + mItemCount);
        }

        // Create a dummy entry that is just a placeholder.
        mItems.add(0, "Dummy item that won't display");
        mAdapter = new TheAdapter(mItems);
        recyclerView.setAdapter(mAdapter);
    }

    @Override
    public void onClick(View view) {
        // Always at to position #1 to let animation occur.
        mItems.add(1, "Item # " + mItemCount++);
        mAdapter.notifyItemInserted(1);
    }
}

TheAdapter.java

class TheAdapter extends RecyclerView.Adapter<TheAdapter.ItemHolder> {
    private ArrayList<String> mData;

    public TheAdapter(ArrayList<String> data) {
        mData = data;
    }

    @Override
    public ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view;

        if (viewType == 0) {
            // Create a zero-height view that will sit at the top of the RecyclerView to force
            // animations when items are added below it.
            view = new Space(parent.getContext());
            view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0));
        } else {
            view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.list_item, parent, false);
        }
        return new ItemHolder(view);
    }

    @Override
    public void onBindViewHolder(final ItemHolder holder, int position) {
        if (position == 0) {
            return;
        }
        holder.mTextView.setText(mData.get(position));
    }

    @Override
    public int getItemViewType(int position) {
        return (position == 0) ? 0 : 1;
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }

    public static class ItemHolder extends RecyclerView.ViewHolder {
        private TextView mTextView;

        public ItemHolder(View itemView) {
            super(itemView);
            mTextView = (TextView) itemView.findViewById(R.id.textView);
        }
    }
}

activity_main.xml

<android.support.constraint.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:scrollbars="vertical"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:text="Button"
        android:onClick="onClick"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>

list_item.xml

<LinearLayout 
    android:id="@+id/list_item"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    android:orientation="horizontal">

    <View
        android:id="@+id/box"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginStart="16dp"
        android:background="@android:color/holo_green_light"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@id/box"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="TextView" />

</LinearLayout>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...