您的位置:首页 > 移动开发 > Android开发

Android的ViewDragHelper源码解析

2016-04-20 17:17 441 查看
此文章是转自以为仁兄的

其实我想看的是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);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: