Android L: Desplazamiento rápido para el RecyclerView

Estoy tratando de usar RecyclerView en mi aplicación con un montón de datos en él y me gustaría hacer un desplazamiento rápido para él, al igual que para el ListView . El enfoque de esta respuesta funcionó para mí con ListView , pero no funciona para el RecyclerView . Incluso si configuro el desplazamiento rápido a true en el diseño de RecyclerView , todavía no funciona:

  <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" /> 

¿El RecyclerView admite el desplazamiento rápido en Android L? No se puede encontrar nada sobre esto en la documentación.

Lo único que encontrará en RecyclerView es la implementación básica de la lógica del reciclaje. Es el polo opuesto completo de ListView en que le ofrece la máxima personalización (puede lograr cualquier diseño único que desee a diferencia de ListView), pero no tiene casi nada construido con él (a diferencia de ListView que tiene numerosas características como el pulgar de desplazamiento rápido) .

Si desea agregar algo como la función de desplazamiento rápido, tendrá que desarrollarlo por su cuenta por ahora.

Nuevo indicador booleano fastScrollEnabled para RecyclerView. Si está activado, se debe establecer fastScrollHorizontalThumbDrawable, fastScrollHorizontalTrackDrawable, fastScrollVerticalThumbDrawable y fastScrollVerticalTrackDrawable. Ahora disponible en Support Library 26.0.0

He hecho uno para mí usando https://github.com/woozzu/IndexableListView/tree/master/src/com/woozzu/android/widget

Y cambiar el ListView a 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) { } } 

Esta clase dibuja los indexadores a un lado y le permite desplazarse sobre ellos y desplazar la recylerview también.

 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; }} 

Esto es sólo una solución rápida que he modificado para probarlo. Parece que funciona para mí, con una lista de 3100 elementos. Cuando establece los elementos en el adaptador, debe calcular el encabezado de sección y la posición. En mi caso i iterar a través de mi lista pre-ordenados y tomar la posición del primer elemento para cada carácter y poner en una lista y pasar al público void notifyChanges (List sectionName, List sectionPosition). Espero eso ayude.

Una más buena solución que he encontrado se describe en un buen artículo de Mark Allison acerca de cómo implementar su propio desplazamiento rápido personalizado para RecyclerView . Vale la pena comprobarlo.

Hay una biblioteca aquí ("RecyclerViewFastScroller"), que podría ser de alguna ayuda.

Alguien lo publicó de un post similar que he escrito, aquí

He hecho una biblioteca para hacer frente a este problema. Hay bastantes opciones de personalización ahora.

  • Uso de RecyclerView con tarjeta
  • RecyclerView y DiffUtil - Una pesadilla de concurrencia
  • Capaz de hacer clic en dos elementos al mismo tiempo en un RecyclerView
  • Incrustación de anuncios en Recyclerview
  • Descripción de RecyclerView.ViewHolder
  • Multiple RecyclerView en un fragmento
  • Cómo puedo crear un diseño nestedScroll como este?
  • Android: RecyclerView dentro de un ScrollView
  • RecyclerView con LinearLayoutManager con vista de encabezado que no recicla
  • RecyclerView notifyItemInserted () La animación no se muestra cuando la posición es 0 pero funciona bien con otra posición
  • Inicio de DialogFragment desde una clase que se extiende RecyclerView.ViewHolder
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.