1. 程式人生 > >Android中自定上下拖動Viewpager

Android中自定上下拖動Viewpager

我們在淘寶上購物的時候,在瀏覽商品頁面時,有看到有”繼續拖動,檢視圖文詳情”,實則就是縱向拖動的Viewpager。今天我們就要來實現它。先上效果圖。
這裡寫圖片描述
要實現上面的效果,我們今天必須學習兩樣東西,一個就是ViewDragHelper,另外一個就是GestureDetectorCompat。好了,我們先,來看看View的拖動工具類。ViewDragHelper的基本用法很簡單。官方自帶的DrawerLayout側滑選單,也是用的這個實現的額。下面我們實現一個View的拖動效果。
這裡寫圖片描述
先自定義一個View在構造方法裡面例項化一個ViewDragHelper物件,直接呼叫靜態方法裡面有三個引數,第一個Context。第二個精度,第三個拖動回撥。這裡要說一下,精度給的值越大越敏感。

mDragHelper = ViewDragHelper.create(this, 10f, new DragHelperCallback());

第三個引數通過,繼承ViewDragHelper.Callback,你就可以在回撥中處理各種拖動行為。當然這裡我們自定義的一個類DragHelperCallback來重寫裡面的方法,簡單來講,我們重寫三個方法就可以了,如下

@Override
public boolean tryCaptureView(View arg0, int arg1) {
  return true;
  }
@Override
public int clampViewPositionVertical
(View child, int top, int dy) { return top; } @Override public int clampViewPositionHorizontal(View child, int left, int dy) { return left; }

以上三個方法,tryCaptureView的返回值可以決定一個parentview中哪個子view可以拖動,即可以判斷方法引數中的int值,給以具體的返回false或true。返回true表示該子View可以被拖動。英語好的朋友可能已經大概知道接下來兩個方法的作用了;對,翻譯過來,就是固定View的縱向和橫向位置。也就是說給拖動View設定邊界。。我們重點來說一下方法裡面第二個引數。第二個引數是指當前拖動子view應該到達的x座標。按照正常邏輯,應該是原值返回,但是我們在使用手機或者其他電子裝置的時候是希望在螢幕以內,以外我們看不到,就沒有意義了。所以,我們要更改一下,方法裡面的內容讓其返回一個恰當的值。

@Override
public int clampViewPositionVertical(View child, int top, int dy) {
    //得到頂部內邊距
   int topBound = getPaddingTop();  
  //得到底部內邊距
  int bottomBound = getHeight() - child.getHeight();  
  //返回最小值
   int newTop = Math.min(Math.max(top, topBound), bottomBound);  
  return newTop; 
  }

我們希望子View在ViewGroup內運動。最小Y軸即為topBound。最大即為bottomBound。通過Math.min方法判斷最小值,返回。就可以讓子View在父View內部運動。不會滑出邊界。同樣橫向方法照樣改寫。最小橫向移動位置在leftBound。最大為整個view最右邊位置減去子view最右邊位置再減去View的右內邊距。

@Override  
public int clampViewPositionHorizontal(View child, int left, int dx) {
  int leftBound = getPaddingLeft();  
   int rightBound = getWidth() - child.getWidth();  
   int newLeft = Math.min(Math.max(left, leftBound), rightBound);  
  return newLeft;  
  }

這樣,就實現了上面的效果,很簡單吧,但是如果想要更復雜的操作,那就得繼續深入。我們還可以重寫幾個方法用於其他功能。比如

//手指釋放的時候回撥// 滑動鬆開後
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel){}
//在邊界拖動時回撥
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId){}

我們想要view拖動後回到原位,首先拖動前先儲存,view的位置座標,然後在onViewReleased()方法種呼叫settleCapturedViewAt(int,int)方法裡面傳入x,y值。最後重繪invalidate();就可以了。邊界拖動同樣通過呼叫captureChildView(edgeFlags, pointerId)就可以了。我們就簡單的做了一個可拖動子view的viewgroup了。全部程式碼貼出來如下。

