ViewDragHelper详解

http://blog.csdn.net/daditao/article/details/29826035

2013年谷歌i/o大会上介绍了两个新的layout: SlidingPaneLayout和DrawerLayout,现在这俩个类被广泛的运用,其实研究他们的源码你会发现这两个类都运用了ViewDragHelper来处理拖动。ViewDragHelper是framework中不为人知却非常有用的一个工具

ViewDragHelper解决了android中手势处理过于复杂的问题,在DrawerLayout出现之前,侧滑菜单都是由第三方开源代码实现的,其中著名的当属MenuDrawer ,MenuDrawer重写onTouchEvent方法来实现侧滑效果,代码量很大,实现逻辑也需要很大的耐心才能看懂。如果每个开发人员都从这么原始的步奏开始做起,那对于安卓生态是相当不利的。所以说ViewDragHelper等的出现反映了安卓开发框架已经开始向成熟的方向迈进。

本文先介绍ViewDragHelper的基本用法,然后介绍一个能真正体现ViewDragHelper实用性的例子。

其实ViewDragHelper并不是第一个用于分析手势处理的类,gesturedetector也是,但是在和拖动相关的手势分析方面gesturedetector只能说是勉为其难。

关于ViewDragHelper有如下几点:

ViewDragHelper.Callback是连接ViewDragHelper与view之间的桥梁(这个view一般是指拥子view的容器即parentView);

   ViewDragHelper的实例是通过静态工厂方法创建的;

   你能够指定拖动的方向;

   ViewDragHelper可以检测到是否触及到边缘;

   ViewDragHelper并不是直接作用于要被拖动的View,而是使其控制的视图容器中的子View可以被拖动,如果要指定某个子view的行为,需要在Callback中想办法;

   ViewDragHelper的本质其实是分析onInterceptTouchEventonTouchEvent的MotionEvent参数,然后根据分析的结果去改变一个容器中被拖动子View的位置( 通过offsetTopAndBottom(int offset)和offsetLeftAndRight(int offset)方法 ),他能在触摸的时候判断当前拖动的是哪个子View;

   虽然ViewDragHelper的实例方法 ViewDragHelper create(ViewGroup forParent, Callback cb) 可以指定一个被ViewDragHelper处理拖动事件的对象 ,但ViewDragHelper类的设计决定了其适用于被包含在一个自定义ViewGroup之中,而不是对任意一个布局上的视图容器使用ViewDragHelper

 

-----------------------------------------------------------------------------------------------

本文最先发表在我的个人网站 http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/0911/1680.html

-----------------------------------------------------------------------------------------------------

用法:

1.ViewDragHelper的初始化

ViewDragHelper一般用在一个自定义ViewGroup的内部,比如下面自定义了一个继承于LinearLayout的DragLayout,DragLayout内部有一个子viewmDragView作为成员变量:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
 
  1. public class DragLayout extends LinearLayout {  
  2. private final ViewDragHelper mDragHelper;  
  3. private View mDragView;  
  4. public DragLayout(Context context) {  
  5.   this(context, null);  
  6. }  
  7. public DragLayout(Context context, AttributeSet attrs) {  
  8.   this(context, attrs, 0);  
  9. }  
  10. public DragLayout(Context context, AttributeSet attrs, int defStyle) {  
  11.   super(context, attrs, defStyle);  
  12. }  


创建一个带有回调接口的ViewDragHelper

 

 

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
 
  1. public DragLayout(Context context, AttributeSet attrs, int defStyle) {  
  2.   super(context, attrs, defStyle);  
  3.   mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());  
  4. }  

 

 

其中1.0f是敏感度参数参数越大越敏感。第一个参数为this,表示该类生成的对象,他是ViewDragHelper的拖动处理对象,必须为ViewGroup。

要让ViewDragHelper能够处理拖动需要将触摸事件传递给ViewDragHelper,这点和gesturedetector是一样的:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
 
  1. @Override  
  2. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  3.   final int action = MotionEventCompat.getActionMasked(ev);  
  4.   if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {  
  5.       mDragHelper.cancel();  
  6.       return false;  
  7.   }  
  8.   return mDragHelper.shouldInterceptTouchEvent(ev);  
  9. }  
  10. @Override  
  11. public boolean onTouchEvent(MotionEvent ev) {  
  12.   mDragHelper.processTouchEvent(ev);  
  13.   return true;  
  14. }  

 

接下来,你就可以在回调中处理各种拖动行为了。

 

2.拖动行为的处理

处理横向的拖动:

DragHelperCallback中实现clampViewPositionHorizontal方法, 并且返回一个适当的数值就能实现横向拖动效果,clampViewPositionHorizontal的第二个参数是指当前拖动子view应该到达的x坐标。所以按照常理这个方法原封返回第二个参数就可以了,但为了让被拖动的view遇到边界之后就不在拖动,对返回的值做了更多的考虑。

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
 
  1. @Override  
  2. public int clampViewPositionHorizontal(View child, int left, int dx) {  
  3.   Log.d("DragLayout", "clampViewPositionHorizontal " + left + "," + dx);  
  4.   final int leftBound = getPaddingLeft();  
  5.   final int rightBound = getWidth() - mDragView.getWidth();  
  6.   final int newLeft = Math.min(Math.max(left, leftBound), rightBound);  
  7.   return newLeft;  
  8. }  

 

 

 

同上,处理纵向的拖动:

DragHelperCallback中实现clampViewPositionVertical方法,实现过程同clampViewPositionHorizontal

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
 
  1. @Override  
  2. public int clampViewPositionVertical(View child, int top, int dy) {  
  3.   final int topBound = getPaddingTop();  
  4.   final int bottomBound = getHeight() - mDragView.getHeight();  
  5.   final int newTop = Math.min(Math.max(top, topBound), bottomBound);  
  6.   return newTop;  
  7. }  


 

clampViewPositionHorizontal 和 clampViewPositionVertical必须要重写,因为默认它返回的是0。事实上我们在这两个方法中所能做的事情很有限。 个人觉得这两个方法的作用就是给了我们重新定义目的坐标的机会。

通过DragHelperCallback的tryCaptureView方法的返回值可以决定一个parentview中哪个子view可以拖动,现在假设有两个子views (mDragView1和mDragView2)  ,如下实现tryCaptureView之后,则只有mDragView1是可以拖动的。

1
2
3
4
@Override
public boolean tryCaptureView(View child, int pointerId) {
  returnchild == mDragView1;
}

 

滑动边缘:

分为滑动左边缘还是右边缘:EDGE_LEFT和EDGE_RIGHT,下面的代码设置了可以处理滑动左边缘:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
 
  1. mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);  


假如如上设置,onEdgeTouched方法会在左边缘滑动的时候被调用,这种情况下一般都是没有和子view接触的情况。

 

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
 
  1. @Override  
  2. public void onEdgeTouched(int edgeFlags, int pointerId) {  
  3.     super.onEdgeTouched(edgeFlags, pointerId);  
  4.     Toast.makeText(getContext(), "edgeTouched", Toast.LENGTH_SHORT).show();  
  5. }  


如果你想在边缘滑动的时候根据滑动距离移动一个子view,可以通过实现onEdgeDragStarted方法,并在onEdgeDragStarted方法中手动指定要移动的子View

 

 

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
 
  1. @Override  
  2. public void onEdgeDragStarted(int edgeFlags, int pointerId) {  
  3.     mDragHelper.captureChildView(mDragView2, pointerId);  
  4. }  

 

 

ViewDragHelper让我们很容易实现一个类似于YouTube视频浏览效果的控件,效果如下:

 

代码中的关键点:

1.tryCaptureView返回了唯一可以被拖动的header view;

2.拖动范围drag range的计算是在onLayout中完成的;

3.注意在onInterceptTouchEvent和onTouchEvent中使用的ViewDragHelper的若干方法;

4.在computeScroll中使用continueSettling方法(因为ViewDragHelper使用了scroller)

5.smoothSlideViewTo方法来完成拖动结束后的惯性操作。

需要注意的是代码仍然有很大改进空间。

activity_main.xml

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
 
  1. <FrameLayout  
  2.         xmlns:android="http://schemas.android.com/apk/res/android"  
  3.         android:layout_width="match_parent"  
  4.         android:layout_height="match_parent">  
  5.     <ListView  
  6.             android:id="@+id/listView"  
  7.             android:layout_width="match_parent"  
  8.             android:layout_height="match_parent"  
  9.             android:tag="list"  
  10.             />  
  11.     <com.example.vdh.YoutubeLayout  
  12.             android:layout_width="match_parent"  
  13.             android:layout_height="match_parent"  
  14.             android:id="@+id/youtubeLayout"  
  15.             android:orientation="vertical"  
  16.             android:visibility="visible">  
  17.         <TextView  
  18.                 android:id="@+id/viewHeader"  
  19.                 android:layout_width="match_parent"  
  20.                 android:layout_height="128dp"  
  21.                 android:fontFamily="sans-serif-thin"  
  22.                 android:textSize="25sp"  
  23.                 android:tag="text"  
  24.                 android:gravity="center"  
  25.                 android:textColor="@android:color/white"  
  26.                 android:background="#AD78CC"/>  
  27.         <TextView  
  28.                 android:id="@+id/viewDesc"  
  29.                 android:tag="desc"  
  30.                 android:textSize="35sp"  
  31.                 android:gravity="center"  
  32.                 android:text="Loreum Loreum"  
  33.                 android:textColor="@android:color/white"  
  34.                 android:layout_width="match_parent"  
  35.                 android:layout_height="match_parent"  
  36.                 android:background="#FF00FF"/>  
  37.     </com.example.vdh.YoutubeLayout>  
  38. </FrameLayout>  


