Android的ViewDragHelper源码解析
2016-04-20 17:17
441 查看
此文章是转自以为仁兄的
其实我想看的是DrawerLayout, 但发现DrawerLayout里面是使用了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提供的接口方法有监听这种状态变更的方法.
view sourceprint?
其实我想看的是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 几个点说到这,下面贴上源码解析. 有点长... 晚了, 具体的应用找个时间放在后面的文章再接着写.view sourceprint?
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.* 0008.* http://www.apache.org/licenses/LICENSE-2.0 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);
相关文章推荐
- Android Studio CheckOut Android项目配置
- TableLayout和霓虹灯效果、网格
- android 6.0 以上代码中必须询问是否获取到权限
- Android Studio技巧 --新学的几个快捷键
- Android支持的视频格式
- Android开发:新建android虚拟机时没有Use Host GPU选项(Emulator without GPU emulation detected.)
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- Android事件分发机制完全解析
- android 百度地图定位开发1
- Android计时器chronometer使用实例讲解
- 关于 Android 进程保活,你所需要知道的一切
- Android布局控件之TableLayout
- [Android教程] android读取sim联系人资料的代码
- android xml布局之———include.merge.ViewStub
- [Android教程] Android系统自定义实现日历控件
- Android AutoLayout全新的适配方式 堪称适配终结者
- [Android教程] android获得json数据并处理
- 学习使用Android Chronometer计时器
- 关于MPAndroidChart柱状图左右滑动
- MVP模式在Android中的应用实例