public class MyViewGoup extends LinearLayout {
    private ViewDragHelper mDragger;

    public MyViewGoup(Context context, AttributeSet attrs) {
        super(context, attrs);
        mDragger = ViewDragHelper.create(this, 10f, new DragHelperCallback());
    }


    class DragHelperCallback extends ViewDragHelper.Callback {

        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            int leftBound = getPaddingLeft();
            int rightBound = getWidth() - child.getWidth();
            int newLeft = Math.min(Math.max(left, leftBound), rightBound);
            return newLeft;
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            //得到頂部內邊距
            int topBound = getPaddingTop();
            //得到底部內邊距
            int bottomBound = getHeight() - child.getHeight();
            //返回最小值
            int newTop = Math.min(Math.max(top, topBound), bottomBound);
            return newTop;
        }

    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragger.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragger.processTouchEvent(event);
        return true;
    }
}

上面簡單的view拖動,我們就完成了,接下來開始我們的上下拖動ViewPager。大概思路也是一樣的。先自定一個類,繼承自ViewGroup。然後。在xml檔案中裝入兩個子佈局(ViewGroup)。然後,通過設定兩個子佈局的高度來控制view的拖動。同樣開始也是構造方法,建立ViewDragHelper物件。設定回撥。設定監聽,初始化炒作。

public class DragLayout extends ViewGroup {
    /**
     * 拖動工具類
     */
    private final ViewDragHelper mDragHelper;
    /**
     * 手勢處理,Y軸
     **/
    private GestureDetectorCompat gestureDetectorCompat;
    /**
     * 上下兩個frameLayout,在Activity中注入fragment
     */
    private View frameView1, frameView2;
    private int viewHeight;
    /**
     * 滑動速度的閾值,超過這個絕對值認為是上下
     */
    private static final int VEL_THRESHOLD = 100;
    /**
     * 單位是畫素,當上下滑動速度不夠時,通過這個閾值來判定是應該粘到頂部還是底部
     */
    private static final int DISTANCE_THRESHOLD = 100;

    /**
     * 手指按下,frameView1的gettop
     */
    private int downTop1;

    /**
     * 手指鬆開是否載入一下一頁的 介面
     */
    private ShowNextPageNotifier nextPageNotifier;

    public DragLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragLayout(Context context) {
        this(context, null);
    }

    public DragLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mDragHelper = ViewDragHelper
                .create(this, 10f, new DragHelperCallback());
        //邊界攔截,全部
        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);
        gestureDetectorCompat = new GestureDetectorCompat(context,
                new YScrollDetector());
    }

這裡,我們加入了手勢操作GestureDetectorCompat 。同樣在構造方法中建立一個例項。需要兩個引數。第一個Context上下文。第二個引數監聽。Detector類對外提供了兩個介面:OnGestureListener,OnDoubleTapListener,還有一個內部類SimpleOnGestureListener;SimpleOnGestureListener類是GestureDetector提供給我們的一個更方便的響應不同手勢的類,它實現了上述兩個介面,該類是static class,也就是說它實際上是一個外部類,我們可以在外部繼承這個類,重寫裡面的手勢處理方法。因此實現手勢識別有兩種方法,一種實現OnGestureListener介面,另一種是使用SimpleOnGestureListener類。我們今天這裡繼承SimpleOnGestureListener監聽,主要判斷Y軸上的手勢(因為是上下滑動)。

/**
 * 手勢監聽
 */
 private class YScrollDetector extends SimpleOnGestureListener {
   @Override
   public boolean onScroll(MotionEvent e1, MotionEvent e2,
                                float distanceX, float distanceY) {
          if (frameView2.getTop() <= 0 && distanceY > 0f) {
              return false;
        }
          // 垂直滾動時dy>dx,才被認定是上下拖動
        return Math.abs(distanceY) > Math.abs(distanceX);
        }
    }

