1. 程式人生 > >Android解決MyScrollView包含RecyclerView重新整理資料會滑動到RecyclerView頂部的問題

Android解決MyScrollView包含RecyclerView重新整理資料會滑動到RecyclerView頂部的問題

首先要說說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就不會拿到焦點了,從而在資料發生變化時不會自動的滑動到列表的頂部。

良好的記錄習慣,方便他人,也方便自己~