最近看到QQ音乐的歌词每次滑动后都可以滚回到中间位置。觉得甚是神奇,打开开发者模式显示布局,发现歌词部分不是采用 android 控件的写的,应该是前端写的。于是,我想,能不能用 recyclerView 实现这个自动回滚到中间位置呢。

功夫不负有心人,查找了一些资料之后,终于搞定了。

下面由我细细讲来。

点击某个条目,在经过4s无任何操作之后,该条目滚动到中间位置显示。点击后,用户在滑动,等用户不操作后再开始延时。用户多次点击,记最后一次点击位置。

首先先考虑,滚动到指定位置是如何操作的?

// 滚动到指定位置
recyclerView.scrollToPosition(position);
// 平滑滚动到指定位置
recyclerView.smoothScrollToPosition(position);

有没有滚动到制定像素位置呢?

// scrollBy(x, y)这个方法是自己去控制移动的距离,单位是像素,所以在使用scrollBy(x, y)需要自己去计算移动的高度或宽度。
recyclerView.scrollBy(x, y)

可是,问题是滚动到中间位置啊?这个怎么办呢?这样子行不行呢?

mRecyclerView.scrollToPosition(0);
mRecyclerView.scrollBy(0,400);

先滚动到制定位置,在滚动一段距离不就好了?运行发现,这两行代码只执行第一行,第二行无效。

debug 调试看了下,还是没有弄懂,实现太复杂。

那就是说这样是不行的,那有没有其他办法呢?

RecyclerView 有一个滚动监听方法:

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);
 

onScrollStateChanged 方法对应三种状态:静止(SCROLL_STATE_IDLE),拖动滚动(SCROLL_STATE_DRAGGING),滑动(SCROLL_STATE_SETTLING)。
当手动缓慢滑动的时候,会触发:onScrollStateChanged (拖动滚动) --> (n个)onScrolled -->onScrollStateChanged(静止);

当手快速滑动的时候,会触发:onScrollStateChanged (拖动滚动) --> (n个)onScrolled --> onScrollStateChanged (滑动) -->

(n个)onScrolled --> onScrollStateChanged (静止);
有想法了,点击的时候,先运行 scrollToPosition,在 onScrolled 方法里面 运行 scrollBy 方法。写代码,运行,通过。
下面就是中间位置的计算了。

首先计算出 recylerview 的展现高度。

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

当运行 scrollToPosition 后,点击条目就会出现在视野当中,这时候,计算出相应的位移即可。需要注意一点的是,当点击条目在视野内的时候,是不会运行 scrollToPosition 方法的。

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

最后就是延时的设定,采用Handler 进行延时。

核心代码如下:

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);
        //创建默认的线性LayoutManager
        mLayoutManager = new LinearLayoutManager(this);
        mLayoutManager.setAutoMeasureEnabled(true);
        mRecyclerView.setLayoutManager(mLayoutManager);
        //如果可以确定每个item的高度是固定的,设置这个选项可以提高性能
        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 + "行", 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;
        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;
 

第二种方法

可以直接采用 scrollToPositionWithOffset 方法。

if (mLayoutManager != null && mLayoutManager instanceof LinearLayoutManager) {
    ((LinearLayoutManager) mLayoutManager)
          .scrollToPositionWithOffset(position, half); 

scrollToPositionWithOffset 会直接把你想滚动的条目滚动到顶部,第二个参数是用来控制滚动的偏移量,距离顶部多少距离,这样的话,我们就不用写上面那么多代码啦。

最近看到QQ音乐的歌词每次滑动后都可以滚回到中间位置。觉得甚是神奇,打开开发者模式显示布局,发现歌词部分不是采用 android 控件的写的,应该是前端写的。于是,我想,能不能用 recyclerView 实现这个自动回滚到中间位置呢。功夫不负有心人,查找了一些资料之后,终于搞定了。下面由我细细讲来。目标点击某个条目,在经过4s无任何操作之后,该条目滚动到中间位置显示。点击后,用户在滑动,... 二、实现的思路: 1、每一次的滑动都让图片保持在中间,其实RecyclerView24.2.0版本以后,Google已经提供一个SnapHelper的辅助类,可以实现每一次滚动结束保持居中。具体用法就是: LinearSnapHelper类似SnapHelper的一个子类,SnapHelper的另一个子类叫PagerSnapHelper。两者的
最近预研EPG一些界面功能的时候需要实现一种节目列表选中自动居中放大的功能,查找一些网上的资料,发现大多数是重写RecyclerView的布局管理器的smoothScrollToPosition函数,该方法通过修改滑动Scroller里面的偏移量来达到居中的效果,不过在测试的过程中我发现当按住遥控器一会再松开时,列表会滚动一段时间再停止,无法实现即停的效果,界面操作起来不是很友好。 经过一些资料的搜索,发现可以在适配器里面做居中操作,具体做法是Item添加一个聚焦的监听,然后对当前Item的坐标以及R..
import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.os.Build; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; imp..