我們剛才做的一個小荔枝。自定義類是繼承的LinearLayout。所以就沒有重寫OnLayout方法。這裡我們繼承自ViewGroup所以要重寫該方法。

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (frameView1.getTop() == 0) {
            frameView1.layout(l, 0, r, b - t);
            frameView2.layout(l, 0, r, b - t);
            //
            viewHeight = frameView1.getMeasuredHeight();
            //將fragmeView2向下移動距離
            frameView2.offsetTopAndBottom(viewHeight);
        } else {
            // 如果已經被初始化,這次onLayout只需將之前的狀態存入即可
            frameView1
                    .layout(l, frameView1.getTop(), r, frameView1.getBottom());
            frameView2
                    .layout(l, frameView2.getTop(), r, frameView2.getBottom());
        }
    }

先判斷第一個View是否在最頂上,如果在,就將fragmeView2排在View1下面。沒在表示,在拖動狀態。注意,排布時候呼叫的方法。offsetTopAndBottom方法就是將View的位置向下移動,它和scrollTo的區別是,前一個是表示移動View座標變化,後一個是移動View的內容,而View的座標不變。
有了排布,我們還是要有測量的,不然排布或者得到View的寬高的時候不準確,或者得不到。接下來測量

@SuppressLint("NewApi")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
        int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
        //計算要將View的內邊距計算進去然後再計算整個寬度高度,
        // 並且最後必須呼叫setMeasuredDimension方法設定寬度和高度,
        // resolveSizeAndState() 方法將返回一個合適的尺寸,只要將測量模式和我們計算的寬度高度傳進去即可。
        // 該方法在API11開始出現,低於該版本將無法使用該方法,
        setMeasuredDimension(
                resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
                resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
    }

上面已經說了,resolveSizeAndState()方法低版本用不了,那麼,就手動複製進來,

/**
* 這是View的方法,該方法不支援android低版本(2.2、2.3)的作業系統,所以手動複製過來以免強制退出
*/
    public static int resolveSizeAndState(int size, int measureSpec,
                                          int childMeasuredState) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

接下來,我們在來寫最重要的拖動回撥類

/**
* 拖拽效果主要邏輯
*/
    private class DragHelperCallback extends ViewDragHelper.Callback {
        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                                          int dx, int dy) {
            int childIndex = 1;
            if (changedView == frameView2) {
                childIndex = 2;
            }
            // 一個view位置改變,另一個view的位置要跟進
            onViewPosChanged(childIndex, top);
        }
        /**
         * 返回值可以決定一個parentview中哪個子view可以拖動
         */
        @Override
        public boolean tryCaptureView(View arg0, int arg1) {
            return true;
        }
        @Override
        public int getViewVerticalDragRange(View child) {
            return 1;
        }
        /**
         * 滑動鬆開後
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            animTopOrBottom(releasedChild, yvel);
        }
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            int finalTop = top;
            // 第一個view被拖動
            if (child == frameView1) {
                if (top > 0) {
                    // 不讓第一個view網上拖,因為頂部會白板
                    finalTop = 0;
                }
            } else if (child == frameView2) {// 第二個view被拖動
                if (top < 0) {
                    // 不讓第二個view往上拖動,因為底部會白板
                    finalTop = 0;
                }
            }
            // finalTop代表的是理論上應該拖動到的位置。此處計算拖動的距離除以一個引數(3),
            // 是讓滑動的速度變慢。數值越大,滑動的越慢
            return child.getTop() + (finalTop - child.getTop()) / 3;
        }
    }

在上下拖動的時候我們還會去判斷是否是最上面一個View,如果是就不允許再繼續想上翻頁了。同樣如果是最下面的View,也不允許向下翻頁。同時為了美觀,這裡設定動畫過度效果。animTopOrBottom方法如下

    /**
     * 滑動時view位置改變協調處理
     *
     * @param viewIndex 滑動view的index(1或2)
     * @param top       滑動View的top位置
     */
    private void onViewPosChanged(int viewIndex, int top) {
        if (viewIndex == 1) {
            int offsetTopBottom = viewHeight + frameView1.getTop()
                    - frameView2.getTop();
            frameView2.offsetTopAndBottom(offsetTopBottom);
        } else if (viewIndex == 2) {
            int offsetTopBottom = frameView2.getTop() - viewHeight
                    - frameView1.getTop();
            frameView1.offsetTopAndBottom(offsetTopBottom);
        }

    }
