Android RecyclerView 滾動到中間位置

最近看到QQ音樂的歌詞每次滑動後均可以滾回到中間位置。以爲甚是神奇,打開開發者模式顯示佈局,發現歌詞部分不是採用 android 控件的寫的,應該是前端寫的。因而,我想,能不能用 recyclerView 實現這個自動回滾到中間位置呢。前端

功夫不負有心人,查找了一些資料以後,終於搞定了。android

下面由我細細講來。ide

目標

點擊某個條目,在通過4s無任何操做以後,該條目滾動到中間位置顯示。點擊後,用戶在滑動,等用戶不操做後再開始延時。用戶屢次點擊,記最後一次點擊位置。佈局

分析

首先先考慮,滾動到指定位置是如何操做的?post

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

有沒有滾動到制定像素位置呢?性能

// scrollBy(x, y)這個方法是本身去控制移動的距離,單位是像素,因此在使用scrollBy(x, y)須要本身去計算移動的高度或寬度。
recyclerView.scrollBy(x, y)

但是,問題是滾動到中間位置啊?這個怎麼辦呢?這樣子行不行呢?this

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

先滾動到制定位置,在滾動一段距離不就行了?運行發現,這兩行代碼只執行第一行,第二行無效。spa

debug 調試看了下,仍是沒有弄懂,實現太複雜。.net

那就是說這樣是不行的,那有沒有其餘辦法呢?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 會直接把你想滾動的條目滾動到頂部,第二個參數是用來控制滾動的偏移量,距離頂部多少距離,這樣的話,咱們就不用寫上面那麼多代碼啦。

相關文章
相關標籤/搜索