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