YoutubeLayout.java

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
 
  1. public class YoutubeLayout extends ViewGroup {  
  2. private final ViewDragHelper mDragHelper;  
  3. private View mHeaderView;  
  4. private View mDescView;  
  5. private float mInitialMotionX;  
  6. private float mInitialMotionY;  
  7. private int mDragRange;  
  8. private int mTop;  
  9. private float mDragOffset;  
  10. public YoutubeLayout(Context context) {  
  11.   this(context, null);  
  12. }  
  13. public YoutubeLayout(Context context, AttributeSet attrs) {  
  14.   this(context, attrs, 0);  
  15. }  
  16. @Override  
  17. protected void onFinishInflate() {  
  18.     mHeaderView = findViewById(R.id.viewHeader);  
  19.     mDescView = findViewById(R.id.viewDesc);  
  20. }  
  21. public YoutubeLayout(Context context, AttributeSet attrs, int defStyle) {  
  22.   super(context, attrs, defStyle);  
  23.   mDragHelper = ViewDragHelper.create(this, 1f, new DragHelperCallback());  
  24. }  
  25. public void maximize() {  
  26.     smoothSlideTo(0f);  
  27. }  
  28. boolean smoothSlideTo(float slideOffset) {  
  29.     final int topBound = getPaddingTop();  
  30.     int y = (int) (topBound + slideOffset * mDragRange);  
  31.     if (mDragHelper.smoothSlideViewTo(mHeaderView, mHeaderView.getLeft(), y)) {  
  32.         ViewCompat.postInvalidateOnAnimation(this);  
  33.         return true;  
  34.     }  
  35.     return false;  
  36. }  
  37. private class DragHelperCallback extends ViewDragHelper.Callback {  
  38.   @Override  
  39.   public boolean tryCaptureView(View child, int pointerId) {  
  40.         return child == mHeaderView;  
  41.   }  
  42.     @Override  
  43.   public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {  
  44.       mTop = top;  
  45.       mDragOffset = (float) top / mDragRange;  
  46.         mHeaderView.setPivotX(mHeaderView.getWidth());  
  47.         mHeaderView.setPivotY(mHeaderView.getHeight());  
  48.         mHeaderView.setScaleX(1 - mDragOffset / 2);  
  49.         mHeaderView.setScaleY(1 - mDragOffset / 2);  
  50.         mDescView.setAlpha(1 - mDragOffset);  
  51.         requestLayout();  
  52.   }  
  53.   @Override  
  54.   public void onViewReleased(View releasedChild, float xvel, float yvel) {  
  55.       int top = getPaddingTop();  
  56.       if (yvel > 0 || (yvel == 0 && mDragOffset > 0.5f)) {  
  57.           top += mDragRange;  
  58.       }  
  59.       mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);  
  60.   }  
  61.   @Override  
  62.   public int getViewVerticalDragRange(View child) {  
  63.       return mDragRange;  
  64.   }  
  65.   @Override  
  66.   public int clampViewPositionVertical(View child, int top, int dy) {  
  67.       final int topBound = getPaddingTop();  
  68.       final int bottomBound = getHeight() - mHeaderView.getHeight() - mHeaderView.getPaddingBottom();  
  69.       final int newTop = Math.min(Math.max(top, topBound), bottomBound);  
  70.       return newTop;  
  71.   }  
  72. }  
  73. @Override  
  74. public void computeScroll() {  
  75.   if (mDragHelper.continueSettling(true)) {  
  76.       ViewCompat.postInvalidateOnAnimation(this);  
  77.   }  
  78. }  
  79. @Override  
  80. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  81.   final int action = MotionEventCompat.getActionMasked(ev);  
  82.   if (( action != MotionEvent.ACTION_DOWN)) {  
  83.       mDragHelper.cancel();  
  84.       return super.onInterceptTouchEvent(ev);  
  85.   }  
  86.   if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {  
  87.       mDragHelper.cancel();  
  88.       return false;  
  89.   }  
  90.   final float x = ev.getX();  
  91.   final float y = ev.getY();  
  92.   boolean interceptTap = false;  
  93.   switch (action) {  
  94.       case MotionEvent.ACTION_DOWN: {  
  95.           mInitialMotionX = x;  
  96.           mInitialMotionY = y;  
  97.             interceptTap = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);  
  98.           break;  
  99.       }  
  100.       case MotionEvent.ACTION_MOVE: {  
  101.           final float adx = Math.abs(x - mInitialMotionX);  
  102.           final float ady = Math.abs(y - mInitialMotionY);  
  103.           final int slop = mDragHelper.getTouchSlop();  
  104.           if (ady > slop && adx > ady) {  
  105.               mDragHelper.cancel();  
  106.               return false;  
  107.           }  
  108.       }  
  109.   }  
  110.   return mDragHelper.shouldInterceptTouchEvent(ev) || interceptTap;  
  111. }  
  112. @Override  
  113. public boolean onTouchEvent(MotionEvent ev) {  
  114.   mDragHelper.processTouchEvent(ev);  
  115.   final int action = ev.getAction();  
  116.     final float x = ev.getX();  
  117.     final float y = ev.getY();  
  118.     boolean isHeaderViewUnder = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);  
  119.     switch (action & MotionEventCompat.ACTION_MASK) {  
  120.       case MotionEvent.ACTION_DOWN: {  
  121.           mInitialMotionX = x;  
  122.           mInitialMotionY = y;  
  123.           break;  
  124.       }  
  125.       case MotionEvent.ACTION_UP: {  
  126.           final float dx = x - mInitialMotionX;  
  127.           final float dy = y - mInitialMotionY;  
  128.           final int slop = mDragHelper.getTouchSlop();  
  129.           if (dx * dx + dy * dy slop * slop && isHeaderViewUnder) {  
  130.               if (mDragOffset == 0) {  
  131.                   smoothSlideTo(1f);  
  132.               } else {  
  133.                   smoothSlideTo(0f);  
  134.               }  
  135.           }  
  136.           break;  
  137.       }  
  138.   }  
  139.   return isHeaderViewUnder && isViewHit(mHeaderView, (int) x, (int) y) || isViewHit(mDescView, (int) x, (int) y);  
  140. }  
  141. private boolean isViewHit(View view, int x, int y) {  
  142.     int[] viewLocation = new int[2];  
  143.     view.getLocationOnScreen(viewLocation);  
  144.     int[] parentLocation = new int[2];  
  145.     this.getLocationOnScreen(parentLocation);  
  146.     int screenX = parentLocation[0] + x;  
  147.     int screenY = parentLocation[1] + y;  
  148.     return screenX >= viewLocation[0] && screenX viewLocation[0] + view.getWidth() &&  
  149.             screenY >= viewLocation[1] && screenY viewLocation[1] + view.getHeight();  
  150. }  
  151. @Override  
  152. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  153.     measureChildren(widthMeasureSpec, heightMeasureSpec);  
  154.     int maxWidth = MeasureSpec.getSize(widthMeasureSpec);  
  155.     int maxHeight = MeasureSpec.getSize(heightMeasureSpec);  
  156.     setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),  
  157.             resolveSizeAndState(maxHeight, heightMeasureSpec, 0));  
  158. }  
  159. @Override  
  160. protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  161.   mDragRange = getHeight() - mHeaderView.getHeight();  
  162.     mHeaderView.layout(  
  163.             0,  
  164.             mTop,  
  165.             r,  
  166.             mTop + mHeaderView.getMeasuredHeight());  
  167.     mDescView.layout(  
  168.             0,  
  169.             mTop + mHeaderView.getMeasuredHeight(),  
  170.             r,  
  171.             mTop  + b);  
  172. }  


代码下载地址:https://github.com/flavienlaurent/flavienlaurent.com

 

不管是menudrawer 还是本文实现的DragLayout都体现了一种设计哲学,即可拖动的控件都是封装在一个自定义的Layout中的,为什么这样做?为什么不直接将ViewDragHelper.create(this, 1f, new DragHelperCallback())中的this替换成任何已经布局好的容器,这样这个容器中的子View就能被拖动了,而往往是单独定义一个Layout来处理?个人认为如果在一般的布局中去拖动子view并不会出现什么问题,只是原本规则的世界被打乱了,而单独一个Layout来完成拖动,无非是说,他本来就没有什么规则可言,拖动一下也无妨。

