Search code examples
androidscrollandroid-recyclerviewlinearlayoutmanager

Scroll in recyclerview scrolls to bottom of view instead of top


I'm using a RecyclerView that displays pages from a book. I have an EditText box that takes you to a page in the book. The only problem is that when it scrolls to the page of the book sometimes it will scroll to the top of the page and sometimes to the bottom. I think this has something to do with the RecyclerView and it not loading the view for the page before i scroll to it. I want it to always scroll to the top of a page but I'm not sure of the best way to go about it.

Here is the relevant code

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private RecyclerView.Adapter mAdapter;
    private RecyclerView.LayoutManager mLayoutManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        EditText editText = (EditText) findViewById(R.id.page);
        editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                boolean handled = false;
                if (actionId == EditorInfo.IME_ACTION_GO) {
                    int p = Integer.valueOf(v.getText().toString()) - 1;
                    scrollToPage(p);
                    handled = true;
                }
                return handled;
            }
        });

        mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);

        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        mRecyclerView.setHasFixedSize(true);

        // use a linear layout manager
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);

        // specify an adapter (see also next example)
        mAdapter = new MyAdapter(pages);
        mRecyclerView.setAdapter(mAdapter);

    }

    //*******************RECYCLERVIE***************************************8//
    public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{
        private String[] mDataset;
        // Provide a reference to the views for each data item
        // Complex data items may need more than one view per item, and
        // you provide access to all the views for a data item in a view holder
        public class ViewHolder extends RecyclerView.ViewHolder {
            // each data item is just a string in this case
            public TextView mTextView;
            public TextView pageNumber;
            public ViewHolder(View v) {
                super(v);
                mTextView = (TextView) v.findViewById(R.id.info_text);
                pageNumber = (TextView) v.findViewById(R.id.page_number);
            }
        }

        // Provide a suitable constructor (depends on the kind of dataset)
        public MyAdapter(String[] myDataset) {
            //my Dataset is an array with 4 members making up the 4 pages//
            mDataset = myDataset;
        }

        // Create new views (invoked by the layout manager)
        @Override
        public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                       int viewType) {
            // create a new view
            View v = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.cards, parent, false);
            // set the view's size, margins, paddings and layout parameters
            ViewHolder vh = new ViewHolder(v);
            return vh;
        }

        // Replace the contents of a view (invoked by the layout manager)
        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            // - get element from your dataset at this position
            // - replace the contents of the view with that element
            holder.mTextView.setText(mDataset[position]);
            int page = position + 1;
            holder.pageNumber.setText("" + page);

        }

        // Return the size of your dataset (invoked by the layout manager)
        @Override
        public int getItemCount() {
            return mDataset.length;
        }
    }


    //******SCROLL TO TOP BUTTON************//
    public void scrollToTop(View v){
        mLayoutManager.scrollToPosition(0);
    }

    //*******************SCROLL TO SELECTED PAGE***********//
    public void scrollToPage(int p){
        mLayoutManager.scrollToPosition(p);
    }


}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- A RecyclerView with some commonly used attributes -->
    <android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_alignParentBottom="true">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="GO TO TOP"
            android:onClick="scrollToTop"
            android:layout_gravity="bottom|left"/>

        <EditText
            android:id="@+id/page"
            android:layout_width="50dp"
            android:layout_height="20dp"
            android:layout_gravity="bottom|center_horizontal"
            android:imeOptions="actionGo"
            android:inputType="number"
            android:background="@color/cardview_light_background"/>

    </LinearLayout>
</RelativeLayout>

cards.xml

<?xml version="1.0" encoding="utf-8"?>

<!-- A CardView that contains a TextView -->
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:id="@+id/card_view"
    android:layout_width="300dp"
    android:layout_height="500dp"
    android:layout_gravity="center"
    card_view:cardCornerRadius="4dp">

    <TextView
        android:id="@+id/info_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/frayedpaper"
        android:text="@string/body" />

    <TextView
        android:id="@+id/page_number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right"
        android:text="page"/>

</android.support.v7.widget.CardView>

Solution

  • I've run into this problem myself before, and I found that calling LayoutManager.scrollToPositionWithOffset(position, 1) instead of simply LayoutManager.scrollToPosition(position) solved it for me.

    Unfortunately, I don't really know why this works. It seems that the one-pixel offset changes the layout manager's calculations somehow; calling scrollToPositionWithOffset(position, 0) didn't work for me. Fortunately, over-scrolling by one pixel wasn't noticeable in my app.