/**
     * 滑動鬆開後,需要向上或者向下粘到特定的位置
     *
     * @param releasedChild
     * @param yvel
     */
    public void animTopOrBottom(View releasedChild, float yvel) {
        // 預設是最頂端
        int finalTop = 0;
        if (releasedChild == frameView1) {
            // 拖動第一個view鬆手
            if (yvel < -VEL_THRESHOLD
                    || (downTop1 == 0 && frameView1.getTop() < -DISTANCE_THRESHOLD)) {
         // 向上的速度足夠大,就滑動到頂端
       // 向上滑動的距離超過某個值,就滑動到頂端
         finalTop = -viewHeight;
        // 下一頁可以初始化了
        if (nextPageNotifier != null) {
            nextPageNotifier.onDragNext();
        }
     }
     } else {
     // 拖動第二個view鬆手
     if (yvel > VEL_THRESHOLD
           || (downTop1 == -viewHeight && releasedChild.getTop() > DISTANCE_THRESHOLD)) {
        // 保持原地不懂
         finalTop = viewHeight;
     if (nextPageNotifier != null) {
            nextPageNotifier.onDragLast();
          }
     }
  }
     if (mDragHelper.smoothSlideViewTo(releasedChild, 0, finalTop)) {
      ViewCompat.postInvalidateOnAnimation(this);
  }
    }

當然,在滑動結束後,即,ACTION_UP後,我們呼叫smoothSlideViewTo方法來判斷是否結束拖動,如果返回為true,則進行重置。這個時候,我們可以設定監聽上下View的回撥介面。可以在鬆開手後回撥方法內,設定其他事務。

public interface ShowNextPageNotifier {
        public void onDragNext();
        public void onDragLast();
    }

當然了,我們的父控制元件同樣需要設定監聽,和重寫方法,

@Override
    protected void onFinishInflate() {
        // 初始化兩個view
        frameView1 = getChildAt(0);
        frameView2 = getChildAt(1);
    }

    @Override
    public void computeScroll() {
        //自動滾動停止時(continueSettling()裡檢測到滾動結束時)
        if (mDragHelper.continueSettling(true)) {
            //通知View進行重繪
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mDragHelper.continueSettling(true)) {
            //正在動畫中的時候,不處理touch事件
            return false;
        }
        boolean yScroll = gestureDetectorCompat.onTouchEvent(ev);
        //是否應該攔截事件
        boolean shouldIntercept = mDragHelper.shouldInterceptTouchEvent(ev);
        int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {
            //action_down時就讓mDragHelper開始工作,否則有時候導致異常
            mDragHelper.processTouchEvent(ev);
            downTop1 = frameView1.getTop();
        }
        return yScroll && shouldIntercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 統一交給mDragHelper處理 由DragHelperCallback實現拖動效果
        mDragHelper.processTouchEvent(event);
        return true;
    }

/**
 * 設定下一頁監聽
 **/
  public void setNextPageListener(ShowNextPageNotifier nextPageNotifier) {
     this.nextPageNotifier = nextPageNotifier;
 }

上面註釋很詳細,大家,都應該看得懂。這裡還要強調一下的是,如果是在父控制元件裡面有DragHelper然後其子類容器控制元件也有DragHelper,快速滑動時有衝突的情況,這個時候最好在ACTION_DOWN的時候判斷一下,就可以避免了。
好了,自定義ViewGroup就完成了,是不是很簡單,當然通過這篇文章的學習,我們掌握了ViewDragHelper的用法,那麼什麼側滑,拖拽,之類的自定義控制元件就可以實現起來很輕鬆了 。最後將完整程式碼貼出來,供大家參考,大晚上的寫著也有點困了,謝謝!

/**
 * viewGroup容器 實現上下兩個layout拖動切換
 */