源码解析:

   1 /*
   2  * Copyright (C) 2013 The <a href="http://www.it165.net/pro/ydad/" target="_blank" class="keylink">Android</a> Open Source Project
   3  *
   4  * Licensed under the Apache License, Version 2.0 (the "License");
   5  * you may not use this file except in compliance with the License.
   6  * You may obtain a copy of the License at
   7  *
   8  *      http://www.apache.org/licenses/LICENSE-2.0
   9  *
  10  * Unless required by applicable law or agreed to in writing, software
  11  * distributed under the License is distributed on an "AS IS" BASIS,
  12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13  * See the License for the specific language governing permissions and
  14  * limitations under the License.
  15  */
  16 
  17 
  18 package android.support.v4.widget;
  19 
  20 import android.content.Context;
  21 import android.support.v4.view.MotionEventCompat;
  22 import android.support.v4.view.VelocityTrackerCompat;
  23 import android.support.v4.view.ViewCompat;
  24 import android.view.MotionEvent;
  25 import android.view.VelocityTracker;
  26 import android.view.View;
  27 import android.view.ViewConfiguration;
  28 import android.view.ViewGroup;
  29 import android.view.animation.Interpolator;
  30 
  31 import java.util.Arrays;
  32 
  33 /**
  34  * @tranlator AlexTam
  35  * ViewDragHelper是自定义ViewGroup时的实用类.它提供大量有用的操作和状态,来追踪用户在父View内的
  36  * 拖曳子view和重新定位子view.(看到这里,估计就会想,要同步监听拖曳事件,肯定少不了在onTouch事件中随处用到的
  37  * MotionEvent这个对象.是的,下面的确有它.)
  38  * 
  39  * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number
  40  * of useful operations and state tracking for allowing a user to drag and reposition
  41  * views within their parent ViewGroup.
  42  */
  43 public class ViewDragHelper {
  44     private static final String TAG = "ViewDragHelper";
  45 
  46     /**
  47      * 空/无效的pointer ID
  48      * A null/invalid pointer ID.
  49      */
  50     public static final int INVALID_POINTER = -1;
  51 
  52     /**
  53      * 状态量:(IDLE是闲置的 意思)表示 view当前没有被拖曳或者运行的动画结束
  54      * A view is not currently being dragged or animating as a result of a fling/snap.
  55      */
  56     public static final int STATE_IDLE = 0;
  57 
  58     /**
  59      * 状态量: view当前正被拖曳.根据用户的输入或者模拟用户的输入,view当前位置发生改变.(表示,
  60      * 用户怎么拖曳移动,view就根据拖曳的动作而发生位置改变.)
  61      * A view is currently being dragged. The position is currently changing as a result
  62      * of user input or simulated user input.
  63      */
  64     public static final int STATE_DRAGGING = 1;
  65 
  66     /**
  67      * 状态量: 由于fling动作,或者预定的无交互的运动,view被"安置"到一个结束的地方.(可以想象,一个view被用户快速拖曳
  68      * 并甩动,从而view被甩到某个结束的位置,的过程.)
  69      * A view is currently settling into place as a result of a fling or
  70      * predefined non-interactive motion.
  71      */
  72     public static final int STATE_SETTLING = 2;
  73 
  74     /**
  75      * 标记可从左边缘拖曳.
  76      * Edge flag indicating that the left edge should be affected.
  77      */
  78     public static final int EDGE_LEFT = 1 << 0;
  79 
  80     /**
  81      * 标记可从右边缘拖曳.
  82      * Edge flag indicating that the right edge should be affected.
  83      */
  84     public static final int EDGE_RIGHT = 1 << 1;
  85 
  86     /**
  87      * 标记可从顶部拖曳.
  88      * Edge flag indicating that the top edge should be affected.
  89      */
  90     public static final int EDGE_TOP = 1 << 2;
  91 
  92     /**
  93      * 标记可从底部拖曳.
  94      * Edge flag indicating that the bottom edge should be affected.
  95      */
  96     public static final int EDGE_BOTTOM = 1 << 3;
  97 
  98     /**
  99      * 标记所有地方(边缘的上下左右)都能被拖曳.
 100      * Edge flag set indicating all edges should be affected.
 101      */
 102     public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM;
 103 
 104     /**
 105      * 指引值:
 106      * 
 107      * 表示a check(指引) 应该沿着水平轴发生.
 108      * Indicates that a check should occur along the horizontal axis
 109      */
 110     public static final int DIRECTION_HORIZONTAL = 1 << 0;
 111 
 112     /**
 113      * 表示a check 应该沿着垂直轴发生.
 114      * Indicates that a check should occur along the vertical axis
 115      */
 116     public static final int DIRECTION_VERTICAL = 1 << 1;
 117 
 118     /**
 119      * 表示a check可水平可垂直的发生.
 120      * Indicates that a check should occur along all axes
 121      */
 122     public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
 123 
 124     //将边缘大小定位20dp
 125     private static final int EDGE_SIZE = 20; // dp
 126 
 127     //时间值
 128     private static final int BASE_SETTLE_DURATION = 256; // ms
 129     private static final int MAX_SETTLE_DURATION = 600; // ms
 130 
 131     // 当前的拖曳状态,值为idle, dragging or settling.
 132     // Current drag state; idle, dragging or settling
 133     private int mDragState;
 134 
 135     // 在拖曳开始前的滑动位移.(可以这么理解,触发拖曳的最大临界值.)
 136     // Distance to travel before a drag may begin
 137     private int mTouchSlop;
 138     
 139     // 上一次的位置或点
 140     // Last known position/pointer tracking
 141     private int mActivePointerId = INVALID_POINTER;
 142     //初始化的X坐标
 143     private float[] mInitialMotionX;
 144     //初始化的Y坐标
 145     private float[] mInitialMotionY;
 146     //下面这些变量不写了噻,看名字也能知道.
 147     private float[] mLastMotionX;
 148     private float[] mLastMotionY;
 149     private int[] mInitialEdgesTouched;
 150     private int[] mEdgeDragsInProgress;
 151     private int[] mEdgeDragsLocked;
 152     private int mPointersDown;
 153 
 154     private VelocityTracker mVelocityTracker;
 155     private float mMaxVelocity;
 156     private float mMinVelocity;
 157     //边缘的大小,单位px
 158     private int mEdgeSize;
 159     private int mTrackingEdges;
 160 
 161     //兼容新API所提供的Scroller
 162     private ScrollerCompat mScroller;
 163     
 164     //内部抽象类,提供一些规范的接口方法
 165     private final Callback mCallback;
 166 
 167     private View mCapturedView;
 168     private boolean mReleaseInProgress;
 169 
 170     private final ViewGroup mParentView;
 171 
 172     /**
 173      * 这个Callback是作为通信接口,当ViewDragHelper返回父view时使用."on"为首的方法是重要事件的回调方法,几个
 174      * 接口方法用于提供更多关于请求父view的状态的信息给ViewDragHelper.这个抽象类同时提供子view拖曳的一些细节信息.
 175      * 
 176      * A Callback is used as a communication channel with the ViewDragHelper back to the
 177      * parent view using it. <code>on*</code>methods are invoked on siginficant events and several
 178      * accessor methods are expected to provide the ViewDragHelper with more information
 179      * about the state of the parent view upon request. The callback also makes decisions
 180      * governing the range and draggability of child views.
 181      */
 182     public static abstract class Callback {
 183         /**
 184          * 当拖曳状态变更时回调该方法.可看"STATE_"为首的常量了解更多信息.
 185          * Called when the drag state changes. See the <code>STATE_*</code> constants
 186          * for more information.
 187          *
 188          * @param state The new drag state
 189          *
 190          * @see #STATE_IDLE
 191          * @see #STATE_DRAGGING
 192          * @see #STATE_SETTLING
 193          */
 194         public void onViewDragStateChanged(int state) {}
 195 
 196         /**
 197          * 当捕获view由于拖曳或者设定而发生位置变更时回调..
 198          * Called when the captured view's position changes as the result of a drag or settle.
 199          *
 200          * @param changedView View whose position changed - 发生位置变更的view
 201          * @param left New X coordinate of the left edge of the view - 新的左边缘X坐标
 202          * @param top New Y coordinate of the top edge of the view    - 新的顶部边缘Y坐标
 203          * @param dx Change in X position from the last call    - 从旧到新位置发生的X偏移值
 204          * @param dy Change in Y position from the last call    - 从旧到新位置发生的Y偏移值
 205          */
 206         public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}
 207 
 208         /**
 209          * 当子view被由于拖曳或设置(settle有点难翻译)而被捕获时回调的方法.提供拖曳的pointer的ID.
 210          * 如果activePointerId被标记为{@link #INVALID_POINTER},它会代替没有初始化的pointer.
 211          * 
 212          * Called when a child view is captured for dragging or settling. The ID of the pointer
 213          * currently dragging the captured view is supplied. If activePointerId is
 214          * identified as {@link #INVALID_POINTER} the capture is programmatic instead of
 215          * pointer-initiated.
 216          *
 217          * @param capturedChild Child view that was captured
 218          * @param activePointerId Pointer id tracking the child capture
 219          */
 220         public void onViewCaptured(View capturedChild, int activePointerId) {}
 221 
 222         /**
 223          * 当子view不再被拖曳时调用.如果有需要,fling的速度也会被提供.速度值会介于系统最小化和最大值之间.
 224          * 
 225          * Called when the child view is no longer being actively dragged.
 226          * The fling velocity is also supplied, if relevant. The velocity values may
 227          * be clamped to system minimums or maximums.
 228          *
 229          * <p>Calling code may decide to fling or otherwise release the view to let it
 230          * settle into place. It should do so using {@link #settleCapturedViewAt(int, int)}
 231          * or {@link #flingCapturedView(int, int, int, int)}. If the Callback invokes
 232          * one of these methods, the ViewDragHelper will enter {@link #STATE_SETTLING}
 233          * and the view capture will not fully end until it comes to a complete stop.
 234          * If neither of these methods is invoked before <code>onViewReleased</code> returns,
 235          * the view will stop in place and the ViewDragHelper will return to
 236          * {@link #STATE_IDLE}.</p>
 237          *
 238          * @param releasedChild The captured child view now being released
 239          *         - 被捕获到的要释放的子view
 240          * @param xvel X velocity of the pointer as it left the screen in pixels per second.
 241          *         - pointer离开屏幕X轴方向每秒运动的速率,单位是px.
 242          * @param yvel Y velocity of the pointer as it left the screen in pixels per second.
 243          *         - pointer离开屏幕Y轴方向每秒运动的速率,单位是px.
 244          */
 245         public void onViewReleased(View releasedChild, float xvel, float yvel) {}
 246 
 247         /**
 248          * 当父view其中一个被标记可拖曳的边缘被用户触摸, 同时父view里没有子view被捕获响应时回调该方法.
 249          * Called when one of the subscribed edges in the parent view has been touched
 250          * by the user while no child view is currently captured.
 251          *
 252          * @param edgeFlags A combination of edge flags describing the edge(s) currently touched
 253          *             - 描述所当前所触摸的位置的边缘标记, 如EDGE_LEFT,EDGE_RIGHT等等.
 254          * @param pointerId ID of the pointer touching the described edge(s)
 255          *             - 触摸的点的ID.
 256          * 
 257          * @see #EDGE_LEFT
 258          * @see #EDGE_TOP
 259          * @see #EDGE_RIGHT
 260          * @see #EDGE_BOTTOM
 261          */
 262         public void onEdgeTouched(int edgeFlags, int pointerId) {}
 263 
 264         /**
 265          * 该方法当原来可以拖曳的边缘被锁定不可拖曳时回调.如果边缘在初始化开始拖曳前被拒绝拖曳,就会发生前面说的这种情况.
 266          * 但这个方法会在{@link #onEdgeTouched(int, int)}之后才会被回调.这个方法会返回true来锁定该边缘.或者
 267          * 返回false来释放解锁该屏幕.默认的行为是后者(返回false来释放解锁该屏幕).
 268          * 
 269          * Called when the given edge may become locked. This can happen if an edge drag
 270          * was preliminarily rejected before beginning, but after {@link #onEdgeTouched(int, int)}
 271          * was called. This method should return true to lock this edge or false to leave it
 272          * unlocked. The default behavior is to leave edges unlocked.
 273          *
 274          * @param edgeFlags A combination of edge flags describing the edge(s) locked
 275          *         - 描述被锁定的边缘的边缘标记,如EDGE_LEFT等.
 276          * @return true to lock the edge, false to leave it unlocked
 277          *         - 返回true来锁定该边缘.或者 返回false来释放解锁该屏幕.
 278          */
 279         public boolean onEdgeLock(int edgeFlags) {
 280             return false;
 281         }
 282 
 283         /**
 284          * 当用户开始从父view中"订阅的"(之前约定允许拖曳的)屏幕边缘拖曳,并且父view中没有子view响应时调用.
 285          * 
 286          * Called when the user has started a deliberate drag away from one
 287          * of the subscribed edges in the parent view while no child view is currently captured.
 288          *
 289          * @param edgeFlags A combination of edge flags describing the edge(s) dragged
 290          *         - 描述该边缘的边缘标记,如EDGE_LEFT等.
 291          * @param pointerId ID of the pointer touching the described edge(s)
 292          *         - pointer的ID.
 293          * @see #EDGE_LEFT
 294          * @see #EDGE_TOP
 295          * @see #EDGE_RIGHT
 296          * @see #EDGE_BOTTOM
 297          */
 298         public void onEdgeDragStarted(int edgeFlags, int pointerId) {}
 299 
 300         /**
 301          * 调用设置子view z轴次序的参数.
 302          * Called to determine the Z-order of child views.
 303          *
 304          * @param index the ordered position to query for
 305          * @return index of the view that should be ordered at position <code>index</code>
 306          */
 307         public int getOrderedChildIndex(int index) {
 308             return index;
 309         }
 310 
 311         /**
 312          * 返回拖曳的子view水平移动范围的值,单位为px.这个方法如果返回0,那么该view则不能水平移动.
 313          * Return the magnitude of a draggable child view's horizontal range of motion in pixels.
 314          * This method should return 0 for views that cannot move horizontally.
 315          *
 316          * @param child Child view to check - 目标子view
 317          * @return range of horizontal motion in pixels    - 水平拖曳的值,单位为px.
 318          */
 319         public int getViewHorizontalDragRange(View child) {
 320             return 0;
 321         }
 322 
 323         /**
 324          * 返回拖曳的子view垂直移动范围的值,单位为px.这个方法如果返回0,那么该view则不能垂直移动.
 325          * Return the magnitude of a draggable child view's vertical range of motion in pixels.
 326          * This method should return 0 for views that cannot move vertically.
 327          *
 328          * @param child Child view to check
 329          * @return range of vertical motion in pixels
 330          */
 331         public int getViewVerticalDragRange(View child) {
 332             return 0;
 333         }
 334 
 335         /**
 336          * 当用户通过pointerId 输入特定值令目标子view移动时回调该方法.callback接口如果返回true,则表示用户
 337          * 允许通过用于引导的pointer来拖曳该子view.
 338          * Called when the user's input indicates that they want to capture the given child view
 339          * with the pointer indicated by pointerId. The callback should return true if the user
 340          * is permitted to drag the given view with the indicated pointer.
 341          *
 342          * 如果该子view已经被捕获, ViewDragHelper可能多次重复的调用该方法.多次的调用会导致新的pointer尝试去控制这个view.
 343          * <p>ViewDragHelper may call this method multiple times for the same view even if
 344          * the view is already captured; this indicates that a new pointer is trying to take
 345          * control of the view.</p>
 346          *
 347          * 如果该方法返回true,并且当成功捕获到该子view时,方法{@link #onViewCaptured(android.view.View, int)}会随即被调用.
 348          * <p>If this method returns true, a call to {@link #onViewCaptured(android.view.View, int)}
 349          * will follow if the capture is successful.</p>
 350          *
 351          * @param child Child the user is attempting to capture    - 用户视图捕获的子view
 352          * @param pointerId ID of the pointer attempting the capture    - 捕获该子view的pointerID.
 353          * @return true if capture should be allowed, false otherwise    - 如果允许并且捕获成功应该返回true.否则返回false.
 354          */
 355         public abstract boolean tryCaptureView(View child, int pointerId);
 356 
 357         /**
 358          * 该方法用于限制子view沿水平拖曳的手势.默认的实现是,不允许水平手势.如果有类继承了该类,
 359          * 必须覆盖重写该方法,并且提供值去限制该拖曳手势.
 360          * Restrict the motion of the dragged child view along the horizontal axis.
 361          * The default implementation does not allow horizontal motion; the extending
 362          * class must override this method and provide the desired clamping.
 363          *
 364          *
 365          * @param child Child view being dragged    -  被拖曳的子view.
 366          * @param left Attempted motion along the X axis    - 沿X轴(水平)的手势
 367          * @param dx Proposed change in position for left    - view的left变更值
 368          * @return The new clamped position for left - 对left返回新的位置值
 369          */
 370         public int clampViewPositionHorizontal(View child, int left, int dx) {
 371             return 0;
 372         }
 373 
 374         /**
 375          * 该方法用于限制子view沿垂直拖曳的手势.默认的实现是,不允许垂直手势...(同上面的方法类似,就不过多解释了.)
 376          * Restrict the motion of the dragged child view along the vertical axis.
 377          * The default implementation does not allow vertical motion; the extending
 378          * class must override this method and provide the desired clamping.
 379          *
 380          *
 381          * @param child Child view being dragged
 382          * @param top Attempted motion along the Y axis
 383          * @param dy Proposed change in position for top
 384          * @return The new clamped position for top
 385          */
 386         public int clampViewPositionVertical(View child, int top, int dy) {
 387             return 0;
 388         }
 389     }
 390 
 391     /**
 392      * 定义曲线动画的插值器
 393      * Interpolator defining the animation curve for mScroller
 394      */
 395     private static final Interpolator sInterpolator = new Interpolator() {
 396         public float getInterpolation(float t) {
 397             t -= 1.0f;
 398             return t * t * t * t * t + 1.0f;
 399         }
 400     };
 401 
 402     // 实现Runnable接口
 403     private final Runnable mSetIdleRunnable = new Runnable() {
 404         public void run() {
 405             setDragState(STATE_IDLE);
 406         }
 407     };
 408 
 409     /**
 410      * 创建ViewDragHelper的工厂方法
 411      * Factory method to create a new ViewDragHelper.
 412      *
 413      * @param forParent Parent view to monitor  - 所要监听的父view
 414      * @param cb Callback to provide information and receive events - 提供信息的Callback对象
 415      * @return a new ViewDragHelper instance
 416      */
 417     public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
 418         return new ViewDragHelper(forParent.getContext(), forParent, cb);
 419     }
 420 
 421     /**
 422      * Factory method to create a new ViewDragHelper.
 423      *
 424      * @param forParent Parent view to monitor
 425      * @param sensitivity Multiplier for how sensitive the helper should be about detecting
 426      *                    the start of a drag. Larger values are more sensitive. 1.0f is normal.
 427      * @param cb Callback to provide information and receive events
 428      * @return a new ViewDragHelper instance
 429      */
 430     public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
 431         final ViewDragHelper helper = create(forParent, cb);
 432         helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
 433         return helper;
 434     }
 435 
 436     /**
 437      * 应用应该使用ViewDragHelper.create()去获取新的实例.这将允许ViewDragHelper使用内部实现去兼容不同的平台版本.
 438      * Apps should use ViewDragHelper.create() to get a new instance.
 439      * This will allow VDH to use internal compatibility implementations for different
 440      * platform versions.
 441      *
 442      * @param context Context to initialize config-dependent params from
 443      * @param forParent Parent view to monitor
 444      */
 445     private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) {
 446         if (forParent == null) {
 447             throw new IllegalArgumentException("Parent view may not be null");
 448         }
 449         if (cb == null) {
 450             throw new IllegalArgumentException("Callback may not be null");
 451         }
 452 
 453         mParentView = forParent;
 454         mCallback = cb;
 455 
 456         // ViewConfiguration是一个包含配置信息,如时间,位移等的配置类.
 457         final ViewConfiguration vc = ViewConfiguration.get(context);
 458         final float density = context.getResources().getDisplayMetrics().density;
 459         mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);
 460 
 461         mTouchSlop = vc.getScaledTouchSlop();
 462         mMaxVelocity = vc.getScaledMaximumFlingVelocity();
 463         mMinVelocity = vc.getScaledMinimumFlingVelocity();
 464         mScroller = ScrollerCompat.create(context, sInterpolator);
 465     }
 466 
 467     /**
 468      * 设置最小速率.大于0px/s的速率能更好的被检测到.这样Callback就能恰当的运用该值去约束移动的速率.
 469      * Set the minimum velocity that will be detected as having a magnitude greater than zero
 470      * in pixels per second. Callback methods accepting a velocity will be clamped appropriately.
 471      *
 472      * @param minVel Minimum velocity to detect
 473      */
 474     public void setMinVelocity(float minVel) {
 475         mMinVelocity = minVel;
 476     }
 477 
 478     /**
 479      * 获取最小速率. 值得注意的是,如果最小速率小于0, 那么直接返回0,不会返回比0小的值.
 480      * Return the currently configured minimum velocity. Any flings with a magnitude less
 481      * than this value in pixels per second. Callback methods accepting a velocity will receive
 482      * zero as a velocity value if the real detected velocity was below this threshold.
 483      *
 484      * @return the minimum velocity that will be detected
 485      */
 486     public float getMinVelocity() {
 487         return mMinVelocity;
 488     }
 489 
 490     /**
 491      * 获取当前helper的拖曳状态,返回结果为{@link #STATE_IDLE}, {@link #STATE_DRAGGING} 
 492      * or {@link #STATE_SETTLING}.中的其一.
 493      * 
 494      * Retrieve the current drag state of this helper. This will return one of
 495      * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
 496      * @return The current drag state
 497      */
 498     public int getViewDragState() {
 499         return mDragState;
 500     }
 501 
 502     /**
 503      * 设置允许父view的某个边缘可追踪.CallBack对象的{@link Callback#onEdgeTouched(int, int)} and
 504      * {@link Callback#onEdgeDragStarted(int, int)}方法只有在边缘允许被追踪时才会调用. 
 505      * (就是说,如果不设置上下左右的某个边缘可追踪,那么这2个方法是不可用的.)
 506      * 
 507      * Enable edge tracking for the selected edges of the parent view.
 508      * The callback's {@link Callback#onEdgeTouched(int, int)} and
 509      * {@link Callback#onEdgeDragStarted(int, int)} methods will only be invoked
 510      * for edges for which edge tracking has been enabled.
 511      *
 512      * @param edgeFlags Combination of edge flags describing the edges to watch
 513      * @see #EDGE_LEFT
 514      * @see #EDGE_TOP
 515      * @see #EDGE_RIGHT
 516      * @see #EDGE_BOTTOM
 517      */
 518     public void setEdgeTrackingEnabled(int edgeFlags) {
 519         mTrackingEdges = edgeFlags;
 520     }
 521 
 522     /**
 523      * 返回边缘大小的值.单位为px.这个值是该view边缘可以被监测或追踪的值的范围.
 524      * Return the size of an edge. This is the range in pixels along the edges of this view
 525      * that will actively detect edge touches or drags if edge tracking is enabled.
 526      *
 527      * @return The size of an edge in pixels
 528      * @see #setEdgeTrackingEnabled(int)
 529      */
 530     public int getEdgeSize() {
 531         return mEdgeSize;
 532     }
 533 
 534     /**
 535      * 在父view内捕获指定的子view用于拖曳.同时callback对象会被通知.但{@link Callback#tryCaptureView(android.view.View, int)}
 536      * 不会被要求获取权限来捕获该view.
 537      * 
 538      * Capture a specific child view for dragging within the parent. The callback will be notified
 539      * but {@link Callback#tryCaptureView(android.view.View, int)} will not be asked permission to
 540      * capture this view.
 541      *
 542      * @param childView Child view to capture
 543      * @param activePointerId ID of the pointer that is dragging the captured child view
 544      */
 545     public void captureChildView(View childView, int activePointerId) {
 546         if (childView.getParent() != mParentView) {
 547             throw new IllegalArgumentException("captureChildView: parameter must be a descendant " +
 548                     "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
 549         }
 550 
 551         mCapturedView = childView;
 552         mActivePointerId = activePointerId;
 553         mCallback.onViewCaptured(childView, activePointerId);
 554         setDragState(STATE_DRAGGING);
 555     }
 556 
 557     /**
 558      * 返回当前捕获的view.如果没有捕获到的view,则返回null.
 559      * @return The currently captured view, or null if no view has been captured.
 560      */
 561     public View getCapturedView() {
 562         return mCapturedView;
 563     }
 564 
 565     /**
 566      * 当前拖曳捕获的view的点(pointer)的ID.
 567      * @return The ID of the pointer currently dragging the captured view,
 568      *         or {@link #INVALID_POINTER}.
 569      */
 570     public int getActivePointerId() {
 571         return mActivePointerId;
 572     }
 573 
 574     /**
 575      * 获取最小触发和初始化拖曳动作的值,单位px.
 576      * @return The minimum distance in pixels that the user must travel to initiate a drag
 577      */
 578     public int getTouchSlop() {
 579         return mTouchSlop;
 580     }
 581 
 582     /**
 583      * 这方法等价于onTouch中MotionEvent的ACTION_CANCEL事件.
 584      * The result of a call to this method is equivalent to
 585      * {@link #processTouchEvent(android.view.MotionEvent)} receiving an ACTION_CANCEL event.
 586      */
 587     public void cancel() {
 588         mActivePointerId = INVALID_POINTER;
 589         clearMotionHistory();
 590 
 591         if (mVelocityTracker != null) {
 592             mVelocityTracker.recycle();
 593             mVelocityTracker = null;
 594         }
 595     }
 596 
 597     /**
 598      * 中止取所有手势.并且直接结束动画.
 599      * {@link #cancel()}, but also abort all motion in progress and snap to the end of any
 600      * animation.
 601      */
 602     public void abort() {
 603         cancel();
 604         if (mDragState == STATE_SETTLING) {
 605             final int oldX = mScroller.getCurrX();
 606             final int oldY = mScroller.getCurrY();
 607             mScroller.abortAnimation();
 608             final int newX = mScroller.getCurrX();
 609             final int newY = mScroller.getCurrY();
 610             mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY);
 611         }
 612         //中止了,当然要设置拖曳状态为闲置(或者说初始态)
 613         setDragState(STATE_IDLE);
 614     }
 615 
 616     /**
 617      * (使用这个方法,可以有动画效果的移动子view到特定位置,该位置需要给出的finalLeft和 finalTop值.)
 618      * 随着动画,子view移动到既定(给定left和top值)的位置.如果这个方法返回true,会在后面随着手势移动的
 619      * 每一帧中回调{@link #continueSettling(boolean)}方法,直至返回false.如果这个方法返回false,
 620      * 就不会再移动去完成手势动作的事件. 
 621      * 
 622      * Animate the view <code>child</code> to the given (left, top) position.
 623      * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
 624      * on each subsequent frame to continue the motion until it returns false. If this method
 625      * returns false there is no further work to do to complete the movement.
 626      *
 627      * 要注意的是,即使方法{@link #getCapturedView()}在这个滑动过程中仍会一直有效,可以获取catureView的值,
 628      * 但这个操作过程不看做是一个捕获事件(我们应当知道,捕获子view不是我们决定的,是Helper自动在父view和
 629      * 子view之间去自动完成的过程,无论这个过程成功还是失败).
 630      * 
 631      * <p>This operation does not count as a capture event, though {@link #getCapturedView()}
 632      * will still report the sliding view while the slide is in progress.</p>
 633      *
 634      * @param child Child view to capture and animate - 要捕获和添加动画移动的view对象
 635      * @param finalLeft Final left position of child - 最终位置的left值
 636      * @param finalTop Final top position of child - 最终位置的top值
 637      * @return true if animation should continue through {@link #continueSettling(boolean)} calls
 638      */
 639     public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {
 640         mCapturedView = child;
 641         mActivePointerId = INVALID_POINTER;
 642 
 643         boolean continueSliding = forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);
 644         if (!continueSliding && mDragState == STATE_IDLE && mCapturedView != null) {
 645             // If we're in an IDLE state to begin with and aren't moving anywhere, we
 646             // end up having a non-null capturedView with an IDLE dragState
 647             mCapturedView = null;
 648         }
 649 
 650         return continueSliding;
 651     }
 652 
 653     /**
 654      * (通过这个方法,我们应当知道settle和slide的区别.前者是直接跳到结束位置,而后者是有过渡效果的.)
 655      * 将捕获的view设置(settle)在给定的left,top值的位置.(表示,直接忽略过程,直接将view显示在特定位置)
 656      * 这个过程中,该view(如果在此时已经有)适当的速度,则该速度会影响settle的过程.
 657      * 如果这个方法返回true,方法{@link #continueSettling(boolean)}在整个settle过程中会被回调,直至返回false.
 658      * 如果这个方法返回false,(表示此时该view已经在给定的位置)这个settle的过程就会结束,不会再工作完成事件.
 659      * 
 660      * Settle the captured view at the given (left, top) position.
 661      * The appropriate velocity from prior motion will be taken into account.
 662      * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
 663      * on each subsequent frame to continue the motion until it returns false. If this method
 664      * returns false there is no further work to do to complete the movement.
 665      *
 666      * @param finalLeft Settled left edge position for the captured view
 667      * @param finalTop Settled top edge position for the captured view
 668      * @return true if animation should continue through {@link #continueSettling(boolean)} calls
 669      */
 670     public boolean settleCapturedViewAt(int finalLeft, int finalTop) {
 671         if (!mReleaseInProgress) {
 672             throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " +
 673                     "Callback#onViewReleased");
 674         }
 675 
 676         return forceSettleCapturedViewAt(finalLeft, finalTop,
 677                 (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
 678                 (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId));
 679     }
 680 
 681     /**
 682      * 同样是将view直接设到特定位置(给定left, top值).
 683      * (看该方法的实现,整个过程 也是靠scroller的scroll去实现的).
 684      * Settle the captured view at the given (left, top) position.
 685      *
 686      * @param finalLeft Target left position for the captured view
 687      * @param finalTop Target top position for the captured view
 688      * @param xvel Horizontal velocity    - 水平速度
 689      * @param yvel Vertical velocity    - 垂直速度
 690      * @return true if animation should continue through {@link #continueSettling(boolean)} calls
 691      *         - settleing的过程中会一直返回true,否则返回false表示结束.
 692      */
 693     private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
 694         final int startLeft = mCapturedView.getLeft();
 695         final int startTop = mCapturedView.getTop();
 696         final int dx = finalLeft - startLeft;
 697         final int dy = finalTop - startTop;
 698 
 699         if (dx == 0 && dy == 0) {
 700             // Nothing to do. Send callbacks, be done.
 701             mScroller.abortAnimation();
 702             setDragState(STATE_IDLE);
 703             return false;
 704         }
 705         // 仔细看computeSettleDuration()这个计算时间的方法,其实挺复杂的.使用了相当多的运算处理.因此可以不看该方法的实现;
 706         // 除非要继承ViewDrarHelper实现子类,实现更多效果...
 707         final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
 708         mScroller.startScroll(startLeft, startTop, dx, dy, duration);
 709 
 710         setDragState(STATE_SETTLING);
 711         return true;
 712     }
 713 
 714     //该方法计算settle的时间
 715     private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) {
 716         //clampMag(...)方法保证水平和垂直速度值不大于最大值, 也不小于最小值.
 717         xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity);
 718         yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity);
 719         final int absDx = Math.abs(dx);
 720         final int absDy = Math.abs(dy);
 721         final int absXVel = Math.abs(xvel);
 722         final int absYVel = Math.abs(yvel);
 723         final int addedVel = absXVel + absYVel;
 724         final int addedDistance = absDx + absDy;
 725 
 726         final float xweight = xvel != 0 ? (float) absXVel / addedVel :
 727                 (float) absDx / addedDistance;
 728         final float yweight = yvel != 0 ? (float) absYVel / addedVel :
 729                 (float) absDy / addedDistance;
 730         //要注意的是getViewHorizontalDragRange(...)方法默认返回0,但一般都会在创建helper时传进的mCallback中重写该方法
 731         int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child));
 732         int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child));
 733 
 734         return (int) (xduration * xweight + yduration * yweight);
 735     }
 736     
 737     //该方法计算settle的时间,三个输入的参数依次分别是:水平或垂直方向的移动距离,水平或垂直方向的速度大小,拖曳范围值
 738     private int computeAxisDuration(int delta, int velocity, int motionRange) {
 739         if (delta == 0) {
 740             return 0;
 741         }
 742 
 743         final int width = mParentView.getWidth();
 744         final int halfWidth = width / 2;
 745         final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width);
 746         final float distance = halfWidth + halfWidth *
 747                 distanceInfluenceForSnapDuration(distanceRatio);
 748 
 749         int duration;
 750         velocity = Math.abs(velocity);
 751         if (velocity > 0) {
 752             duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
 753         } else {
 754             final float range = (float) Math.abs(delta) / motionRange;
 755             duration = (int) ((range + 1) * BASE_SETTLE_DURATION);
 756         }
 757         return Math.min(duration, MAX_SETTLE_DURATION);
 758     }
 759 
 760     /**
 761      * 该方法通过最大和最小值,算出区间值.低于最小值返回0,大于最大值则返回最大值.
 762      * Clamp the magnitude of value for absMin and absMax.
 763      * If the value is below the minimum, it will be clamped to zero.
 764      * If the value is above the maximum, it will be clamped to the maximum.
 765      *
 766      * @param value Value to clamp
 767      * @param absMin Absolute value of the minimum significant value to return
 768      * @param absMax Absolute value of the maximum value to return
 769      * @return The clamped value with the same sign as <code>value</code>
 770      */
 771     private int clampMag(int value, int absMin, int absMax) {
 772         final int absValue = Math.abs(value);
 773         if (absValue < absMin) return 0;
 774         if (absValue > absMax) return value > 0 ? absMax : -absMax;
 775         return value;
 776     }
 777 
 778     /**
 779      * 这个方法和上面的clampMag(int value, int absMin, int absMax)几乎一样,只是换了浮点型.
 780      * Clamp the magnitude of value for absMin and absMax.
 781      * If the value is below the minimum, it will be clamped to zero.
 782      * If the value is above the maximum, it will be clamped to the maximum.
 783      *
 784      * @param value Value to clamp
 785      * @param absMin Absolute value of the minimum significant value to return
 786      * @param absMax Absolute value of the maximum value to return
 787      * @return The clamped value with the same sign as <code>value</code>
 788      */
 789     private float clampMag(float value, float absMin, float absMax) {
 790         final float absValue = Math.abs(value);
 791         if (absValue < absMin) return 0;
 792         if (absValue > absMax) return value > 0 ? absMax : -absMax;
 793         return value;
 794     }
 795 
 796     private float distanceInfluenceForSnapDuration(float f) {
 797         f -= 0.5f; // center the values about 0.
 798         f *= 0.3f * Math.PI / 2.0f;
 799         return (float) Math.sin(f);
 800     }
 801 
 802     /**
 803      * 该方法类似上面的forceSettleCapturedViewAt(...),可参考之.
 804      * 
 805      * Settle the captured view based on standard free-moving fling behavior.
 806      * The caller should invoke {@link #continueSettling(boolean)} on each subsequent frame
 807      * to continue the motion until it returns false.
 808      *
 809      * @param minLeft Minimum X position for the view's left edge
 810      * @param minTop Minimum Y position for the view's top edge
 811      * @param maxLeft Maximum X position for the view's left edge
 812      * @param maxTop Maximum Y position for the view's top edge
 813      */
 814     public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {
 815         if (!mReleaseInProgress) {
 816             throw new IllegalStateException("Cannot flingCapturedView outside of a call to " +
 817                     "Callback#onViewReleased");
 818         }
 819 
 820         mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),
 821                 (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
 822                 (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),
 823                 minLeft, maxLeft, minTop, maxTop);
 824 
 825         setDragState(STATE_SETTLING);
 826     }
 827 
 828     /**
 829      * 这个方法在上面好几地方都被提及了.
 830      * 在整个settle的过程中,这个方法会返回true.直至返回false,表示settle的过程结束.
 831      * (该方法是内部调用的,外部建议不适用.)
 832      * 
 833      * Move the captured settling view by the appropriate amount for the current time.
 834      * If <code>continueSettling</code> returns true, the caller should call it again
 835      * on the next frame to continue.
 836      *
 837      * 参数deferCallbacks - 如果要推迟滑动,比如在{@link android.view.View#computeScroll()}里面回调,或者view还在layout或者draw
 838      * 的过程中,该参数应当传true;
 839      * @param deferCallbacks true if state callbacks should be deferred via posted message.
 840      *                       Set this to true if you are calling this method from
 841      *                       {@link android.view.View#computeScroll()} or similar methods
 842      *                       invoked as part of layout or drawing.
 843      * @return true if settle is still in progress
 844      */
 845     public boolean continueSettling(boolean deferCallbacks) {
 846         if (mDragState == STATE_SETTLING) {
 847             // 由于整个settle的过程都借助Scroller去实现,
 848             // 因此keepGoing这个值也来自mScroller.computeScrollOffset();
 849             // mScroller.computeScrollOffset()这方法,表示只要view处于scroll状态,都会返回true.停止scroll则返回false.
 850             boolean keepGoing = mScroller.computeScrollOffset();
 851             final int x = mScroller.getCurrX();
 852             final int y = mScroller.getCurrY();
 853             final int dx = x - mCapturedView.getLeft();
 854             final int dy = y - mCapturedView.getTop();
 855 
 856             if (dx != 0) {
 857                 mCapturedView.offsetLeftAndRight(dx);
 858             }
 859             if (dy != 0) {
 860                 mCapturedView.offsetTopAndBottom(dy);
 861             }
 862 
 863             if (dx != 0 || dy != 0) {
 864                 // 可见该方法在整个settle的过程中,由于位置的不断变化
 865                 // 会一直回调mCallback.onViewPositionChanged(...)的方法
 866                 mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy);
 867             }
 868             
 869             //这里很明显,当view已经去到最终位置,XY的坐标均相等时,即使keepGoing依然为true,系统以为
 870             //该view依旧处于滑动中,但很显然,应该结束了.于是方法里面强制调用Scroller.abortAnimation()去中止动画,并
 871             //向mScroller标记完成状态.keepGoing自然就为false了.
 872             if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) {
 873                 // Close enough. The interpolator/scroller might think we're still moving
 874                 // but the user sure doesn't.
 875                 mScroller.abortAnimation();
 876                 keepGoing = false;
 877             }
 878             
 879             //此处推迟滑动,借助Runable接口去实现
 880             if (!keepGoing) {
 881                 if (deferCallbacks) {
 882                     mParentView.post(mSetIdleRunnable);
 883                 } else {
 884                     //来到这里,keepGoing和deferCallbacks为false,表示整个settle过程都结束了.
 885                     //更改拖曳状态,continueSettling(...)不会再被回调.
 886                     setDragState(STATE_IDLE);
 887                 }
 888             }
 889         }
 890 
 891         return mDragState == STATE_SETTLING;
 892     }
 893 
 894     /**
 895      * (该方法是当完成settle过程后释放捕获到的view对象, 内部方法,不必了解详细过程.)
 896      * 正如所有接口事件的方法,这个方法也必须在UI主线程中使用.在释放的过程中,只会调用一次
 897      * {@link #settleCapturedViewAt(int, int)}或者{@link #flingCapturedView(int, int, int, int)}方法.
 898      * 
 899      * Like all callback events this must happen on the UI thread, but release
 900      * involves some extra semantics. During a release (mReleaseInProgress)
 901      * is the only time it is valid to call {@link #settleCapturedViewAt(int, int)}
 902      * or {@link #flingCapturedView(int, int, int, int)}.
 903      */
 904     private void dispatchViewReleased(float xvel, float yvel) {
 905         mReleaseInProgress = true;
 906         mCallback.onViewReleased(mCapturedView, xvel, yvel);
 907         mReleaseInProgress = false;
 908 
 909         if (mDragState == STATE_DRAGGING) {
 910             // onViewReleased didn't call a method that would have changed this. Go idle.
 911             setDragState(STATE_IDLE);
 912         }
 913     }
 914 
 915     //下面几个"clear"为首的方法都是清空历史记录了
 916     private void clearMotionHistory() {
 917         if (mInitialMotionX == null) {
 918             return;
 919         }
 920         Arrays.fill(mInitialMotionX, 0);
 921         Arrays.fill(mInitialMotionY, 0);
 922         Arrays.fill(mLastMotionX, 0);
 923         Arrays.fill(mLastMotionY, 0);
 924         Arrays.fill(mInitialEdgesTouched, 0);
 925         Arrays.fill(mEdgeDragsInProgress, 0);
 926         Arrays.fill(mEdgeDragsLocked, 0);
 927         mPointersDown = 0;
 928     }
 929 
 930     private void clearMotionHistory(int pointerId) {
 931         if (mInitialMotionX == null) {
 932             return;
 933         }
 934         mInitialMotionX[pointerId] = 0;
 935         mInitialMotionY[pointerId] = 0;
 936         mLastMotionX[pointerId] = 0;
 937         mLastMotionY[pointerId] = 0;
 938         mInitialEdgesTouched[pointerId] = 0;
 939         mEdgeDragsInProgress[pointerId] = 0;
 940         mEdgeDragsLocked[pointerId] = 0;
 941         mPointersDown &= ~(1 << pointerId);
 942     }
 943     
 944     //这个方法很明显是内部调用的,在saveInitialMotion(...)中调用.
 945     //因为mInitialMotionX数组里面保存有触摸X坐标的缓存信息,该方法确保mInitialMotionX一直保存最新的pointerId值
 946     private void ensureMotionHistorySizeForId(int pointerId) {
 947         if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) {
 948             float[] imx = new float[pointerId + 1];
 949             float[] imy = new float[pointerId + 1];
 950             float[] lmx = new float[pointerId + 1];
 951             float[] lmy = new float[pointerId + 1];
 952             int[] iit = new int[pointerId + 1];
 953             int[] edip = new int[pointerId + 1];
 954             int[] edl = new int[pointerId + 1];
 955             
 956             //这个过程,将触摸的X,Y坐标,上次触摸的X,Y坐标等信息复制过去
 957             if (mInitialMotionX != null) {
 958                 //这里调用本地C方法去将mInitialMotionX的内存复制给imx数组,没源码...
 959                 System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length);
 960                 System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length);
 961                 System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length);
 962                 System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length);
 963                 System.arraycopy(mInitialEdgesTouched, 0, iit, 0, mInitialEdgesTouched.length);
 964                 System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length);
 965                 System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length);
 966             }
 967 
 968             mInitialMotionX = imx;
 969             mInitialMotionY = imy;
 970             mLastMotionX = lmx;
 971             mLastMotionY = lmy;
 972             mInitialEdgesTouched = iit;
 973             mEdgeDragsInProgress = edip;
 974             mEdgeDragsLocked = edl;
 975         }
 976     }
 977 
 978     // 在这里,连同pointerId,保存X,Y轴坐标信息
 979     // 也许看到这里,你已经猜到,pointerId这个值是递增的,由系统自动分配.
 980     private void saveInitialMotion(float x, float y, int pointerId) {
 981         ensureMotionHistorySizeForId(pointerId);
 982         mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;
 983         mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;
 984         mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y);
 985         // 或运算后再进行左移运算. 
 986         mPointersDown |= 1 << pointerId;
 987     }
 988 
 989     private void saveLastMotion(MotionEvent ev) {
 990         final int pointerCount = MotionEventCompat.getPointerCount(ev);
 991         for (int i = 0; i < pointerCount; i++) {
 992             final int pointerId = MotionEventCompat.getPointerId(ev, i);
 993             final float x = MotionEventCompat.getX(ev, i);
 994             final float y = MotionEventCompat.getY(ev, i);
 995             mLastMotionX[pointerId] = x;
 996             mLastMotionY[pointerId] = y;
 997         }
 998     }
 999 
