RecyclerView does not scroll as expected

30,377

Solution 1

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.

Solution 2

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

Solution 3

This worked for me:

itemsView.smoothScrollBy(-recyclerView.computeHorizontalScrollOffset(), 0)
Share:
30,377
rekire
Author by

rekire

Check also my gists and projects on GitHub. Voice related Apps: REWE✝ (Google, Amazon) CentralStation CRM (Google (DE), Google (US), Amazon (DE), Amazon (US)) List of my Apps or Apps I worked on: snabble by snabble CentralPlanner by 42he (flutter) gooods by snabble toom by snabble CentralStation CRM by 42he Zykluskalender✝ by NetMoms Hotel Search by HRS REWE Lieferservice, Supermarkt by REWE Digital BILLA Online Shop✝ by REWE Digital CentralStation CRM by 42he ✝ = Reached end of life Open source projects where I am involved: Author of Konversation intent-schema-generation provisioning cli kotlin-multi-platform Contributor of dialog dialogflow alexa kotlin Author of LazyWorker android tool Contributor of Futter flutter You want to hire me? Check my careers profile.

Updated on December 14, 2020

Comments

  • rekire
    rekire over 3 years

    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