Android L: быстрый прокрутка для RecyclerView

Я пытаюсь использовать RecyclerView в своем приложении с большим количеством данных в нем и хотел бы сделать быстрый прокрутка для него, как и для ListView . Подход от этого ответа работал для меня с ListView , но не работает для RecyclerView . Даже если я установил быструю прокрутку в true в макете RecyclerView , она все равно не работает:

  <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:scrollbars="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:fastScrollEnabled="true" android:fastScrollAlwaysVisible="true" /> 

Поддерживает ли RecyclerView быстрый прокрутка в Android L? Не могу найти ничего об этом в документации.

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

Если вы хотите добавить что-то вроде функции быстрого прокрутки, вам нужно будет разработать ее самостоятельно.

Новый булевский флаг fastScrollEnabled для RecyclerView. Если включено, необходимо установить fastScrollHorizontalThumbDrawable, fastScrollHorizontalTrackDrawable, fastScrollVerticalThumbDrawable и fastScrollVerticalTrackDrawable. Теперь доступно в Библиотеке поддержки 26.0.0

Я сделал один для себя, используя https://github.com/woozzu/IndexableListView/tree/master/src/com/woozzu/android/widget

И изменение ListView на RecylerView

 public class IndexableRecylerView extends RecyclerView implements RecyclerView.OnItemTouchListener{ public IndexScroller mScroller = null; public IndexableRecylerView(Context context) { super(context); init(); } public IndexableRecylerView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public IndexableRecylerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } public void init() { addOnItemTouchListener(this); } public void setFastScrollEnabled(boolean enable) { if (enable) { if (mScroller == null) mScroller = new IndexScroller(getContext(), this); } else { if (mScroller != null) { mScroller.hide(); mScroller = null; } } } @Override public void draw(Canvas canvas) { super.draw(canvas); // Overlay index bar if (mScroller != null) mScroller.draw(canvas); } @Override public boolean onTouchEvent(MotionEvent ev) { if (mScroller != null) mScroller.show(); // Intercept ListView's touch event if (mScroller != null && mScroller.onTouchEvent(ev)) return true; return super.onTouchEvent(ev); } public void setIndexAdapter(List<String> sectionName, List<Integer> sectionPosition) { if (mScroller != null) mScroller.notifyChanges(sectionName, sectionPosition); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mScroller != null) mScroller.onSizeChanged(w, h, oldw, oldh); } @Override public void stopScroll() { try { super.stopScroll(); } catch( NullPointerException exception ) { Log.i("RecyclerView", "NPE caught in stopScroll"); } } @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { if (mScroller != null && mScroller.contains(e.getX(), e.getY())) { mScroller.show(); return true; }else{ return false; } } @Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { } } 

Этот класс рисует указатели сбоку и позволяет вам прокручивать их и прокручивать recylerview тоже ..

 public class IndexScroller { private float mIndexbarWidth; private float mIndexbarMargin; private float mPreviewPadding; private float mDensity; private float mScaledDensity; private float mAlphaRate; private int mState = STATE_HIDDEN; private int mListViewWidth; private int mListViewHeight; private int mCurrentSection = -1; private boolean mIsIndexing = false; private RecyclerView recyclerView = null; public List<String> mSections = new ArrayList<>(); public List<Integer> mSectionPosition = new ArrayList<>(); private RectF mIndexbarRect; private static final int STATE_HIDDEN = 0; private static final int STATE_SHOWING = 1; private static final int STATE_SHOWN = 2; private static final int STATE_HIDING = 3; public IndexScroller(Context context, RecyclerView lv) { mDensity = context.getResources().getDisplayMetrics().density; mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity; recyclerView = lv; mIndexbarWidth = 20 * mDensity; mIndexbarMargin = 10 * mDensity; mPreviewPadding = 5 * mDensity; } public void draw(Canvas canvas) { if (mState == STATE_HIDDEN) return; // mAlphaRate determines the rate of opacity Paint indexbarPaint = new Paint(); indexbarPaint.setColor(Color.BLACK); indexbarPaint.setAlpha((int) (64 * mAlphaRate)); indexbarPaint.setAntiAlias(true); canvas.drawRoundRect(mIndexbarRect, 5 * mDensity, 5 * mDensity, indexbarPaint); if (mSections != null && mSections.size() > 0) { // Preview is shown when mCurrentSection is set if (mCurrentSection >= 0) { Paint previewPaint = new Paint(); previewPaint.setColor(Color.BLACK); previewPaint.setAlpha(96); previewPaint.setAntiAlias(true); previewPaint.setShadowLayer(3, 0, 0, Color.argb(64, 0, 0, 0)); Paint previewTextPaint = new Paint(); previewTextPaint.setColor(Color.WHITE); previewTextPaint.setAntiAlias(true); previewTextPaint.setTextSize(50 * mScaledDensity); float previewTextWidth = previewTextPaint.measureText(mSections.get(mCurrentSection)); float previewSize = 2 * mPreviewPadding + previewTextPaint.descent() - previewTextPaint.ascent(); RectF previewRect = new RectF((mListViewWidth - previewSize) / 2 , (mListViewHeight - previewSize) / 2 , (mListViewWidth - previewSize) / 2 + previewSize , (mListViewHeight - previewSize) / 2 + previewSize); canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint); canvas.drawText(mSections.get(mCurrentSection), previewRect.left + (previewSize - previewTextWidth) / 2 - 1 , previewRect.top + mPreviewPadding - previewTextPaint.ascent() + 1, previewTextPaint); } Paint indexPaint = new Paint(); indexPaint.setColor(Color.WHITE); indexPaint.setAlpha((int) (255 * mAlphaRate)); indexPaint.setAntiAlias(true); indexPaint.setTextSize(12 * mScaledDensity); float sectionHeight = (mIndexbarRect.height() - 2 * mIndexbarMargin) / mSections.size(); float paddingTop = (sectionHeight - (indexPaint.descent() - indexPaint.ascent())) / 2; for (int i = 0; i < mSections.size(); i++) { float paddingLeft = (mIndexbarWidth - indexPaint.measureText(mSections.get(i))) / 2; canvas.drawText(mSections.get(i), mIndexbarRect.left + paddingLeft , mIndexbarRect.top + mIndexbarMargin + sectionHeight * i + paddingTop - indexPaint.ascent(), indexPaint); } } } public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // If down event occurs inside index bar region, start indexing if (mState != STATE_HIDDEN && contains(ev.getX(), ev.getY())) { setState(STATE_SHOWN); // It demonstrates that the motion event started from index bar mIsIndexing = true; // Determine which section the point is in, and move the list to that section mCurrentSection = getSectionByPoint(ev.getY()); recyclerView.scrollToPosition(mSectionPosition.get(mCurrentSection)); return true; } break; case MotionEvent.ACTION_MOVE: if (mIsIndexing) { // If this event moves inside index bar if (contains(ev.getX(), ev.getY())) { // Determine which section the point is in, and move the list to that section mCurrentSection = getSectionByPoint(ev.getY()); recyclerView.scrollToPosition(mSectionPosition.get(mCurrentSection)); } return true; } break; case MotionEvent.ACTION_UP: if (mIsIndexing) { mIsIndexing = false; mCurrentSection = -1; } if (mState == STATE_SHOWN) { setState(STATE_HIDING); } break; } return false; } public void onSizeChanged(int w, int h, int oldw, int oldh) { mListViewWidth = w; mListViewHeight = h; mIndexbarRect = new RectF(w - mIndexbarMargin - mIndexbarWidth , mIndexbarMargin , w - mIndexbarMargin , h - mIndexbarMargin); } public void show() { if (mState == STATE_HIDDEN) setState(STATE_SHOWING); else if (mState == STATE_HIDING) setState(STATE_HIDING); } public void hide() { if (mState == STATE_SHOWN) setState(STATE_HIDING); } private void setState(int state) { if (state < STATE_HIDDEN || state > STATE_HIDING) return; mState = state; switch (mState) { case STATE_HIDDEN: // Cancel any fade effect mHandler.removeMessages(0); break; case STATE_SHOWING: // Start to fade in mAlphaRate = 0; fade(0); break; case STATE_SHOWN: // Cancel any fade effect mHandler.removeMessages(0); break; case STATE_HIDING: // Start to fade out after three seconds mAlphaRate = 1; fade(3000); break; } } public boolean contains(float x, float y) { // Determine if the point is in index bar region, which includes the right margin of the bar return (x >= mIndexbarRect.left && y >= mIndexbarRect.top && y <= mIndexbarRect.top + mIndexbarRect.height()); } private int getSectionByPoint(float y) { if (mSections == null || mSections.size() == 0) return 0; if (y < mIndexbarRect.top + mIndexbarMargin) return 0; if (y >= mIndexbarRect.top + mIndexbarRect.height() - mIndexbarMargin) return mSections.size() - 1; return (int) ((y - mIndexbarRect.top - mIndexbarMargin) / ((mIndexbarRect.height() - 2 * mIndexbarMargin) / mSections.size())); } private void fade(long delay) { mHandler.removeMessages(0); mHandler.sendEmptyMessageAtTime(0, SystemClock.uptimeMillis() + delay); } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (mState) { case STATE_SHOWING: // Fade in effect mAlphaRate += (1 - mAlphaRate) * 0.2; if (mAlphaRate > 0.9) { mAlphaRate = 1; setState(STATE_SHOWN); } recyclerView.invalidate(); fade(10); break; case STATE_SHOWN: // If no action, hide automatically setState(STATE_HIDING); break; case STATE_HIDING: // Fade out effect mAlphaRate -= mAlphaRate * 0.2; if (mAlphaRate < 0.1) { mAlphaRate = 0; setState(STATE_HIDDEN); } recyclerView.invalidate(); fade(10); break; } } }; public void notifyChanges(List<String> sectionName, List<Integer> sectionPosition) { // Pre-calculate and pass your section header and position mSections = sectionNames; mSectionPosition = sectionPosition; }} 

Это только быстрое исправление, которое я модифицировал, чтобы проверить его. Кажется, работает для меня со списком из 3100 предметов. Когда вы устанавливаете элементы на свой адаптер, вам нужно рассчитать заголовок раздела и позицию. В моем случае я повторяю свой предварительно отсортированный список и занимаю позицию первого элемента для каждого символа и помещаю его в список и передаю публике void notifyChanges (List sectionName, List sectionPosition). Надеюсь, поможет.

Еще одно хорошее решение, которое я нашел, описано в хорошей статье Марка Эллисона о том, как реализовать свой собственный быстрый прокрутка для RecyclerView . Стоит проверить это.

Здесь есть библиотека («RecyclerViewFastScroller»), которая может быть полезной.

Кто-то опубликовал его из аналогичного сообщения, которое я написал здесь.

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