public class DragLayout extends ViewGroup {
    /**
     * 拖動工具類
     */
    private final ViewDragHelper mDragHelper;
    /**
     * 手勢處理,Y軸
     **/
    private GestureDetectorCompat gestureDetectorCompat;
    /**
     * 上下兩個frameLayout,在Activity中注入fragment
     */
    private View frameView1, frameView2;

    private int viewHeight;
    /**
     * 滑動速度的閾值,超過這個絕對值認為是上下
     */
    private static final int VEL_THRESHOLD = 100;
    /**
     * 單位是畫素,當上下滑動速度不夠時,通過這個閾值來判定是應該粘到頂部還是底部
     */
    private static final int DISTANCE_THRESHOLD = 100;

    /**
     * 手指按下,frameView1的gettop
     */
    private int downTop1;

    /**
     * 手指鬆開是否載入一下一頁的 介面
     */
    private ShowNextPageNotifier nextPageNotifier;

    public DragLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragLayout(Context context) {
        this(context, null);
    }

    public DragLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mDragHelper = ViewDragHelper
                .create(this, 10f, new DragHelperCallback());
        /**邊界攔截,全部**/
        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);
        gestureDetectorCompat = new GestureDetectorCompat(context,
                new YScrollDetector());
    }

    @Override
    protected void onFinishInflate() {
        // 初始化兩個view
        frameView1 = getChildAt(0);
        frameView2 = getChildAt(1);
    }

    @Override
    public void computeScroll() {
        //自動滾動停止時(continueSettling()裡檢測到滾動結束時)
        if (mDragHelper.continueSettling(true)) {
            //通知View進行重繪
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mDragHelper.continueSettling(true)) {
            //正在動畫中的時候,不處理touch事件
            return false;
        }
        boolean yScroll = gestureDetectorCompat.onTouchEvent(ev);
        //是否應該攔截事件
        boolean shouldIntercept = mDragHelper.shouldInterceptTouchEvent(ev);
        int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {
            //action_down時就讓mDragHelper開始工作,否則有時候導致異常
            mDragHelper.processTouchEvent(ev);
            downTop1 = frameView1.getTop();
        }
        return yScroll && shouldIntercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 統一交給mDragHelper處理 由DragHelperCallback實現拖動效果
        mDragHelper.processTouchEvent(event);
        return true;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (frameView1.getTop() == 0) {
            frameView1.layout(l, 0, r, b - t);
            frameView2.layout(l, 0, r, b - t);

            viewHeight = frameView1.getMeasuredHeight();
            //將fragmeView2向下移動距離
            frameView2.offsetTopAndBottom(viewHeight);
        } else {
            // 如果已經被初始化,這次onLayout只需將之前的狀態存入即可
            frameView1
                    .layout(l, frameView1.getTop(), r, frameView1.getBottom());
            frameView2
                    .layout(l, frameView2.getTop(), r, frameView2.getBottom());
        }
    }

    @SuppressLint("NewApi")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
        int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
        //計算要將View的內邊距計算進去然後再計算整個寬度高度,
        // 並且最後必須呼叫setMeasuredDimension方法設定寬度和高度,
        // resolveSizeAndState() 方法將返回一個合適的尺寸,只要將測量模式和我們計算的寬度高度傳進去即可。
        // 該方法在API11開始出現,低於該版本將無法使用該方法,
        setMeasuredDimension(
                resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
                resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
    }

    /**
     * 這是View的方法,該方法不支援android低版本(2.2、2.3)的作業系統,所以手動複製過來以免強制退出
     */
    public static int resolveSizeAndState(int size, int measureSpec,
                                          int childMeasuredState) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }

        return result | (childMeasuredState & MEASURED_STATE_MASK);

    }

    /**
     * 設定下一頁監聽
     **/
    public void setNextPageListener(ShowNextPageNotifier nextPageNotifier) {
        this.nextPageNotifier = nextPageNotifier;
    }

    /**
     * 手勢監聽
     */
    private class YScrollDetector extends SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                                float distanceX, float distanceY) {
            if (frameView2.getTop() <= 0 && distanceY > 0f) {
                return false;
            }

            // 垂直滾動時dy>dx,才被認定是上下拖動
            return Math.abs(distanceY) > Math.abs(distanceX);
        }
    }

    /**
     * 滑動時view位置改變協調處理
     *
     * @param viewIndex 滑動view的index(1或2)
     * @param top       滑動View的top位置
     */
    private void onViewPosChanged(int viewIndex, int top) {
        if (viewIndex == 1) {
            int offsetTopBottom = viewHeight + frameView1.getTop()
                    - frameView2.getTop();
            frameView2.offsetTopAndBottom(offsetTopBottom);
        } else if (viewIndex == 2) {
            int offsetTopBottom = frameView2.getTop() - viewHeight
                    - frameView1.getTop();
            frameView1.offsetTopAndBottom(offsetTopBottom);
        }

    }

    /**
     * 拖拽效果主要邏輯
     */
    private class DragHelperCallback extends ViewDragHelper.Callback {

        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                                          int dx, int dy) {
            int childIndex = 1;
            if (changedView == frameView2) {
                childIndex = 2;
            }
            // 一個view位置改變,另一個view的位置要跟進
            onViewPosChanged(childIndex, top);
        }

        /**
         * 返回值可以決定一個parentview中哪個子view可以拖動
         */
        @Override
        public boolean tryCaptureView(View arg0, int arg1) {
            return true;
        }

        @Override
        public int getViewVerticalDragRange(View child) {
            return 1;
        }

        /**
         * 滑動鬆開後
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            animTopOrBottom(releasedChild, yvel);
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            int finalTop = top;
            // 第一個view被拖動
            if (child == frameView1) {
                if (top > 0) {
                    // 不讓第一個view網上拖,因為頂部會白板
                    finalTop = 0;
                }
            } else if (child == frameView2) {// 第二個view被拖動
                if (top < 0) {
                    // 不讓第二個view往上拖動,因為底部會白板
                    finalTop = 0;
                }
            }
            // finalTop代表的是理論上應該拖動到的位置。此處計算拖動的距離除以一個引數(3),
            // 是讓滑動的速度變慢。數值越大,滑動的越慢
            return child.getTop() + (finalTop - child.getTop()) / 3;
        }
    }

    /**
     * 滑動鬆開後,需要向上或者向下粘到特定的位置
     *
     * @param releasedChild
     * @param yvel
     */
    public void animTopOrBottom(View releasedChild, float yvel) {
        // 預設是最頂端
        int finalTop = 0;
        if (releasedChild == frameView1) {
            // 拖動第一個view鬆手
            if (yvel < -VEL_THRESHOLD
                    || (downTop1 == 0 && frameView1.getTop() < -DISTANCE_THRESHOLD)) {
                // 向上的速度足夠大,就滑動到頂端
                // 向上滑動的距離超過某個值,就滑動到頂端
                finalTop = -viewHeight;

                // 下一頁可以初始化了
                if (nextPageNotifier != null) {
                    nextPageNotifier.onDragNext();
                }
            }
        } else {
            // 拖動第二個view鬆手
            if (yvel > VEL_THRESHOLD
                    || (downTop1 == -viewHeight && releasedChild.getTop() > DISTANCE_THRESHOLD)) {
                // 保持原地不懂
                finalTop = viewHeight;
                if (nextPageNotifier != null) {
                    nextPageNotifier.onDragLast();
                }
            }
        }

        if (mDragHelper.smoothSlideViewTo(releasedChild, 0, finalTop)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }

    }

    public void scrollChildView(int childIndex) {
        if (childIndex == 0) {
            scrollChildView(frameView1);
        } else if (childIndex == 1) {
            scrollChildView(frameView2);
        }
    }

    public void scrollChildView(View view) {
        if (nextPageNotifier != null) {
            if (view == frameView1) {
                nextPageNotifier.onDragLast();
            } else if (view == frameView2) {
                nextPageNotifier.onDragNext();
            }
        }
        if (mDragHelper.smoothSlideViewTo(view, 0, 0)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    public interface ShowNextPageNotifier {
        public void onDragNext();

        public void onDragLast();
    }

}