I have a project where I use a horizontal recycler view and I want to center one element. My implementation works, but not in every case check this GIF here:
As you may note it scrolls correctly if I come from the left. If I come from the right it overscrolls a lot and I have no idea how to stop nor how to fix that.
I striped my code to this example here:
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); } } }
And here is my adapter:
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); } } }
Do you see a reason for that? To make it simpler for you I also put this code on GitHub. https://github.com/rekire/RecylcerViewBug
I found a surprising simple workaround:
@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); } }
Here I calculate the width to jump myself, that does exactly what I want.
In case of LinearLayoutManager with vertical orientation you can create own SmoothScroller and override calculateDyToMakeVisible() method where you can set desired view position. For example, to make the target view always to be appeared at the top side of RecyclerView after smoothScroll() write this:
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" and "bottom" - bounds of the target view
"start" and "end" - points between which the view should be placed during smoothScroll
To support smooth scrolling, you must override smoothScrollToPosition(RecyclerView, State, int) and create a RecyclerView.SmoothScroller.
RecyclerView.LayoutManager is responsible for creating the actual scroll action. If you want to provide a custom smooth scroll logic, override smoothScrollToPosition(RecyclerView, State, int) in your LayoutManager.
In your case, use smoothScrollBy could be a workaround (doesn't need this override).
This worked for me:
itemsView.smoothScrollBy(-recyclerView.computeHorizontalScrollOffset(), 0)
来源:https://stackoverflow.com/questions/25684167/recyclerview-does-not-scroll-as-expected