Recently see QQ music lyrics every time after sliding can roll back to the middle position. Feel very magical, open the developer mode to display the layout, found that the lyrics part is not written by Android control, should be front-end written. So I thought, can I use recyclerView to do this auto-rollback to the middle?

Everything pays off. After searching for some information, I finally got it done.

Let me elaborate.

The target

Click on an item, and after four seconds of nothing, the item scrolls to the middle of the display. After clicking, the user slides, and the delay starts after the user does not operate. The user clicks several times, and the position of the last click is recorded.

Analysis of the

First of all, how does scrolling to a given location work?

. / / scroll to the specified location recyclerView scrollToPosition (position); . / / smooth scrolling to the specified location recyclerView smoothScrollToPosition (position);Copy the code

Did you scroll to the specified pixel position?

// The scrollBy(x, y) method controls the distance moved in pixels, so you need to calculate the height or width of the move yourself. recyclerView.scrollBy(x, y)Copy the code

The problem, though, is rolling to the middle? What about this? How about this?

mRecyclerView.scrollToPosition(0); MRecyclerView. ScrollBy (0400);Copy the code

First scroll to the specified position, in the scroll for a distance is not good? The run found that these two lines only execute the first line, and the second line is invalid.

Debug the implementation is too complex.

That means it’s not going to work. Is there any other way?

RecyclerView has a scrolllistening method:

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); }});Copy the code

The onScrollStateChanged method corresponds to three states: Static (SCROLL_STATE_IDLE), drag scrolling (SCROLL_STATE_DRAGGING), and sliding (SCROLL_STATE_SETTLING). OnScrollStateChanged (drag roller) –> (N) onScrollStateChanged(static);

OnScrollStateChanged (drag roller) –> (N) onScrollStateChanged (slide) –>

(n)onScrolled –> onScrollStateChanged I have an idea. When clicking, run scrollToPosition first, and run scrollBy method in onScrolled method. Write code, run it, pass it. So here’s the middle position.

First calculate the recylerView display height.

 Rect rect = new Rect();
 mRecyclerView.getGlobalVisibleRect(rect);
 reHeight = rect.bottom - rect.top - vHeight;
Copy the code

When running scrollToPosition, click the item will appear in the field of view, at this time, calculate the corresponding displacement. Note that the scrollToPosition method does not run when the click item is in view.

        int top = mRecyclerView.getChildAt(position - firstPosition).getTop();
int half = reHeight / 2;
        mRecyclerView.scrollBy(0, top - half);
Copy the code

Finally, the delay is set, using Handler to delay.

code

The code download

The core code is as follows:

public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private RecyclerView mRecyclerView; private LinearLayoutManager mLayoutManager; private RecyclerView.Adapter mAdapter; private String[] data; private Handler handler; private boolean isClick = false; private static int vHeight = -1; private static int reHeight = -1; private static int position = 0; private static final int target = 10; private static boolean isMove = false; private Runnable runnable; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); handler = new Handler(); mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view); MLayoutManager = new LinearLayoutManager(this); mLayoutManager.setAutoMeasureEnabled(true); mRecyclerView.setLayoutManager(mLayoutManager); / / if you can determine the height of each item is fixed, set this option may improve performance. MRecyclerView setHasFixedSize (true); mRecyclerView.setNestedScrollingEnabled(false); data = new String[]{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21"}; runnable = new Runnable() { @Override public void run() { if (isVisible()) { scrollToMiddle(); } else { mRecyclerView.scrollToPosition(position); isMove = true; isClick = false; }}}; mAdapter = new MyAdapter(data, new MyAdapter.onRecyclerViewItemClick() { @Override public void onItemClick(View v, Int pos) {toast.maketext (mainactivity.this, "" + pos +" "line ", toast.length_short).show(); position = pos; vHeight = v.getHeight(); Rect rect = new Rect(); mRecyclerView.getGlobalVisibleRect(rect); reHeight = rect.bottom - rect.top - vHeight; // handler.removeCallbacksAndMessages(null); handler.removeCallbacks(runnable); handler.postDelayed(runnable, 4000); isClick = true; }}); mRecyclerView.setAdapter(mAdapter); mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); Log.d(TAG, "" + newState); if (newState == RecyclerView.SCROLL_STATE_DRAGGING && ! isMove) { handler.removeCallbacks(runnable); } if (newState == RecyclerView.SCROLL_STATE_IDLE) { if (isClick) { handler.postDelayed(runnable, 4000); } } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (isMove) { if (vHeight < 0) { isMove = false; return; } scrollToMiddle(); }}}); public void scrollToMiddle() { final int firstPosition = mLayoutManager.findFirstVisibleItemPosition(); int top = mRecyclerView.getChildAt(position - firstPosition).getTop(); Log.d(TAG, " position" + position + " " + top); int half = reHeight / 2;Copy the code
mRecyclerView.scrollBy(0, top - half); isMove = false; } public boolean isVisible() { final int firstPosition = mLayoutManager.findFirstVisibleItemPosition(); final int lastPosition = mLayoutManager.findLastVisibleItemPosition(); return position <= lastPosition && position >= firstPosition; } @Override protected void onDestroy() { super.onDestroy(); handler.removeCallbacksAndMessages(null); handler = null; }}Copy the code

 

The second way

Can directly using scrollToPositionWithOffset method.

if (mLayoutManager ! = null && mLayoutManager instanceof LinearLayoutManager) { ((LinearLayoutManager) mLayoutManager) ScrollToPositionWithOffset (position, half);Copy the code
}
Copy the code

ScrollToPositionWithOffset will direct you to scroll the entry of scroll to the top, and the second parameter is used to control the scrolling offsets, how many distance from the top, so, we don’t have to write so much code above.