RecyclerView no se desplaza como se esperaba

Tengo un proyecto en el que utilizo una vista de reciclador horizontal y quiero centrar un elemento. Mi implementación funciona, pero no en todos los casos verifique este GIF aquí:

Como puede notar, se desplaza correctamente si vengo de la izquierda. Si vengo de la derecha que overscrolls mucho y no tengo ni idea de cómo parar ni cómo arreglar eso.

He rayado mi código a este ejemplo aquí:

public class DemoActivity extends ActionBarActivity implements View.OnClickListener { private static final int JUMP_TO_LEFT = MyAdapter.NON_VISIBLE_ITEMS + MyAdapter.VISIBLE_ITEMS - 1; private static final int JUMP_TO_RIGHT = MyAdapter.NON_VISIBLE_ITEMS; private LinearLayoutManager mLayoutManager; private RecyclerView mRecycler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); findViewById(android.R.id.button1).setOnClickListener(this); mRecycler = (RecyclerView)findViewById(R.id.recycler); MyAdapter mAdapter = new MyAdapter(); mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); mRecycler.setLayoutManager(mLayoutManager); mRecycler.setHasFixedSize(true); mRecycler.scrollToPosition(MyAdapter.NON_VISIBLE_ITEMS); mRecycler.setAdapter(mAdapter); } @Override public void onClick(View v) { int pos = mLayoutManager.findFirstVisibleItemPosition(); int outer = (MyAdapter.VISIBLE_ITEMS - 1) / 2; if(pos + outer >= MyAdapter.ITEM_IN_CENTER) { mRecycler.smoothScrollToPosition(JUMP_TO_RIGHT); } else { mRecycler.smoothScrollToPosition(JUMP_TO_LEFT); } } } 

Y aquí está mi adaptador:

 public class MyAdapter extends RecyclerView.Adapter<MyAdapter.Holder> implements View.OnClickListener { public static final int VISIBLE_ITEMS = 7; public static final int NON_VISIBLE_ITEMS = 150; private static final int TOTAL_ITEMS = VISIBLE_ITEMS + NON_VISIBLE_ITEMS * 2; public static final int ITEM_IN_CENTER = (int)Math.ceil(VISIBLE_ITEMS / 2f) + NON_VISIBLE_ITEMS; private Calendar mCalendar; public MyAdapter() { mCalendar = GregorianCalendar.getInstance(); setHasStableIds(true); } private int getToday() { return (int)TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis()); } @Override public int getItemCount() { return TOTAL_ITEMS; } @Override public Holder onCreateViewHolder(ViewGroup parent, int viewType) { final TextView tv = new TextView(parent.getContext()); int width = parent.getWidth() / VISIBLE_ITEMS; tv.setLayoutParams(new TableRow.LayoutParams(width, ViewGroup.LayoutParams.MATCH_PARENT, 1)); tv.setGravity(Gravity.CENTER); tv.setBackgroundColor(Color.TRANSPARENT); DisplayMetrics metrics = tv.getContext().getResources().getDisplayMetrics(); float padding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, metrics); tv.setLineSpacing(padding, 1f); tv.setPadding(0, (int)padding, 0, 0); tv.setOnClickListener(this); return new Holder(tv); } @Override public void onBindViewHolder(Holder holder, int position) { int today = getToday(); mCalendar.setTimeInMillis(System.currentTimeMillis()); mCalendar.set(Calendar.HOUR_OF_DAY, 12); // set to noon to avoid energy saver time problems mCalendar.add(Calendar.DAY_OF_YEAR, position - ITEM_IN_CENTER + 1); DateFormat format = new SimpleDateFormat("E\nd"); String label = format.format(mCalendar.getTime()).replace(".\n", "\n"); int day = (int)TimeUnit.MILLISECONDS.toDays(mCalendar.getTimeInMillis()); holder.update(day, today, label); } @Override public long getItemId(int position) { mCalendar.setTimeInMillis(System.currentTimeMillis()); mCalendar.set(Calendar.HOUR_OF_DAY, 12); // set to noon to avoid energy saver time problems mCalendar.add(Calendar.DAY_OF_YEAR, position - ITEM_IN_CENTER + 1); DateFormat format = new SimpleDateFormat("dMMyyyy"); return Long.parseLong(format.format(mCalendar.getTime())); } @Override public void onClick(View v) { String day = ((TextView)v).getText().toString().replace("\n", " "); Toast.makeText(v.getContext(), "You clicked on " + day, Toast.LENGTH_SHORT).show(); } public class Holder extends RecyclerView.ViewHolder { private final Typeface font; private Holder(TextView v) { super(v); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { font = Typeface.create("sans-serif-light", Typeface.NORMAL); } else { font = null; } } public void update(int day, int today, String label) { TextView tv = (TextView)itemView; tv.setText(label); if(day == today) { tv.setTextSize(18); tv.setTypeface(null, Typeface.BOLD); } else { tv.setTextSize(16); tv.setTypeface(font, Typeface.NORMAL); } tv.setBackgroundColor(0xff8dc380); } } } 

¿Ves una razón para eso? Para que sea más sencillo para usted, también pongo este código en GitHub. Https://github.com/rekire/RecylcerViewBug

He encontrado una solución simple y sorprendente:

 @Override public void onClick(View v) { int pos = mLayoutManager.findFirstVisibleItemPosition(); int outer = (MyAdapter.VISIBLE_ITEMS + 1) / 2; int delta = pos + outer - ForecastAdapter.ITEM_IN_CENTER; //Log.d("Scroll", "delta=" + delta); View firstChild = mForecast.getChildAt(0); if(firstChild != null) { mForecast.smoothScrollBy(firstChild.getWidth() * -delta, 0); } } 

Aquí calculo la anchura para saltar a mí mismo, que hace exactamente lo que quiero.

En el caso de LinearLayoutManager con orientación vertical, puede crear su propio SmoothScroller y sustituir el método calculateDyToMakeVisible () donde puede establecer la posición de vista deseada. Por ejemplo, para que la vista de destino siempre aparezca en la parte superior de RecyclerView después de smoothScroll () escriba esto:

 class CustomLinearSmoothScroller extends LinearSmoothScroller { public CustomLinearSmoothScroller(Context context) { super(context); } @Override public int calculateDyToMakeVisible(View view, int snapPreference) { final RecyclerView.LayoutManager layoutManager = getLayoutManager(); if (!layoutManager.canScrollVertically()) { return 0; } final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)view.getLayoutParams(); final int top = layoutManager.getDecoratedTop(view) - params.topMargin; final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin; final int viewHeight = bottom - top; final int start = layoutManager.getPaddingTop(); final int end = start + viewHeight; return calculateDtToFit(top, bottom, start, end, snapPreference); } 

"Top" y "bottom" – límites de la vista de destino

"Start" y "end" – puntos entre los que se debe colocar la vista durante el smoothScroll

Para soportar el desplazamiento suave, debe reemplazar smoothScrollToPosition (RecyclerView, State, int) y crear un RecyclerView.SmoothScroller.

RecyclerView.LayoutManager es responsable de crear la acción de desplazamiento real. Si desea proporcionar una lógica de desplazamiento suave personalizada, reemplace smoothScrollToPosition (RecyclerView, State, int) en su LayoutManager.

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#smoothScrollToPosition(int)

En su caso, use smoothScrollBy podría ser una solución (no necesita esta anulación).

Esto funcionó para mí:

 itemsView.smoothScrollBy(-recyclerView.computeHorizontalScrollOffset(), 0) 
FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.