转载自: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,
0
,
0
);
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.