1000     /**
1001      * 检查给定id的pointer是否当前按下的pointer.
1002      * Check if the given pointer ID represents a pointer that is currently down (to the best
1003      * of the ViewDragHelper's knowledge).
1004      *
1005      * 被用于报告这个pointer信息的有以下几个方法:shouldInterceptTouchEvent()和processTouchEvent().
1006      * 如果这其中一个方法都没有被相关的触摸事件回调,那么该方法中所汇报的信息是不准确或者过时的.
1007      * (很明显,最新的触摸信息,必须是当前InterceptTouchEvent事件中能回调的.)
1008      * 
1009      * <p>The state used to report this information is populated by the methods
1010      * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or
1011      * {@link #processTouchEvent(android.view.MotionEvent)}. If one of these methods has not
1012      * been called for all relevant MotionEvents to track, the information reported
1013      * by this method may be stale or incorrect.</p>
1014      *
1015      * @param pointerId pointer ID to check; corresponds to IDs provided by MotionEvent
1016      * @return true if the pointer with the given ID is still down
1017      */
1018     public boolean isPointerDown(int pointerId) {
1019         return (mPointersDown & 1 << pointerId) != 0;
1020     }
1021 
1022     void setDragState(int state) {
1023         mParentView.removeCallbacks(mSetIdleRunnable);
1024         if (mDragState != state) {
1025             mDragState = state;
1026             mCallback.onViewDragStateChanged(state);
1027             if (mDragState == STATE_IDLE) {
1028                 mCapturedView = null;
1029             }
1030         }
1031     }
1032 
1033     /**
1034      * 通过传进的pointerId,试图捕获view.如果之前已成功捕获过,则不再调用mCallback.tryCaptureView()方法,而直接返回true.
1035      * Attempt to capture the view with the given pointer ID. The callback will be involved.
1036      * This will put us into the "dragging" state. If we've already captured this view with
1037      * this pointer this method will immediately return true without consulting the callback.
1038      *
1039      * @param toCapture View to capture
1040      * @param pointerId Pointer to capture with
1041      * @return true if capture was successful
1042      */
1043     boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
1044         if (toCapture == mCapturedView && mActivePointerId == pointerId) {
1045             // Already done!
1046             return true;
1047         }
1048         if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) {
1049             mActivePointerId = pointerId;
1050             captureChildView(toCapture, pointerId);
1051             return true;
1052         }
1053         return false;
1054     }
1055 
1056     /**
1057      * 测试是否view v是否能滑动
1058      * Tests scrollability within child views of v given a delta of dx.
1059      *
1060      * @param v View to test for horizontal scrollability
1061      * @param checkV Whether the view v passed should itself be checked for scrollability (true),
1062      *               or just its children (false).
1063      * @param dx Delta scrolled in pixels along the X axis
1064      * @param dy Delta scrolled in pixels along the Y axis
1065      * @param x X coordinate of the active touch point
1066      * @param y Y coordinate of the active touch point
1067      * @return true if child views of v can be scrolled by delta of dx.
1068      */
1069     protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) {
1070         if (v instanceof ViewGroup) {
1071             final ViewGroup group = (ViewGroup) v;
1072             final int scrollX = v.getScrollX();
1073             final int scrollY = v.getScrollY();
1074             final int count = group.getChildCount();
1075             // Count backwards - let topmost views consume scroll distance first.
1076             for (int i = count - 1; i >= 0; i--) {
1077                 // TODO: Add versioned support here for transformed views.
1078                 // This will not work for transformed views in Honeycomb+
1079                 final View child = group.getChildAt(i);
1080                 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
1081                         y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
1082                         canScroll(child, true, dx, dy, x + scrollX - child.getLeft(),
1083                                 y + scrollY - child.getTop())) {
1084                     return true;
1085                 }
1086             }
1087         }
1088 
1089         return checkV && (ViewCompat.canScrollHorizontally(v, -dx) ||
1090                 ViewCompat.canScrollVertically(v, -dy));
1091     }
1092 
1093     /**
1094      * 检测这个作为被提供给父view的onInterceptTouchEvent的事件是否令父view拦截到当前的触摸事件流.
1095      * Check if this event as provided to the parent view's onInterceptTouchEvent should
1096      * cause the parent to intercept the touch event stream.
1097      *
1098      * @param ev MotionEvent provided to onInterceptTouchEvent - 提供给onInterceptTouchEvent()方法的触摸事件对象
1099      * @return true if the parent view should return true from onInterceptTouchEvent
1100      */
1101     public boolean shouldInterceptTouchEvent(MotionEvent ev) {
1102         final int action = MotionEventCompat.getActionMasked(ev);
1103         final int actionIndex = MotionEventCompat.getActionIndex(ev);
1104 
1105         if (action == MotionEvent.ACTION_DOWN) {
1106             // Reset things for a new event stream, just in case we didn't get
1107             // the whole previous stream.
1108             cancel();
1109         }
1110 
1111         if (mVelocityTracker == null) {
1112             mVelocityTracker = VelocityTracker.obtain();
1113         }
1114         mVelocityTracker.addMovement(ev);
1115 
1116         switch (action) {
1117             case MotionEvent.ACTION_DOWN: {
1118                 final float x = ev.getX();
1119                 final float y = ev.getY();
1120                 final int pointerId = MotionEventCompat.getPointerId(ev, 0);
1121                 saveInitialMotion(x, y, pointerId);
1122 
1123                 final View toCapture = findTopChildUnder((int) x, (int) y);
1124 
1125                 // Catch a settling view if possible.
1126                 if (toCapture == mCapturedView && mDragState == STATE_SETTLING) {
1127                     tryCaptureViewForDrag(toCapture, pointerId);
1128                 }
1129 
1130                 final int edgesTouched = mInitialEdgesTouched[pointerId];
1131                 if ((edgesTouched & mTrackingEdges) != 0) {
1132                     mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
1133                 }
1134                 break;
1135             }
1136 
1137             case MotionEventCompat.ACTION_POINTER_DOWN: {
1138                 final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
1139                 final float x = MotionEventCompat.getX(ev, actionIndex);
1140                 final float y = MotionEventCompat.getY(ev, actionIndex);
1141 
1142                 saveInitialMotion(x, y, pointerId);
1143 
1144                 // A ViewDragHelper can only manipulate one view at a time.
1145                 if (mDragState == STATE_IDLE) {
1146                     final int edgesTouched = mInitialEdgesTouched[pointerId];
1147                     if ((edgesTouched & mTrackingEdges) != 0) {
1148                         mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
1149                     }
1150                 } else if (mDragState == STATE_SETTLING) {
1151                     // Catch a settling view if possible.
1152                     final View toCapture = findTopChildUnder((int) x, (int) y);
1153                     if (toCapture == mCapturedView) {
1154                         tryCaptureViewForDrag(toCapture, pointerId);
1155                     }
1156                 }
1157                 break;
1158             }
1159 
1160             case MotionEvent.ACTION_MOVE: {
1161                 // First to cross a touch slop over a draggable view wins. Also report edge drags.
1162                 final int pointerCount = MotionEventCompat.getPointerCount(ev);
1163                 for (int i = 0; i < pointerCount; i++) {
1164                     final int pointerId = MotionEventCompat.getPointerId(ev, i);
1165                     final float x = MotionEventCompat.getX(ev, i);
1166                     final float y = MotionEventCompat.getY(ev, i);
1167                     final float dx = x - mInitialMotionX[pointerId];
1168                     final float dy = y - mInitialMotionY[pointerId];
1169 
1170                     final View toCapture = findTopChildUnder((int) x, (int) y);
1171                     final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);
1172                     if (pastSlop) {
1173                         // check the callback's
1174                         // getView[Horizontal|Vertical]DragRange methods to know
1175                         // if you can move at all along an axis, then see if it
1176                         // would clamp to the same value. If you can't move at
1177                         // all in every dimension with a nonzero range, bail.
1178                         final int oldLeft = toCapture.getLeft();
1179                         final int targetLeft = oldLeft + (int) dx;
1180                         final int newLeft = mCallback.clampViewPositionHorizontal(toCapture,
1181                                 targetLeft, (int) dx);
1182                         final int oldTop = toCapture.getTop();
1183                         final int targetTop = oldTop + (int) dy;
1184                         final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop,
1185                                 (int) dy);
1186                         final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
1187                                 toCapture);
1188                         final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
1189                         if ((horizontalDragRange == 0 || horizontalDragRange > 0
1190                                 && newLeft == oldLeft) && (verticalDragRange == 0
1191                                 || verticalDragRange > 0 && newTop == oldTop)) {
1192                             break;
1193                         }
1194                     }
1195                     reportNewEdgeDrags(dx, dy, pointerId);
1196                     if (mDragState == STATE_DRAGGING) {
1197                         // Callback might have started an edge drag
1198                         break;
1199                     }
1200 
1201                     if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
1202                         break;
1203                     }
1204                 }
1205                 saveLastMotion(ev);
1206                 break;
1207             }
1208 
1209             case MotionEventCompat.ACTION_POINTER_UP: {
1210                 final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
1211                 clearMotionHistory(pointerId);
1212                 break;
1213             }
1214 
1215             case MotionEvent.ACTION_UP:
1216             case MotionEvent.ACTION_CANCEL: {
1217                 cancel();
1218                 break;
1219             }
1220         }
1221 
1222         return mDragState == STATE_DRAGGING;
1223     }
1224 
1225     /**
1226      * 加工从父view中获取的触摸事件.这个方法将分发callback回调事件.父view的触摸事件实现中应该调用该方法.
1227      * Process a touch event received by the parent view. This method will dispatch callback events
1228      * as needed before returning. The parent view's onTouchEvent implementation should call this.
1229      *
1230      * @param ev The touch event received by the parent view
1231      */
1232     public void processTouchEvent(MotionEvent ev) {
1233         final int action = MotionEventCompat.getActionMasked(ev);
1234         final int actionIndex = MotionEventCompat.getActionIndex(ev);
1235 
1236         if (action == MotionEvent.ACTION_DOWN) {
1237             // Reset things for a new event stream, just in case we didn't get
1238             // the whole previous stream.
1239             cancel();
1240         }
1241 
1242         if (mVelocityTracker == null) {
1243             mVelocityTracker = VelocityTracker.obtain();
1244         }
1245         mVelocityTracker.addMovement(ev);
1246 
1247         switch (action) {
1248             case MotionEvent.ACTION_DOWN: {
1249                 final float x = ev.getX();
1250                 final float y = ev.getY();
1251                 final int pointerId = MotionEventCompat.getPointerId(ev, 0);
1252                 final View toCapture = findTopChildUnder((int) x, (int) y);
1253 
1254                 saveInitialMotion(x, y, pointerId);
1255 
1256                 // Since the parent is already directly processing this touch event,
1257                 // there is no reason to delay for a slop before dragging.
1258                 // Start immediately if possible.
1259                 tryCaptureViewForDrag(toCapture, pointerId);
1260 
1261                 final int edgesTouched = mInitialEdgesTouched[pointerId];
1262                 if ((edgesTouched & mTrackingEdges) != 0) {
1263                     mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
1264                 }
1265                 break;
1266             }
1267 
1268             case MotionEventCompat.ACTION_POINTER_DOWN: {
1269                 final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
1270                 final float x = MotionEventCompat.getX(ev, actionIndex);
1271                 final float y = MotionEventCompat.getY(ev, actionIndex);
1272 
1273                 saveInitialMotion(x, y, pointerId);
1274 
1275                 // A ViewDragHelper can only manipulate one view at a time.
1276                 if (mDragState == STATE_IDLE) {
1277                     // If we're idle we can do anything! Treat it like a normal down event.
1278 
1279                     final View toCapture = findTopChildUnder((int) x, (int) y);
1280                     tryCaptureViewForDrag(toCapture, pointerId);
1281 
1282                     final int edgesTouched = mInitialEdgesTouched[pointerId];
1283                     if ((edgesTouched & mTrackingEdges) != 0) {
1284                         mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
1285                     }
1286                 } else if (isCapturedViewUnder((int) x, (int) y)) {
1287                     // We're still tracking a captured view. If the same view is under this
1288                     // point, we'll swap to controlling it with this pointer instead.
1289                     // (This will still work if we're "catching" a settling view.)
1290 
1291                     tryCaptureViewForDrag(mCapturedView, pointerId);
1292                 }
1293                 break;
1294             }
1295 
1296             case MotionEvent.ACTION_MOVE: {
1297                 if (mDragState == STATE_DRAGGING) {
1298                     final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1299                     final float x = MotionEventCompat.getX(ev, index);
1300                     final float y = MotionEventCompat.getY(ev, index);
1301                     final int idx = (int) (x - mLastMotionX[mActivePointerId]);
1302                     final int idy = (int) (y - mLastMotionY[mActivePointerId]);
1303 
1304                     dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);
1305 
1306                     saveLastMotion(ev);
1307                 } else {
1308                     // Check to see if any pointer is now over a draggable view.
1309                     final int pointerCount = MotionEventCompat.getPointerCount(ev);
1310                     for (int i = 0; i < pointerCount; i++) {
1311                         final int pointerId = MotionEventCompat.getPointerId(ev, i);
1312                         final float x = MotionEventCompat.getX(ev, i);
1313                         final float y = MotionEventCompat.getY(ev, i);
1314                         final float dx = x - mInitialMotionX[pointerId];
1315                         final float dy = y - mInitialMotionY[pointerId];
1316 
1317                         reportNewEdgeDrags(dx, dy, pointerId);
1318                         if (mDragState == STATE_DRAGGING) {
1319                             // Callback might have started an edge drag.
1320                             break;
1321                         }
1322 
1323                         final View toCapture = findTopChildUnder((int) x, (int) y);
1324                         if (checkTouchSlop(toCapture, dx, dy) &&
1325                                 tryCaptureViewForDrag(toCapture, pointerId)) {
1326                             break;
1327                         }
1328                     }
1329                     saveLastMotion(ev);
1330                 }
1331                 break;
1332             }
1333 
1334             case MotionEventCompat.ACTION_POINTER_UP: {
1335                 final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
1336                 if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) {
1337                     // Try to find another pointer that's still holding on to the captured view.
1338                     int newActivePointer = INVALID_POINTER;
1339                     final int pointerCount = MotionEventCompat.getPointerCount(ev);
1340                     for (int i = 0; i < pointerCount; i++) {
1341                         final int id = MotionEventCompat.getPointerId(ev, i);
1342                                       
View Code

 

posted @ 2015-06-28 20:15  CoolRandy  阅读(615)  评论(0)    收藏  举报