Android的ViewDragHelper源码解析

转载自:http://www.it165.net/pro/html/201505/40127.html

其实我想看的是DrawerLayout, 但发现DrawerLayout里面是使用了ViewDragHelper去实现. 谷歌比较早就放出这个类了,但ViewDragHelper是开发中很少用到一个类.顾名思义这是一个和拖曳触摸有关的类. 本着追根溯源的想法, 加上ViewDragHelper的源码也不算多,就决定将ViewDragHelper的源码看一遍.对实现原理了解下.

代码一千多行,看完还是需要点时间的. 因此不会逐一讲完, 当然下面也会放出该类源码的解析,注释中也有一些个人理解的点写在里面. 有兴趣可以看看.所以这里直接讲下概括的东西,也足以对ViewDragHelper有个了解, 起码不至于陌生.

ViewDragHelper的原理和几个点:

原理:

ViewDragHelper定义大量的常量状态值( 比如设定边缘触发拖曳事件的临界值,边缘大小, 可拖曳的边缘标记等等 )用于监听整个拖曳的开始到结束的过程. 这个过程主要分为三个步骤: 闲置状态( IDLE ), 运动( DRAG 或者 SETTLE ) , 闲置状态( IDLE ).

要知道,要令父view中的子view发生位移, 除了手动拖曳令子view同步跟着手势运动, 还可以通过设定结束位置的X,Y坐标和LEFT,TOP值来直接使子view移动到该指定位置. 前者是拖曳( DRAG ),拖曳的过程是有过渡动画效果的. 而后者就是SETTLE (这个词不好翻译, 直译是"安置"), 没有过渡动画, 就辣么直接出现在指定的位置.

但无论是前者还是后者, 整个拖曳或settle的过程, 一开始时子view是静止的, 叫闲置态( IDLE ), 然后手指在屏幕触摸拖曳, 当手势运动超过触发拖曳的临界值,子view就会被拖曳或者被settle, 当移动的过程结束, 又会变成闲置态(IDLE).

说说几个点:

1, ViewDragHelper的拖曳,比如drag,settle这些操作,是通过Scroller去实现的. (Scroller 是滑动事件的辅助类,非常有用, 写过类似上下拉等滑动效果的童鞋应该用过.)

2, ViewDragHelper的是有记录历史功能的, 事件的过程会将触摸的X,Y坐标存在数组中, 然后通过本地的System.arraycopy(Object src, int srcPos, Object dst, int dstPos, int length)方法,传递旧的的触摸坐标值到数组中保存.这使得拖曳过程是连贯的.

3, 有历史记录,当然也有清空记录的功能. ViewDragHelper提供了cancel()方法,类似onTouch的ACTION_UP事件. 当拖曳结束,或settle的过程结束, 可能系统还认为过程还在, 因此就需要提供的cancel()或abort()方法去终止这个过程,同时也自动调用clearMotionHistory()方法,置空历史记录.确保下次触摸拖曳事件是"新的开始".

4, 要理解触摸的点( pointer ),触摸事件和子view,父view在拖曳或settle中的相互关系.

手指触摸屏幕,刚接触屏幕肯定有一个点是先碰到的, 然后深按下去,手指的一定面积触摸到屏幕, 就意味着触摸动作是由一定数量的点( pointer )组成.这些pointer都有自己的X,Y坐标. 而能够响应拖曳或settle触摸事件的父view中如果子view所处的位置保证这些pointer是落在子view内的, 那这个子view才能被捕获到( be catured ). 子view被捕获到, 才能发生我们所看到的拖曳或settle过程. 当然,因为ViewDragHelper给view的边缘设定了大小, 也设定了触发view拖曳事件的临界值, 只有手指在屏幕边缘移动大于这临界值,才能使子view运动.(具体过程可参考源码)

5, ViewDragHelper的触摸拖曳事件,是针对父view最顶层的子view才会响应该事件.这个应该不难理解吧. 所以该类提供了public View findTopChildUnder(int x, int y)方法捕获父view中最顶层的子view对象.

6, ViewDragHelper 提供了强大的通讯接口 - Callback. 这是一个静态抽象类,定义了相当齐全的监听拖曳或settle过程各种状态的接口方法:

void onViewDragStateChanged(int state); //当拖曳状态变更时回调该方法

void onViewPositionChanged(View changedView, int left, int top, int dx, int dy); //当捕获view由于拖曳或者设定而发生位置变更时回调

void onViewCaptured(View capturedChild, int activePointerId); //当子view被由于拖曳或被settle, 而被捕获时回调的方法.

void onViewReleased(View releasedChild, float xvel, float yvel); //当子view不再被拖曳时调用.如果有需要,fling的速度也会被提供.速度值会介于系统最小化和最大值之间.

void onEdgeTouched(int edgeFlags, int pointerId); //当父view其中一个被标记可拖曳的边缘被用户触摸, 同时父view里没有子view被捕获响应时回调该方法.

boolean onEdgeLock(int edgeFlags); //当原来可以拖曳的边缘被锁定不可拖曳时回调

void onEdgeDragStarted(int edgeFlags, int pointerId); //当用户开始从父view中"订阅的"(之前约定允许拖曳的)屏幕边缘拖曳,并且父view中没有子view响应时调用.

等等.

Callback是理解ViewDragHelper的关键,因为它反映了开始移动到结束的监听过程.对了解ViewDrag事件有很大帮助.

7, 因为移动的子view肯定是View的子类, 因此ViewDragHelper还有延迟移动的功能,借助View的post()方法, Runnable接口实现.

8, ViewDragHelper通过EDGE_LEFT, EDGE_RIGHT, EDGE_TOP和EDGE_BOTTOM 来标记哪个边缘可被拖曳或settle. 从可拖曳或settle标记为不可拖曳或settle, 这个过程叫做被锁了( locked ). 因为CallBack提供的接口方法有监听这种状态变更的方法.

类源码

Then 几个点说到这,下面贴上源码解析. 有点长... 晚了, 具体的应用找个时间放在后面的文章再接着写.

