Android解決MyScrollView包含RecyclerView重新整理資料會滑動到RecyclerView頂部的問題
阿新 • • 發佈:2019-02-06
首先要說說MyScrollView是什麼東西,其實這個控制元件大家用過的話都會了解,Android原生自帶的ScrollView中如果包裹了RecyclerView或者ListView等控制元件的話,那麼執行效果不盡如人意,主要體現在RecyclerView或ListView等資料不能完全顯示出來,這是因為系統內部對這些並沒有處理,所以這個時候MyScrollView基於ScrollView進行了改造,使得我們的資料能夠完全顯示並且沒有滑動衝突等問題,這類程式碼網上有很多,我這裡只簡單的提供一個供大家參考:
public class MyScrollView extends ScrollView { private List<OnScrollListener> onScrollListeners; //滑動開始、結束狀態回撥 private List<OnScrollStateListener> mOnScrollStateListeners; /** 當前滑動到y座標 */ private int mCurrentY; /** 滑動到底部監聽器 */ private OnScrollToBottomListener mOnScrollToBottom; /** 滑動到底部監聽器 */ public interface OnScrollToBottomListener { void onScrollBottom(); } /** * 主要是用在使用者手指離開MyScrollView,MyScrollView還在繼續滑動,我們用來儲存Y的距離,然後做比較 */ private int lastScrollY; private long DELAY_TIME = 100; //延遲判斷滑動是否結束 private long lastScrollTime = -1; //上次滑動時間 /** * 用於使用者手指離開MyScrollView的時候獲取MyScrollView滾動的Y距離,然後回撥給onScroll方法中 */ private Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { int scrollY = MyScrollView.this.getScrollY(); // 此時的距離和記錄下的距離不相等,在隔5毫秒給handler傳送訊息 if (lastScrollY != scrollY) { lastScrollY = scrollY; handler.sendMessageDelayed(handler.obtainMessage(), 5); } if (onScrollListeners != null) { for (OnScrollListener listner : onScrollListeners) { listner.onScroll(scrollY); } } } ; }; //通過延時時間判斷onScroll函式是否執行 private Runnable scrollTask = new Runnable() { @Override public void run() { long currentTime = System.currentTimeMillis(); if (currentTime - lastScrollTime > DELAY_TIME) { lastScrollTime = -1; onScrollStop(); } else { //滑動中 handler.postDelayed(this, DELAY_TIME); } } }; //開始滑動 private void onScrollStart() { if (mOnScrollStateListeners == null) { return; } for (OnScrollStateListener listener:mOnScrollStateListeners) { listener.onScrollStart(); } } //停止滑動 private void onScrollStop() { if (mOnScrollStateListeners == null) { return; } for (OnScrollStateListener listener:mOnScrollStateListeners) { listener.onScrollStop(); } } public MyScrollView(Context context) { super(context); } public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public MyScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } /** * 設定滑動狀態監聽 */ public void setOnScrollStateListener(OnScrollStateListener listener) { if (mOnScrollStateListeners == null) { mOnScrollStateListeners = new ArrayList<>(); } if (listener != null) { mOnScrollStateListeners.add(listener); } } public void removeScrollStateListener(OnScrollStateListener listener) { if (listener != null && mOnScrollStateListeners != null) { mOnScrollStateListeners.remove(listener); } } /** * 設定滾動介面 */ public void setOnScrollListener(OnScrollListener onScrollListener) { if (onScrollListeners == null) { onScrollListeners = new ArrayList<>(); } onScrollListeners.add(onScrollListener); } public void removeAllScrollListener() { if (onScrollListeners != null) { onScrollListeners.clear(); } } public void setOnScrollToBottomListener(OnScrollToBottomListener listener) { mOnScrollToBottom = listener; } private float xDistance, yDistance, lastX, lastY; public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: xDistance = yDistance = 0f; lastX = ev.getX(); lastY = ev.getY(); break; case MotionEvent.ACTION_MOVE: final float curX = ev.getX(); final float curY = ev.getY(); xDistance += Math.abs(curX - lastX); yDistance += Math.abs(curY - lastY); lastX = curX; lastY = curY; if (xDistance > yDistance) { return false; } } return super.onInterceptTouchEvent(ev); } /** * 重寫onTouchEvent, 當用戶的手在MyScrollView上面的時候, * 直接將MyScrollView滑動的Y方向距離回撥給onScroll方法中,當用戶擡起手的時候, * MyScrollView可能還在滑動,所以當用戶擡起手我們隔5毫秒給handler傳送訊息,在handler處理 * MyScrollView滑動的距離 */ @Override public boolean onTouchEvent(MotionEvent ev) { if (onScrollListeners != null) { for (OnScrollListener listner : onScrollListeners) { listner.onScroll(lastScrollY = this.getScrollY()); listner.onTouchEvent(ev); } } switch (ev.getAction()) { case MotionEvent.ACTION_UP: handler.sendMessageDelayed(handler.obtainMessage(), 20); break; } return super.onTouchEvent(ev); } public interface OnScrollStateListener { void onScrollStart(); //開始滑動 void onScrollStop(); //停止滑動 } /** * 滾動的回撥介面 */ public interface OnScrollListener { /** * 回撥方法, 返回MyScrollView滑動的Y方向距離 */ public void onScroll(int scrollY); /** * 回撥方法, 返回MyScrollView滑動到Y方向上的位置 */ public void onScrollPosition(int position); public void onTouchEvent(MotionEvent ev); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { if (onScrollListeners != null) { for (OnScrollListener listner : onScrollListeners) { int scrollY = getScrollY(); listner.onScrollPosition(scrollY); } } if (lastScrollTime == -1) { onScrollStart(); postDelayed(scrollTask, DELAY_TIME); } lastScrollTime = System.currentTimeMillis(); //更新scrollView的滑動時間 super.onScrollChanged(l, t, oldl, oldt); } @Override protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { super.onOverScrolled(scrollX, scrollY, clampedX, clampedY); //下滑 if (scrollY > mCurrentY && scrollY > 0 && clampedY) { if (null != mOnScrollToBottom) { mOnScrollToBottom.onScrollBottom(); } } mCurrentY = scrollY; } }
但是如果使用起來的話還是會發現另一個問題,那就是當其中的RecyclerView的資料進行重新整理或者發生變化時,這個時候會發現頁面內容會滑動到RecyclerView的頂部,而它上面的內容會滑出螢幕,這個原因主要是因為RecyclerView獲取到了焦點,從而進行了一個滑動的操作,要想解決這個問題的方法也很簡單,那就是在xml檔案中MyScrollView下的第一個子節點新增2個屬性,舉個例子:
<com.view.MyScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:overScrollMode="never" android:scrollbars="none"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:focusable="true" android:focusableInTouchMode="true" android:paddingBottom="24dp"> ......
其中
android:focusable=“true”
android:focusableInTouchMode=“true”
這2個屬性表示LinearLayout會獲取焦點,這個時候RecyclerView就不會拿到焦點了,從而在資料發生變化時不會自動的滑動到列表的頂部。
良好的記錄習慣,方便他人,也方便自己~