ventajas usar studio son recyclerview qué los libreria elementos cuando construyen android android-animation smooth-scrolling android-recyclerview

android - usar - RecyclerView no se desplaza como se esperaba



son los elementos qué construyen un recyclerview (4)

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

Como puede observar, se desplaza correctamente si vengo de la izquierda. Si vengo de la derecha, se desplaza demasiado y no tengo idea de cómo parar ni de cómo solucionarlo.

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 hacerlo más simple para ti, también puse este código en GitHub. https://github.com/rekire/RecylcerViewBug


Para admitir un desplazamiento suave, debe anular 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, anule smoothScrollToPosition (RecyclerView, State, int) en su LayoutManager.

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

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


En el caso de LinearLayoutManager con orientación vertical, puede crear su propio SmoothScroller y anular el método calculaDyToMakeVisible () donde puede establecer la posición de vista deseada. Por ejemplo, para hacer que la vista de destino aparezca siempre 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); }

"arriba" y "abajo" - límites de la vista de destino

"Inicio" y "Fin": puntos entre los que se debe colocar la vista durante el desplazamiento suave


Encontré 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 el ancho para saltar yo mismo, que hace exactamente lo que quiero.


Esto funcionó para mí:

itemsView.smoothScrollBy(-recyclerView.computeHorizontalScrollOffset(), 0)