0001. /*
0002. * Copyright (C) 2013 The <a href="http://www.it165.net/pro/ydad/" target="_blank" class="keylink">Android</a> Open Source Project
0003. *
0004. * Licensed under the Apache License, Version 2.0 (the "License");
0005. * you may not use this file except in compliance with the License.
0006. * You may obtain a copy of the License at
0007. *
0009. *
0010. * Unless required by applicable law or agreed to in writing, software
0011. * distributed under the License is distributed on an "AS IS" BASIS,
0012. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013. * See the License for the specific language governing permissions and
0014. * limitations under the License.
0015. */
0016.  
0017.  
0018. package android.support.v4.widget;
0019.  
0020. import android.content.Context;
0021. import android.support.v4.view.MotionEventCompat;
0022. import android.support.v4.view.VelocityTrackerCompat;
0023. import android.support.v4.view.ViewCompat;
0024. import android.view.MotionEvent;
0025. import android.view.VelocityTracker;
0026. import android.view.View;
0027. import android.view.ViewConfiguration;
0028. import android.view.ViewGroup;
0029. import android.view.animation.Interpolator;
0030.  
0031. import java.util.Arrays;
0032.  
0033. /**
0034. * @tranlator AlexTam
0035. * ViewDragHelper是自定义ViewGroup时的实用类.它提供大量有用的操作和状态,来追踪用户在父View内的
0036. * 拖曳子view和重新定位子view.(看到这里,估计就会想,要同步监听拖曳事件,肯定少不了在onTouch事件中随处用到的
0037. * MotionEvent这个对象.是的,下面的确有它.)
0038. *
0039. * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number
0040. * of useful operations and state tracking for allowing a user to drag and reposition
0041. * views within their parent ViewGroup.
0042. */
0043. public class ViewDragHelper {
0044. private static final String TAG = "ViewDragHelper";
0045.  
0046. /**
0047. * 空/无效的pointer ID
0048. * A null/invalid pointer ID.
0049. */
0050. public static final int INVALID_POINTER = -1;
0051.  
0052. /**
0053. * 状态量:(IDLE是闲置的 意思)表示 view当前没有被拖曳或者运行的动画结束
0054. * A view is not currently being dragged or animating as a result of a fling/snap.
0055. */
0056. public static final int STATE_IDLE = 0;
0057.  
0058. /**
0059. * 状态量: view当前正被拖曳.根据用户的输入或者模拟用户的输入,view当前位置发生改变.(表示,
0060. * 用户怎么拖曳移动,view就根据拖曳的动作而发生位置改变.)
0061. * A view is currently being dragged. The position is currently changing as a result
0062. * of user input or simulated user input.
0063. */
0064. public static final int STATE_DRAGGING = 1;
0065.  
0066. /**
0067. * 状态量: 由于fling动作,或者预定的无交互的运动,view被"安置"到一个结束的地方.(可以想象,一个view被用户快速拖曳
0068. * 并甩动,从而view被甩到某个结束的位置,的过程.)
0069. * A view is currently settling into place as a result of a fling or
0070. * predefined non-interactive motion.
0071. */
0072. public static final int STATE_SETTLING = 2;
0073.  
0074. /**
0075. * 标记可从左边缘拖曳.
0076. * Edge flag indicating that the left edge should be affected.
0077. */
0078. public static final int EDGE_LEFT = 1 << 0;
0079.  
0080. /**
0081. * 标记可从右边缘拖曳.
0082. * Edge flag indicating that the right edge should be affected.
0083. */
0084. public static final int EDGE_RIGHT = 1 << 1;
0085.  
0086. /**
0087. * 标记可从顶部拖曳.
0088. * Edge flag indicating that the top edge should be affected.
0089. */
0090. public static final int EDGE_TOP = 1 << 2;
0091.  
0092. /**
0093. * 标记可从底部拖曳.
0094. * Edge flag indicating that the bottom edge should be affected.
0095. */
0096. public static final int EDGE_BOTTOM = 1 << 3;
0097.  
0098. /**
0099. * 标记所有地方(边缘的上下左右)都能被拖曳.
0100. * Edge flag set indicating all edges should be affected.
0101. */
0102. public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM;
0103.  
0104. /**
0105. * 指引值:
0106. *
0107. * 表示a check(指引) 应该沿着水平轴发生.
0108. * Indicates that a check should occur along the horizontal axis
0109. */
0110. public static final int DIRECTION_HORIZONTAL = 1 << 0;
0111.  
0112. /**
0113. * 表示a check 应该沿着垂直轴发生.
0114. * Indicates that a check should occur along the vertical axis
0115. */
0116. public static final int DIRECTION_VERTICAL = 1 << 1;
0117.  
0118. /**
0119. * 表示a check可水平可垂直的发生.
0120. * Indicates that a check should occur along all axes
0121. */
0122. public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
0123.  
0124. //将边缘大小定位20dp
0125. private static final int EDGE_SIZE = 20// dp
0126.  
0127. //时间值
0128. private static final int BASE_SETTLE_DURATION = 256// ms
0129. private static final int MAX_SETTLE_DURATION = 600// ms
0130.  
0131. // 当前的拖曳状态,值为idle, dragging or settling.
0132. // Current drag state; idle, dragging or settling
0133. private int mDragState;
0134.  
0135. // 在拖曳开始前的滑动位移.(可以这么理解,触发拖曳的最大临界值.)
0136. // Distance to travel before a drag may begin
0137. private int mTouchSlop;
0138.  
0139. // 上一次的位置或点
0140. // Last known position/pointer tracking
0141. private int mActivePointerId = INVALID_POINTER;
0142. //初始化的X坐标
0143. private float[] mInitialMotionX;
0144. //初始化的Y坐标
0145. private float[] mInitialMotionY;
0146. //下面这些变量不写了噻,看名字也能知道.
0147. private float[] mLastMotionX;
0148. private float[] mLastMotionY;
0149. private int[] mInitialEdgesTouched;
0150. private int[] mEdgeDragsInProgress;
0151. private int[] mEdgeDragsLocked;
0152. private int mPointersDown;
0153.  
0154. private VelocityTracker mVelocityTracker;
0155. private float mMaxVelocity;
0156. private float mMinVelocity;
0157. //边缘的大小,单位px
0158. private int mEdgeSize;
0159. private int mTrackingEdges;
0160.  
0161. //兼容新API所提供的Scroller
0162. private ScrollerCompat mScroller;
0163.  
0164. //内部抽象类,提供一些规范的接口方法
0165. private final Callback mCallback;
0166.  
0167. private View mCapturedView;
0168. private boolean mReleaseInProgress;
0169.  
0170. private final ViewGroup mParentView;
0171.  
0172. /**
0173. * 这个Callback是作为通信接口,当ViewDragHelper返回父view时使用."on"为首的方法是重要事件的回调方法,几个
0174. * 接口方法用于提供更多关于请求父view的状态的信息给ViewDragHelper.这个抽象类同时提供子view拖曳的一些细节信息.
0175. *
0176. * A Callback is used as a communication channel with the ViewDragHelper back to the
0177. * parent view using it. <code>on*</code>methods are invoked on siginficant events and several
0178. * accessor methods are expected to provide the ViewDragHelper with more information
0179. * about the state of the parent view upon request. The callback also makes decisions
0180. * governing the range and draggability of child views.
0181. */
0182. public static abstract class Callback {
0183. /**
0184. * 当拖曳状态变更时回调该方法.可看"STATE_"为首的常量了解更多信息.
0185. * Called when the drag state changes. See the <code>STATE_*</code> constants
0186. * for more information.
0187. *
0188. * @param state The new drag state
0189. *
0190. * @see #STATE_IDLE
0191. * @see #STATE_DRAGGING
0192. * @see #STATE_SETTLING
0193. */
0194. public void onViewDragStateChanged(int state) {}
0195.  
0196. /**
0197. * 当捕获view由于拖曳或者设定而发生位置变更时回调..
0198. * Called when the captured view's position changes as the result of a drag or settle.
0199. *
0200. * @param changedView View whose position changed - 发生位置变更的view
0201. * @param left New X coordinate of the left edge of the view - 新的左边缘X坐标
0202. * @param top New Y coordinate of the top edge of the view  - 新的顶部边缘Y坐标
0203. * @param dx Change in X position from the last call    - 从旧到新位置发生的X偏移值
0204. * @param dy Change in Y position from the last call    - 从旧到新位置发生的Y偏移值
0205. */
0206. public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}
0207.  
0208. /**
0209. * 当子view被由于拖曳或设置(settle有点难翻译)而被捕获时回调的方法.提供拖曳的pointer的ID.
0210. * 如果activePointerId被标记为{@link #INVALID_POINTER},它会代替没有初始化的pointer.
0211. *
0212. * Called when a child view is captured for dragging or settling. The ID of the pointer
0213. * currently dragging the captured view is supplied. If activePointerId is
0214. * identified as {@link #INVALID_POINTER} the capture is programmatic instead of
0215. * pointer-initiated.
0216. *
0217. * @param capturedChild Child view that was captured
0218. * @param activePointerId Pointer id tracking the child capture
0219. */
0220. public void onViewCaptured(View capturedChild, int activePointerId) {}
0221.  
0222. /**
0223. * 当子view不再被拖曳时调用.如果有需要,fling的速度也会被提供.速度值会介于系统最小化和最大值之间.
0224. *
0225. * Called when the child view is no longer being actively dragged.
0226. * The fling velocity is also supplied, if relevant. The velocity values may
0227. * be clamped to system minimums or maximums.
0228. *
0229. * <p>Calling code may decide to fling or otherwise release the view to let it
0230. * settle into place. It should do so using {@link #settleCapturedViewAt(int, int)}
0231. * or {@link #flingCapturedView(int, int, int, int)}. If the Callback invokes
0232. * one of these methods, the ViewDragHelper will enter {@link #STATE_SETTLING}
0233. * and the view capture will not fully end until it comes to a complete stop.
0234. * If neither of these methods is invoked before <code>onViewReleased</code> returns,
0235. * the view will stop in place and the ViewDragHelper will return to
0236. * {@link #STATE_IDLE}.</p>
0237. *
0238. * @param releasedChild The captured child view now being released
0239. *      - 被捕获到的要释放的子view
0240. * @param xvel X velocity of the pointer as it left the screen in pixels per second.
0241. *      - pointer离开屏幕X轴方向每秒运动的速率,单位是px.
0242. * @param yvel Y velocity of the pointer as it left the screen in pixels per second.
0243. *      - pointer离开屏幕Y轴方向每秒运动的速率,单位是px.
0244. */
0245. public void onViewReleased(View releasedChild, float xvel, float yvel) {}
0246.  
0247. /**
0248. * 当父view其中一个被标记可拖曳的边缘被用户触摸, 同时父view里没有子view被捕获响应时回调该方法.
0249. * Called when one of the subscribed edges in the parent view has been touched
0250. * by the user while no child view is currently captured.
0251. *
0252. * @param edgeFlags A combination of edge flags describing the edge(s) currently touched
0253. *          - 描述所当前所触摸的位置的边缘标记, 如EDGE_LEFT,EDGE_RIGHT等等.
0254. * @param pointerId ID of the pointer touching the described edge(s)
0255. *          - 触摸的点的ID.
0256. *
0257. * @see #EDGE_LEFT
0258. * @see #EDGE_TOP
0259. * @see #EDGE_RIGHT
0260. * @see #EDGE_BOTTOM
0261. */
0262. public void onEdgeTouched(int edgeFlags, int pointerId) {}
0263.  
0264. /**
0265. * 该方法当原来可以拖曳的边缘被锁定不可拖曳时回调.如果边缘在初始化开始拖曳前被拒绝拖曳,就会发生前面说的这种情况.
0266. * 但这个方法会在{@link #onEdgeTouched(int, int)}之后才会被回调.这个方法会返回true来锁定该边缘.或者
0267. * 返回false来释放解锁该屏幕.默认的行为是后者(返回false来释放解锁该屏幕).
0268. *
0269. * Called when the given edge may become locked. This can happen if an edge drag
0270. * was preliminarily rejected before beginning, but after {@link #onEdgeTouched(int, int)}
0271. * was called. This method should return true to lock this edge or false to leave it
0272. * unlocked. The default behavior is to leave edges unlocked.
0273. *
0274. * @param edgeFlags A combination of edge flags describing the edge(s) locked
0275. *      - 描述被锁定的边缘的边缘标记,如EDGE_LEFT等.
0276. * @return true to lock the edge, false to leave it unlocked
0277. *      - 返回true来锁定该边缘.或者 返回false来释放解锁该屏幕.
0278. */
0279. public boolean onEdgeLock(int edgeFlags) {
0280. return false;
0281. }
0282.  
0283. /**
0284. * 当用户开始从父view中"订阅的"(之前约定允许拖曳的)屏幕边缘拖曳,并且父view中没有子view响应时调用.
0285. *
0286. * Called when the user has started a deliberate drag away from one
0287. * of the subscribed edges in the parent view while no child view is currently captured.
0288. *
0289. * @param edgeFlags A combination of edge flags describing the edge(s) dragged
0290. *      - 描述该边缘的边缘标记,如EDGE_LEFT等.
0291. * @param pointerId ID of the pointer touching the described edge(s)
0292. *      - pointer的ID.
0293. * @see #EDGE_LEFT
0294. * @see #EDGE_TOP
0295. * @see #EDGE_RIGHT
0296. * @see #EDGE_BOTTOM
0297. */
0298. public void onEdgeDragStarted(int edgeFlags, int pointerId) {}
0299.  
0300. /**
0301. * 调用设置子view z轴次序的参数.
0302. * Called to determine the Z-order of child views.
0303. *
0304. * @param index the ordered position to query for
0305. * @return index of the view that should be ordered at position <code>index</code>
0306. */
0307. public int getOrderedChildIndex(int index) {
0308. return index;
0309. }
0310.  
0311. /**
0312. * 返回拖曳的子view水平移动范围的值,单位为px.这个方法如果返回0,那么该view则不能水平移动.
0313. * Return the magnitude of a draggable child view's horizontal range of motion in pixels.
0314. * This method should return 0 for views that cannot move horizontally.
0315. *
0316. * @param child Child view to check - 目标子view
0317. * @return range of horizontal motion in pixels - 水平拖曳的值,单位为px.
0318. */
0319. public int getViewHorizontalDragRange(View child) {
0320. return 0;
0321. }
0322.  
0323. /**
0324. * 返回拖曳的子view垂直移动范围的值,单位为px.这个方法如果返回0,那么该view则不能垂直移动.
0325. * Return the magnitude of a draggable child view's vertical range of motion in pixels.
0326. * This method should return 0 for views that cannot move vertically.
0327. *
0328. * @param child Child view to check
0329. * @return range of vertical motion in pixels
0330. */
0331. public int getViewVerticalDragRange(View child) {
0332. return 0;
0333. }
0334.  
0335. /**
0336. * 当用户通过pointerId 输入特定值令目标子view移动时回调该方法.callback接口如果返回true,则表示用户
0337. * 允许通过用于引导的pointer来拖曳该子view.
0338. * Called when the user's input indicates that they want to capture the given child view
0339. * with the pointer indicated by pointerId. The callback should return true if the user
0340. * is permitted to drag the given view with the indicated pointer.
0341. *
0342. * 如果该子view已经被捕获, ViewDragHelper可能多次重复的调用该方法.多次的调用会导致新的pointer尝试去控制这个view.
0343. * <p>ViewDragHelper may call this method multiple times for the same view even if
0344. * the view is already captured; this indicates that a new pointer is trying to take
0345. * control of the view.</p>
0346. *
0347. * 如果该方法返回true,并且当成功捕获到该子view时,方法{@link #onViewCaptured(android.view.View, int)}会随即被调用.
0348. * <p>If this method returns true, a call to {@link #onViewCaptured(android.view.View, int)}
0349. * will follow if the capture is successful.</p>
0350. *
0351. * @param child Child the user is attempting to capture - 用户视图捕获的子view
0352. * @param pointerId ID of the pointer attempting the capture    - 捕获该子view的pointerID.
0353. * @return true if capture should be allowed, false otherwise   - 如果允许并且捕获成功应该返回true.否则返回false.
0354. */
0355. public abstract boolean tryCaptureView(View child, int pointerId);
0356.  
0357. /**
0358. * 该方法用于限制子view沿水平拖曳的手势.默认的实现是,不允许水平手势.如果有类继承了该类,
0359. * 必须覆盖重写该方法,并且提供值去限制该拖曳手势.
0360. * Restrict the motion of the dragged child view along the horizontal axis.
0361. * The default implementation does not allow horizontal motion; the extending
0362. * class must override this method and provide the desired clamping.
0363. *
0364. *
0365. * @param child Child view being dragged    -  被拖曳的子view.
0366. * @param left Attempted motion along the X axis    - 沿X轴(水平)的手势
0367. * @param dx Proposed change in position for left   - view的left变更值
0368. * @return The new clamped position for left - 对left返回新的位置值
0369. */
0370. public int clampViewPositionHorizontal(View child, int left, int dx) {
0371. return 0;
0372. }
0373.  
0374. /**
0375. * 该方法用于限制子view沿垂直拖曳的手势.默认的实现是,不允许垂直手势...(同上面的方法类似,就不过多解释了.)
0376. * Restrict the motion of the dragged child view along the vertical axis.
0377. * The default implementation does not allow vertical motion; the extending
0378. * class must override this method and provide the desired clamping.
0379. *
0380. *
0381. * @param child Child view being dragged
0382. * @param top Attempted motion along the Y axis
0383. * @param dy Proposed change in position for top
0384. * @return The new clamped position for top
0385. */
0386. public int clampViewPositionVertical(View child, int top, int dy) {
0387. return 0;
0388. }
0389. }
0390.  
0391. /**
0392. * 定义曲线动画的插值器
0393. * Interpolator defining the animation curve for mScroller
0394. */
0395. private static final Interpolator sInterpolator = new Interpolator() {
0396. public float getInterpolation(float t) {
0397. t -= 1.0f;
0398. return t * t * t * t * t + 1.0f;
0399. }
0400. };
0401.  
0402. // 实现Runnable接口
0403. private final Runnable mSetIdleRunnable = new Runnable() {
0404. public void run() {
0405. setDragState(STATE_IDLE);
0406. }
0407. };
0408.  
0409. /**
0410. * 创建ViewDragHelper的工厂方法
0411. * Factory method to create a new ViewDragHelper.
0412. *
0413. * @param forParent Parent view to monitor  - 所要监听的父view
0414. * @param cb Callback to provide information and receive events - 提供信息的Callback对象
0415. * @return a new ViewDragHelper instance
0416. */
0417. public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
0418. return new ViewDragHelper(forParent.getContext(), forParent, cb);
0419. }
0420.  
0421. /**
0422. * Factory method to create a new ViewDragHelper.
0423. *
0424. * @param forParent Parent view to monitor
0425. * @param sensitivity Multiplier for how sensitive the helper should be about detecting
0426. *                    the start of a drag. Larger values are more sensitive. 1.0f is normal.
0427. * @param cb Callback to provide information and receive events
0428. * @return a new ViewDragHelper instance
0429. */
0430. public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
0431. final ViewDragHelper helper = create(forParent, cb);
0432. helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
0433. return helper;
0434. }
0435.  
0436. /**
0437. * 应用应该使用ViewDragHelper.create()去获取新的实例.这将允许ViewDragHelper使用内部实现去兼容不同的平台版本.
0438. * Apps should use ViewDragHelper.create() to get a new instance.
0439. * This will allow VDH to use internal compatibility implementations for different
0440. * platform versions.
0441. *
0442. * @param context Context to initialize config-dependent params from
0443. * @param forParent Parent view to monitor
0444. */
0445. private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) {
0446. if (forParent == null) {
0447. throw new IllegalArgumentException("Parent view may not be null");
0448. }
0449. if (cb == null) {
0450. throw new IllegalArgumentException("Callback may not be null");
0451. }
0452.  
0453. mParentView = forParent;
0454. mCallback = cb;
0455.  
0456. // ViewConfiguration是一个包含配置信息,如时间,位移等的配置类.
0457. final ViewConfiguration vc = ViewConfiguration.get(context);
0458. final float density = context.getResources().getDisplayMetrics().density;
0459. mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);
0460.  
0461. mTouchSlop = vc.getScaledTouchSlop();
0462. mMaxVelocity = vc.getScaledMaximumFlingVelocity();
0463. mMinVelocity = vc.getScaledMinimumFlingVelocity();
0464. mScroller = ScrollerCompat.create(context, sInterpolator);
0465. }
0466.  
0467. /**
0468. * 设置最小速率.大于0px/s的速率能更好的被检测到.这样Callback就能恰当的运用该值去约束移动的速率.
0469. * Set the minimum velocity that will be detected as having a magnitude greater than zero
0470. * in pixels per second. Callback methods accepting a velocity will be clamped appropriately.
0471. *
0472. * @param minVel Minimum velocity to detect
0473. */
0474. public void setMinVelocity(float minVel) {
0475. mMinVelocity = minVel;
0476. }
0477.  
0478. /**
0479. * 获取最小速率. 值得注意的是,如果最小速率小于0, 那么直接返回0,不会返回比0小的值.
0480. * Return the currently configured minimum velocity. Any flings with a magnitude less
0481. * than this value in pixels per second. Callback methods accepting a velocity will receive
0482. * zero as a velocity value if the real detected velocity was below this threshold.
0483. *
0484. * @return the minimum velocity that will be detected
0485. */
0486. public float getMinVelocity() {
0487. return mMinVelocity;
0488. }
0489.  
0490. /**
0491. * 获取当前helper的拖曳状态,返回结果为{@link #STATE_IDLE}, {@link #STATE_DRAGGING}
0492. * or {@link #STATE_SETTLING}.中的其一.
0493. *
0494. * Retrieve the current drag state of this helper. This will return one of
0495. * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
0496. * @return The current drag state
0497. */
0498. public int getViewDragState() {
0499. return mDragState;
0500. }
0501.  
0502. /**
0503. * 设置允许父view的某个边缘可追踪.CallBack对象的{@link Callback#onEdgeTouched(int, int)} and
0504. * {@link Callback#onEdgeDragStarted(int, int)}方法只有在边缘允许被追踪时才会调用.
0505. * (就是说,如果不设置上下左右的某个边缘可追踪,那么这2个方法是不可用的.)
0506. *
0507. * Enable edge tracking for the selected edges of the parent view.
0508. * The callback's {@link Callback#onEdgeTouched(int, int)} and
0509. * {@link Callback#onEdgeDragStarted(int, int)} methods will only be invoked
0510. * for edges for which edge tracking has been enabled.
0511. *
0512. * @param edgeFlags Combination of edge flags describing the edges to watch
0513. * @see #EDGE_LEFT
0514. * @see #EDGE_TOP
0515. * @see #EDGE_RIGHT
0516. * @see #EDGE_BOTTOM
0517. */
0518. public void setEdgeTrackingEnabled(int edgeFlags) {
0519. mTrackingEdges = edgeFlags;
0520. }
0521.  
0522. /**
0523. * 返回边缘大小的值.单位为px.这个值是该view边缘可以被监测或追踪的值的范围.
0524. * Return the size of an edge. This is the range in pixels along the edges of this view
0525. * that will actively detect edge touches or drags if edge tracking is enabled.
0526. *
0527. * @return The size of an edge in pixels
0528. * @see #setEdgeTrackingEnabled(int)
0529. */
0530. public int getEdgeSize() {
0531. return mEdgeSize;
0532. }
0533.  
0534. /**
0535. * 在父view内捕获指定的子view用于拖曳.同时callback对象会被通知.但{@link Callback#tryCaptureView(android.view.View, int)}
0536. * 不会被要求获取权限来捕获该view.
0537. *
0538. * Capture a specific child view for dragging within the parent. The callback will be notified
0539. * but {@link Callback#tryCaptureView(android.view.View, int)} will not be asked permission to
0540. * capture this view.
0541. *
0542. * @param childView Child view to capture
0543. * @param activePointerId ID of the pointer that is dragging the captured child view
0544. */
0545. public void captureChildView(View childView, int activePointerId) {
0546. if (childView.getParent() != mParentView) {
0547. throw new IllegalArgumentException("captureChildView: parameter must be a descendant " +
0548. "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
0549. }
0550.  
0551. mCapturedView = childView;
0552. mActivePointerId = activePointerId;
0553. mCallback.onViewCaptured(childView, activePointerId);
0554. setDragState(STATE_DRAGGING);
0555. }
0556.  
0557. /**
0558. * 返回当前捕获的view.如果没有捕获到的view,则返回null.
0559. * @return The currently captured view, or null if no view has been captured.
0560. */
0561. public View getCapturedView() {
0562. return mCapturedView;
0563. }
0564.  
0565. /**
0566. * 当前拖曳捕获的view的点(pointer)的ID.
0567. * @return The ID of the pointer currently dragging the captured view,
0568. *         or {@link #INVALID_POINTER}.
0569. */
0570. public int getActivePointerId() {
0571. return mActivePointerId;
0572. }
0573.  
0574. /**
0575. * 获取最小触发和初始化拖曳动作的值,单位px.
0576. * @return The minimum distance in pixels that the user must travel to initiate a drag
0577. */
0578. public int getTouchSlop() {
0579. return mTouchSlop;
0580. }
0581.  
0582. /**
0583. * 这方法等价于onTouch中MotionEvent的ACTION_CANCEL事件.
0584. * The result of a call to this method is equivalent to
0585. * {@link #processTouchEvent(android.view.MotionEvent)} receiving an ACTION_CANCEL event.
0586. */
0587. public void cancel() {
0588. mActivePointerId = INVALID_POINTER;
0589. clearMotionHistory();
0590.  
0591. if (mVelocityTracker != null) {
0592. mVelocityTracker.recycle();
0593. mVelocityTracker = null;
0594. }
0595. }
0596.  
0597. /**
0598. * 中止取所有手势.并且直接结束动画.
0599. * {@link #cancel()}, but also abort all motion in progress and snap to the end of any
0600. * animation.
0601. */
0602. public void abort() {
0603. cancel();
0604. if (mDragState == STATE_SETTLING) {
0605. final int oldX = mScroller.getCurrX();
0606. final int oldY = mScroller.getCurrY();
0607. mScroller.abortAnimation();
0608. final int newX = mScroller.getCurrX();
0609. final int newY = mScroller.getCurrY();
0610. mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY);
0611. }
0612. //中止了,当然要设置拖曳状态为闲置(或者说初始态)
0613. setDragState(STATE_IDLE);
0614. }
0615.  
0616. /**
0617. * (使用这个方法,可以有动画效果的移动子view到特定位置,该位置需要给出的finalLeft和 finalTop值.)
0618. * 随着动画,子view移动到既定(给定left和top值)的位置.如果这个方法返回true,会在后面随着手势移动的
0619. * 每一帧中回调{@link #continueSettling(boolean)}方法,直至返回false.如果这个方法返回false,
0620. * 就不会再移动去完成手势动作的事件.
0621. *
0622. * Animate the view <code>child</code> to the given (left, top) position.
0623. * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
0624. * on each subsequent frame to continue the motion until it returns false. If this method
0625. * returns false there is no further work to do to complete the movement.
0626. *
0627. * 要注意的是,即使方法{@link #getCapturedView()}在这个滑动过程中仍会一直有效,可以获取catureView的值,
0628. * 但这个操作过程不看做是一个捕获事件(我们应当知道,捕获子view不是我们决定的,是Helper自动在父view和
0629. * 子view之间去自动完成的过程,无论这个过程成功还是失败).
0630. *
0631. * <p>This operation does not count as a capture event, though {@link #getCapturedView()}
0632. * will still report the sliding view while the slide is in progress.</p>
0633. *
0634. * @param child Child view to capture and animate - 要捕获和添加动画移动的view对象
0635. * @param finalLeft Final left position of child - 最终位置的left值
0636. * @param finalTop Final top position of child - 最终位置的top值
0637. * @return true if animation should continue through {@link #continueSettling(boolean)} calls
0638. */
0639. public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {
0640. mCapturedView = child;
0641. mActivePointerId = INVALID_POINTER;
0642.  
0643. boolean continueSliding = forceSettleCapturedViewAt(finalLeft, finalTop, 00);
0644. if (!continueSliding && mDragState == STATE_IDLE && mCapturedView != null) {
0645. // If we're in an IDLE state to begin with and aren't moving anywhere, we
0646. // end up having a non-null capturedView with an IDLE dragState
0647. mCapturedView = null;
0648. }
0649.  
0650. return continueSliding;
0651. }
0652.  
0653. /**
0654. * (通过这个方法,我们应当知道settle和slide的区别.前者是直接跳到结束位置,而后者是有过渡效果的.)
0655. * 将捕获的view设置(settle)在给定的left,top值的位置.(表示,直接忽略过程,直接将view显示在特定位置)
0656. * 这个过程中,该view(如果在此时已经有)适当的速度,则该速度会影响settle的过程.
0657. * 如果这个方法返回true,方法{@link #continueSettling(boolean)}在整个settle过程中会被回调,直至返回false.
0658. * 如果这个方法返回false,(表示此时该view已经在给定的位置)这个settle的过程就会结束,不会再工作完成事件.
0659. *
0660. * Settle the captured view at the given (left, top) position.
0661. * The appropriate velocity from prior motion will be taken into account.
0662. * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
0663. * on each subsequent frame to continue the motion until it returns false. If this method
0664. * returns false there is no further work to do to complete the movement.
0665. *
0666. * @param finalLeft Settled left edge position for the captured view
0667. * @param finalTop Settled top edge position for the captured view
0668. * @return true if animation should continue through {@link #continueSettling(boolean)} calls
0669. */
0670. public boolean settleCapturedViewAt(int finalLeft, int finalTop) {
0671. if (!mReleaseInProgress) {
0672. throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " +
0673. "Callback#onViewReleased");
0674. }
0675.  
0676. return forceSettleCapturedViewAt(finalLeft, finalTop,
0677. (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
0678. (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId));
0679. }
0680.  
0681. /**
0682. * 同样是将view直接设到特定位置(给定left, top值).
0683. * (看该方法的实现,整个过程 也是靠scroller的scroll去实现的).
0684. * Settle the captured view at the given (left, top) position.
0685. *
0686. * @param finalLeft Target left position for the captured view
0687. * @param finalTop Target top position for the captured view
0688. * @param xvel Horizontal velocity  - 水平速度
0689. * @param yvel Vertical velocity    - 垂直速度
0690. * @return true if animation should continue through {@link #continueSettling(boolean)} calls
0691. *      - settleing的过程中会一直返回true,否则返回false表示结束.
0692. */
0693. private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
0694. final int startLeft = mCapturedView.getLeft();
0695. final int startTop = mCapturedView.getTop();
0696. final int dx = finalLeft - startLeft;
0697. final int dy = finalTop - startTop;
0698.  
0699. if (dx == 0 && dy == 0) {
0700. // Nothing to do. Send callbacks, be done.
0701. mScroller.abortAnimation();
0702. setDragState(STATE_IDLE);
0703. return false;
0704. }
0705. // 仔细看computeSettleDuration()这个计算时间的方法,其实挺复杂的.使用了相当多的运算处理.因此可以不看该方法的实现;
0706. // 除非要继承ViewDrarHelper实现子类,实现更多效果...
0707. final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
0708. mScroller.startScroll(startLeft, startTop, dx, dy, duration);
0709.  
0710. setDragState(STATE_SETTLING);
0711. return true;
0712. }
0713.  
0714. //该方法计算settle的时间
0715. private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) {
0716. //clampMag(...)方法保证水平和垂直速度值不大于最大值, 也不小于最小值.
0717. xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity);
0718. yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity);
0719. final int absDx = Math.abs(dx);
0720. final int absDy = Math.abs(dy);
0721. final int absXVel = Math.abs(xvel);
0722. final int absYVel = Math.abs(yvel);
0723. final int addedVel = absXVel + absYVel;
0724. final int addedDistance = absDx + absDy;
0725.  
0726. final float xweight = xvel != 0 ? (float) absXVel / addedVel :
0727. (float) absDx / addedDistance;
0728. final float yweight = yvel != 0 ? (float) absYVel / addedVel :
0729. (float) absDy / addedDistance;
0730. //要注意的是getViewHorizontalDragRange(...)方法默认返回0,但一般都会在创建helper时传进的mCallback中重写该方法
0731. int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child));
0732. int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child));
0733.  
0734. return (int) (xduration * xweight + yduration * yweight);
0735. }
0736.  
0737. //该方法计算settle的时间,三个输入的参数依次分别是:水平或垂直方向的移动距离,水平或垂直方向的速度大小,拖曳范围值
0738. private int computeAxisDuration(int delta, int velocity, int motionRange) {
0739. if (delta == 0) {
0740. return 0;
0741. }
0742.  
0743. final int width = mParentView.getWidth();
0744. final int halfWidth = width / 2;
0745. final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width);
0746. final float distance = halfWidth + halfWidth *
0747. distanceInfluenceForSnapDuration(distanceRatio);
0748.  
0749. int duration;
0750. velocity = Math.abs(velocity);
0751. if (velocity > 0) {
0752. duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
0753. else {
0754. final float range = (float) Math.abs(delta) / motionRange;
0755. duration = (int) ((range + 1) * BASE_SETTLE_DURATION);
0756. }
0757. return Math.min(duration, MAX_SETTLE_DURATION);
0758. }
0759.  
0760. /**
0761. * 该方法通过最大和最小值,算出区间值.低于最小值返回0,大于最大值则返回最大值.
0762. * Clamp the magnitude of value for absMin and absMax.
0763. * If the value is below the minimum, it will be clamped to zero.
0764. * If the value is above the maximum, it will be clamped to the maximum.
0765. *
0766. * @param value Value to clamp
0767. * @param absMin Absolute value of the minimum significant value to return
0768. * @param absMax Absolute value of the maximum value to return
0769. * @return The clamped value with the same sign as <code>value</code>
0770. */
0771. private int clampMag(int value, int absMin, int absMax) {
0772. final int absValue = Math.abs(value);
0773. if (absValue < absMin) return 0;
0774. if (absValue > absMax) return value > 0 ? absMax : -absMax;
0775. return value;
0776. }
0777.  
0778. /**
0779. * 这个方法和上面的clampMag(int value, int absMin, int absMax)几乎一样,只是换了浮点型.
0780. * Clamp the magnitude of value for absMin and absMax.
0781. * If the value is below the minimum, it will be clamped to zero.
0782. * If the value is above the maximum, it will be clamped to the maximum.
0783. *
0784. * @param value Value to clamp
0785. * @param absMin Absolute value of the minimum significant value to return
0786. * @param absMax Absolute value of the maximum value to return
0787. * @return The clamped value with the same sign as <code>value</code>
0788. */
0789. private float clampMag(float value, float absMin, float absMax) {
0790. final float absValue = Math.abs(value);
0791. if (absValue < absMin) return 0;
0792. if (absValue > absMax) return value > 0 ? absMax : -absMax;
0793. return value;
0794. }
0795.  
0796. private float distanceInfluenceForSnapDuration(float f) {
0797. f -= 0.5f; // center the values about 0.
0798. f *= 0.3f * Math.PI / 2.0f;
0799. return (float) Math.sin(f);
0800. }
0801.  
0802. /**
0803. * 该方法类似上面的forceSettleCapturedViewAt(...),可参考之.
0804. *
0805. * Settle the captured view based on standard free-moving fling behavior.
0806. * The caller should invoke {@link #continueSettling(boolean)} on each subsequent frame
0807. * to continue the motion until it returns false.
0808. *
0809. * @param minLeft Minimum X position for the view's left edge
0810. * @param minTop Minimum Y position for the view's top edge
0811. * @param maxLeft Maximum X position for the view's left edge
0812. * @param maxTop Maximum Y position for the view's top edge
0813. */
0814. public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {
0815. if (!mReleaseInProgress) {
0816. throw new IllegalStateException("Cannot flingCapturedView outside of a call to " +
0817. "Callback#onViewReleased");
0818. }
0819.  
0820. mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),
0821. (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
0822. (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),
0823. minLeft, maxLeft, minTop, maxTop);
0824.  
0825. setDragState(STATE_SETTLING);
0826. }
0827.  
0828. /**
0829. * 这个方法在上面好几地方都被提及了.
0830. * 在整个settle的过程中,这个方法会返回true.直至返回false,表示settle的过程结束.
0831. * (该方法是内部调用的,外部建议不适用.)
0832. *
0833. * Move the captured settling view by the appropriate amount for the current time.
0834. * If <code>continueSettling</code> returns true, the caller should call it again
0835. * on the next frame to continue.
0836. *
0837. * 参数deferCallbacks - 如果要推迟滑动,比如在{@link android.view.View#computeScroll()}里面回调,或者view还在layout或者draw
0838. * 的过程中,该参数应当传true;
0839. * @param deferCallbacks true if state callbacks should be deferred via posted message.
0840. *                       Set this to true if you are calling this method from
0841. *                       {@link android.view.View#computeScroll()} or similar methods
0842. *                       invoked as part of layout or drawing.
0843. * @return true if settle is still in progress
0844. */
0845. public boolean continueSettling(boolean deferCallbacks) {
0846. if (mDragState == STATE_SETTLING) {
0847. // 由于整个settle的过程都借助Scroller去实现,
0848. // 因此keepGoing这个值也来自mScroller.computeScrollOffset();
0849. // mScroller.computeScrollOffset()这方法,表示只要view处于scroll状态,都会返回true.停止scroll则返回false.
0850. boolean keepGoing = mScroller.computeScrollOffset();
0851. final int x = mScroller.getCurrX();
0852. final int y = mScroller.getCurrY();
0853. final int dx = x - mCapturedView.getLeft();
0854. final int dy = y - mCapturedView.getTop();
0855.  
0856. if (dx != 0) {
0857. mCapturedView.offsetLeftAndRight(dx);
0858. }
0859. if (dy != 0) {
0860. mCapturedView.offsetTopAndBottom(dy);
0861. }
0862.  
0863. if (dx != 0 || dy != 0) {
0864. // 可见该方法在整个settle的过程中,由于位置的不断变化
0865. // 会一直回调mCallback.onViewPositionChanged(...)的方法
0866. mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy);
0867. }
0868.  
0869. //这里很明显,当view已经去到最终位置,XY的坐标均相等时,即使keepGoing依然为true,系统以为
0870. //该view依旧处于滑动中,但很显然,应该结束了.于是方法里面强制调用Scroller.abortAnimation()去中止动画,并
0871. //向mScroller标记完成状态.keepGoing自然就为false了.
0872. if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) {
0873. // Close enough. The interpolator/scroller might think we're still moving
0874. // but the user sure doesn't.
0875. mScroller.abortAnimation();
0876. keepGoing = false;
0877. }
0878.  
0879. //此处推迟滑动,借助Runable接口去实现
0880. if (!keepGoing) {
0881. if (deferCallbacks) {
0882. mParentView.post(mSetIdleRunnable);
0883. else {
0884. //来到这里,keepGoing和deferCallbacks为false,表示整个settle过程都结束了.
0885. //更改拖曳状态,continueSettling(...)不会再被回调.
0886. setDragState(STATE_IDLE);
0887. }
0888. }
0889. }
0890.  
0891. return mDragState == STATE_SETTLING;
0892. }
0893.  
0894. /**
0895. * (该方法是当完成settle过程后释放捕获到的view对象, 内部方法,不必了解详细过程.)
0896. * 正如所有接口事件的方法,这个方法也必须在UI主线程中使用.在释放的过程中,只会调用一次
0897. * {@link #settleCapturedViewAt(int, int)}或者{@link #flingCapturedView(int, int, int, int)}方法.
0898. *
0899. * Like all callback events this must happen on the UI thread, but release
0900. * involves some extra semantics. During a release (mReleaseInProgress)
0901. * is the only time it is valid to call {@link #settleCapturedViewAt(int, int)}
0902. * or {@link #flingCapturedView(int, int, int, int)}.
0903. */
0904. private void dispatchViewReleased(float xvel, float yvel) {
0905. mReleaseInProgress = true;
0906. mCallback.onViewReleased(mCapturedView, xvel, yvel);
0907. mReleaseInProgress = false;
0908.  
0909. if (mDragState == STATE_DRAGGING) {
0910. // onViewReleased didn't call a method that would have changed this. Go idle.
0911. setDragState(STATE_IDLE);
0912. }
0913. }
0914.  
0915. //下面几个"clear"为首的方法都是清空历史记录了
0916. private void clearMotionHistory() {
0917. if (mInitialMotionX == null) {
0918. return;
0919. }
0920. Arrays.fill(mInitialMotionX, 0);
0921. Arrays.fill(mInitialMotionY, 0);
0922. Arrays.fill(mLastMotionX, 0);
0923. Arrays.fill(mLastMotionY, 0);
0924. Arrays.fill(mInitialEdgesTouched, 0);
0925. Arrays.fill(mEdgeDragsInProgress, 0);
0926. Arrays.fill(mEdgeDragsLocked, 0);
0927. mPointersDown = 0;
0928. }
0929.  
0930. private void clearMotionHistory(int pointerId) {
0931. if (mInitialMotionX == null) {
0932. return;
0933. }
0934. mInitialMotionX[pointerId] = 0;
0935. mInitialMotionY[pointerId] = 0;
0936. mLastMotionX[pointerId] = 0;
0937. mLastMotionY[pointerId] = 0;
0938. mInitialEdgesTouched[pointerId] = 0;
0939. mEdgeDragsInProgress[pointerId] = 0;
0940. mEdgeDragsLocked[pointerId] = 0;
0941. mPointersDown &= ~(1 << pointerId);
0942. }
0943.  
0944. //这个方法很明显是内部调用的,在saveInitialMotion(...)中调用.
0945. //因为mInitialMotionX数组里面保存有触摸X坐标的缓存信息,该方法确保mInitialMotionX一直保存最新的pointerId值
0946. private void ensureMotionHistorySizeForId(int pointerId) {
0947. if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) {
0948. float[] imx = new float[pointerId + 1];
0949. float[] imy = new float[pointerId + 1];
0950. float[] lmx = new float[pointerId + 1];
0951. float[] lmy = new float[pointerId + 1];
0952. int[] iit = new int[pointerId + 1];
0953. int[] edip = new int[pointerId + 1];
0954. int[] edl = new int[pointerId + 1];
0955.  
0956. //这个过程,将触摸的X,Y坐标,上次触摸的X,Y坐标等信息复制过去
0957. if (mInitialMotionX != null) {
0958. //这里调用本地C方法去将mInitialMotionX的内存复制给imx数组,没源码...
0959. System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length);
0960. System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length);
0961. System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length);
0962. System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length);
0963. System.arraycopy(mInitialEdgesTouched, 0, iit, 0, mInitialEdgesTouched.length);
0964. System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length);
0965. System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length);
0966. }
0967.  
0968. mInitialMotionX = imx;
0969. mInitialMotionY = imy;
0970. mLastMotionX = lmx;
0971. mLastMotionY = lmy;
0972. mInitialEdgesTouched = iit;
0973. mEdgeDragsInProgress = edip;
0974. mEdgeDragsLocked = edl;
0975. }
0976. }
0977.  
0978. // 在这里,连同pointerId,保存X,Y轴坐标信息
0979. // 也许看到这里,你已经猜到,pointerId这个值是递增的,由系统自动分配.
0980. private void saveInitialMotion(float x, float y, int pointerId) {
0981. ensureMotionHistorySizeForId(pointerId);
0982. mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;
0983. mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;
0984. mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y);
0985. // 或运算后再进行左移运算.
0986. mPointersDown |= 1 << pointerId;
0987. }
0988.  
0989. private void saveLastMotion(MotionEvent ev) {
0990. final int pointerCount = MotionEventCompat.getPointerCount(ev);
0991. for (int i = 0; i < pointerCount; i++) {
0992. final int pointerId = MotionEventCompat.getPointerId(ev, i);
0993. final float x = MotionEventCompat.getX(ev, i);
0994. final float y = MotionEventCompat.getY(ev, i);
0995. mLastMotionX[pointerId] = x;
0996. mLastMotionY[pointerId] = y;
0997. }
0998. }
0999.  
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.  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值