desplegable - Android L: desplazamiento rápido para RecyclerView
menu recyclerview (5)
RecyclerView
utilizar RecyclerView
en mi aplicación con muchos datos y me gustaría crear un desplazamiento rápido para él, al igual que para ListView
. El enfoque de esta respuesta funcionó para mí con ListView
, pero no funciona para RecyclerView
. Incluso si configuré 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" />
¿ RecyclerView
compatible con el desplazamiento rápido en Android L? No se puede encontrar nada sobre esto en la documentación.
He hecho uno para mí mismo 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 desplazarse también por la vista de reciclaje.
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;
}}
Esta es solo una solución rápida que modifiqué para probarla. Parece que funciona para mí, con una lista de 3100 artículos. Cuando configura los artículos en su adaptador, necesita calcular el encabezado de su sección y la posición. En mi caso, repito a través de mi lista preordenada y tomo la posición del primer ítem para cada personaje y lo pongo en una lista y lo paso al vacío público notifyChanges (List sectionName, List sectionPosition). Espero eso ayude.
Otra buena solución que he encontrado se describe en un buen artículo de Mark Allison sobre cómo implementar su propio desplazamiento rápido personalizado para RecyclerView
. Vale la pena echarle un vistazo.
He creado una biblioteca para hacer frente a este problema. Hay bastantes opciones de personalización ahora.
Hay una biblioteca aquí ("RecyclerViewFastScroller"), que podría ser de alguna ayuda.
Alguien lo publicó de una publicación similar que he escrito, aquí
Lo único que encontrará en RecyclerView es la implementación básica de la lógica de reciclaje. Es el polo opuesto completo de ListView en cuanto a que le ofrece la máxima personalización (puede lograr cualquier diseño único que desee a diferencia de ListView), pero casi no tiene nada integrado (a diferencia de ListView que tiene numerosas características como el rápido scroll thumb) .
Si desea agregar algo como la función de desplazamiento rápido, tendrá que desarrollarlo por su cuenta por el momento.
Nuevo indicador booleano fastScrollEnabled para RecyclerView. Si está habilitado, se deben configurar fastScrollHorizontalThumbDrawable, fastScrollHorizontalTrackDrawable, fastScrollVerticalThumbDrawable, y fastScrollVerticalTrackDrawable. Ahora disponible en la Biblioteca de soporte 26.0.0