22 | * Call this whenever the background of a translucent Activity has changed 23 | * to become opaque. Doing so will allow the {@link android.view.Surface} of 24 | * the Activity behind to be released. 25 | *
26 | * This call has no effect on non-translucent activities or on activities 27 | * with the {@link android.R.attr#windowIsFloating} attribute. 28 | */ 29 | public static void convertActivityFromTranslucent(Activity activity) { 30 | try { 31 | Method method = Activity.class.getDeclaredMethod("convertFromTranslucent"); 32 | method.setAccessible(true); 33 | method.invoke(activity); 34 | } catch (Throwable t) { 35 | } 36 | } 37 | 38 | /** 39 | * Convert a translucent themed Activity 40 | * {@link android.R.attr#windowIsTranslucent} back from opaque to 41 | * translucent following a call to 42 | * {@link #convertActivityFromTranslucent(android.app.Activity)} . 43 | *
44 | * Calling this allows the Activity behind this one to be seen again. Once 45 | * all such Activities have been redrawn 46 | *
47 | * This call has no effect on non-translucent activities or on activities
48 | * with the {@link android.R.attr#windowIsFloating} attribute.
49 | */
50 | public static void convertActivityToTranslucent(Activity activity) {
51 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
52 | convertActivityToTranslucentAfterL(activity);
53 | } else {
54 | convertActivityToTranslucentBeforeL(activity);
55 | }
56 | }
57 |
58 | /**
59 | * Calling the convertToTranslucent method on platforms before Android 5.0
60 | */
61 | public static void convertActivityToTranslucentBeforeL(Activity activity) {
62 | try {
63 | Class>[] classes = Activity.class.getDeclaredClasses();
64 | Class> translucentConversionListenerClazz = null;
65 | for (Class clazz : classes) {
66 | if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
67 | translucentConversionListenerClazz = clazz;
68 | }
69 | }
70 | Method method = Activity.class.getDeclaredMethod("convertToTranslucent",
71 | translucentConversionListenerClazz);
72 | method.setAccessible(true);
73 | method.invoke(activity, new Object[] {
74 | null
75 | });
76 | } catch (Throwable t) {
77 | }
78 | }
79 |
80 | /**
81 | * Calling the convertToTranslucent method on platforms after Android 5.0
82 | */
83 | private static void convertActivityToTranslucentAfterL(Activity activity) {
84 | try {
85 | Method getActivityOptions = Activity.class.getDeclaredMethod("getActivityOptions");
86 | getActivityOptions.setAccessible(true);
87 | Object options = getActivityOptions.invoke(activity);
88 |
89 | Class>[] classes = Activity.class.getDeclaredClasses();
90 | Class> translucentConversionListenerClazz = null;
91 | for (Class clazz : classes) {
92 | if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
93 | translucentConversionListenerClazz = clazz;
94 | }
95 | }
96 | Method convertToTranslucent = Activity.class.getDeclaredMethod("convertToTranslucent",
97 | translucentConversionListenerClazz, ActivityOptions.class);
98 | convertToTranslucent.setAccessible(true);
99 | convertToTranslucent.invoke(activity, null, options);
100 | } catch (Throwable t) {
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/library/src/main/java/me/imid/swipebacklayout/lib/ViewDragHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package me.imid.swipebacklayout.lib;
18 |
19 | import android.content.Context;
20 | import android.support.v4.view.MotionEventCompat;
21 | import android.support.v4.view.VelocityTrackerCompat;
22 | import android.support.v4.view.ViewCompat;
23 | import android.support.v4.widget.ScrollerCompat;
24 | import android.view.MotionEvent;
25 | import android.view.VelocityTracker;
26 | import android.view.View;
27 | import android.view.ViewConfiguration;
28 | import android.view.ViewGroup;
29 | import android.view.animation.Interpolator;
30 |
31 | import java.util.Arrays;
32 |
33 | /**
34 | * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a
35 | * number of useful operations and state tracking for allowing a user to drag
36 | * and reposition views within their parent ViewGroup.
37 | */
38 | public class ViewDragHelper {
39 | private static final String TAG = "ViewDragHelper";
40 |
41 | /**
42 | * A null/invalid pointer ID.
43 | */
44 | public static final int INVALID_POINTER = -1;
45 |
46 | /**
47 | * A view is not currently being dragged or animating as a result of a
48 | * fling/snap.
49 | */
50 | public static final int STATE_IDLE = 0;
51 |
52 | /**
53 | * A view is currently being dragged. The position is currently changing as
54 | * a result of user input or simulated user input.
55 | */
56 | public static final int STATE_DRAGGING = 1;
57 |
58 | /**
59 | * A view is currently settling into place as a result of a fling or
60 | * predefined non-interactive motion.
61 | */
62 | public static final int STATE_SETTLING = 2;
63 |
64 | /**
65 | * Edge flag indicating that the left edge should be affected.
66 | */
67 | public static final int EDGE_LEFT = 1 << 0;
68 |
69 | /**
70 | * Edge flag indicating that the right edge should be affected.
71 | */
72 | public static final int EDGE_RIGHT = 1 << 1;
73 |
74 | /**
75 | * Edge flag indicating that the top edge should be affected.
76 | */
77 | public static final int EDGE_TOP = 1 << 2;
78 |
79 | /**
80 | * Edge flag indicating that the bottom edge should be affected.
81 | */
82 | public static final int EDGE_BOTTOM = 1 << 3;
83 |
84 | /**
85 | * Edge flag set indicating all edges should be affected.
86 | */
87 | public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM;
88 |
89 | /**
90 | * Indicates that a check should occur along the horizontal axis
91 | */
92 | public static final int DIRECTION_HORIZONTAL = 1 << 0;
93 |
94 | /**
95 | * Indicates that a check should occur along the vertical axis
96 | */
97 | public static final int DIRECTION_VERTICAL = 1 << 1;
98 |
99 | /**
100 | * Indicates that a check should occur along all axes
101 | */
102 | public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
103 |
104 | public static final int EDGE_SIZE = 20; // dp
105 |
106 | private static final int BASE_SETTLE_DURATION = 256; // ms
107 |
108 | private static final int MAX_SETTLE_DURATION = 600; // ms
109 |
110 | // Current drag state; idle, dragging or settling
111 | private int mDragState;
112 |
113 | // Distance to travel before a drag may begin
114 | private int mTouchSlop;
115 |
116 | // Last known position/pointer tracking
117 | private int mActivePointerId = INVALID_POINTER;
118 |
119 | private float[] mInitialMotionX;
120 |
121 | private float[] mInitialMotionY;
122 |
123 | private float[] mLastMotionX;
124 |
125 | private float[] mLastMotionY;
126 |
127 | private int[] mInitialEdgeTouched;
128 |
129 | private int[] mEdgeDragsInProgress;
130 |
131 | private int[] mEdgeDragsLocked;
132 |
133 | private int mPointersDown;
134 |
135 | private VelocityTracker mVelocityTracker;
136 |
137 | private float mMaxVelocity;
138 |
139 | private float mMinVelocity;
140 |
141 | private int mEdgeSize;
142 |
143 | private int mTrackingEdges;
144 |
145 | private ScrollerCompat mScroller;
146 |
147 | private final Callback mCallback;
148 |
149 | private View mCapturedView;
150 |
151 | private boolean mReleaseInProgress;
152 |
153 | private final ViewGroup mParentView;
154 |
155 | /**
156 | * A Callback is used as a communication channel with the ViewDragHelper
157 | * back to the parent view using it. on*
methods are invoked on
158 | * siginficant events and several accessor methods are expected to provide
159 | * the ViewDragHelper with more information about the state of the parent
160 | * view upon request. The callback also makes decisions governing the range
161 | * and draggability of child views.
162 | */
163 | public static abstract class Callback {
164 | /**
165 | * Called when the drag state changes. See the STATE_*
166 | * constants for more information.
167 | *
168 | * @param state The new drag state
169 | * @see #STATE_IDLE
170 | * @see #STATE_DRAGGING
171 | * @see #STATE_SETTLING
172 | */
173 | public void onViewDragStateChanged(int state) {
174 | }
175 |
176 | /**
177 | * Called when the captured view's position changes as the result of a
178 | * drag or settle.
179 | *
180 | * @param changedView View whose position changed
181 | * @param left New X coordinate of the left edge of the view
182 | * @param top New Y coordinate of the top edge of the view
183 | * @param dx Change in X position from the last call
184 | * @param dy Change in Y position from the last call
185 | */
186 | public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
187 | }
188 |
189 | /**
190 | * Called when a child view is captured for dragging or settling. The ID
191 | * of the pointer currently dragging the captured view is supplied. If
192 | * activePointerId is identified as {@link #INVALID_POINTER} the capture
193 | * is programmatic instead of pointer-initiated.
194 | *
195 | * @param capturedChild Child view that was captured
196 | * @param activePointerId Pointer id tracking the child capture
197 | */
198 | public void onViewCaptured(View capturedChild, int activePointerId) {
199 | }
200 |
201 | /**
202 | * Called when the child view is no longer being actively dragged. The
203 | * fling velocity is also supplied, if relevant. The velocity values may
204 | * be clamped to system minimums or maximums.
205 | *
206 | * Calling code may decide to fling or otherwise release the view to let
207 | * it settle into place. It should do so using
208 | * {@link #settleCapturedViewAt(int, int)} or
209 | * {@link #flingCapturedView(int, int, int, int)}. If the Callback
210 | * invokes one of these methods, the ViewDragHelper will enter
211 | * {@link #STATE_SETTLING} and the view capture will not fully end until
212 | * it comes to a complete stop. If neither of these methods is invoked
213 | * before onViewReleased
returns, the view will stop in
214 | * place and the ViewDragHelper will return to {@link #STATE_IDLE}.
215 | *
index
278 | */
279 | public int getOrderedChildIndex(int index) {
280 | return index;
281 | }
282 |
283 | /**
284 | * Return the magnitude of a draggable child view's horizontal range of
285 | * motion in pixels. This method should return 0 for views that cannot
286 | * move horizontally.
287 | *
288 | * @param child Child view to check
289 | * @return range of horizontal motion in pixels
290 | */
291 | public int getViewHorizontalDragRange(View child) {
292 | return 0;
293 | }
294 |
295 | /**
296 | * Return the magnitude of a draggable child view's vertical range of
297 | * motion in pixels. This method should return 0 for views that cannot
298 | * move vertically.
299 | *
300 | * @param child Child view to check
301 | * @return range of vertical motion in pixels
302 | */
303 | public int getViewVerticalDragRange(View child) {
304 | return 0;
305 | }
306 |
307 | /**
308 | * Called when the user's input indicates that they want to capture the
309 | * given child view with the pointer indicated by pointerId. The
310 | * callback should return true if the user is permitted to drag the
311 | * given view with the indicated pointer.
312 | * 313 | * ViewDragHelper may call this method multiple times for the same view 314 | * even if the view is already captured; this indicates that a new 315 | * pointer is trying to take control of the view. 316 | *
317 | *318 | * If this method returns true, a call to 319 | * {@link #onViewCaptured(android.view.View, int)} will follow if the 320 | * capture is successful. 321 | *
322 | * 323 | * @param child Child the user is attempting to capture 324 | * @param pointerId ID of the pointer attempting the capture 325 | * @return true if capture should be allowed, false otherwise 326 | */ 327 | public abstract boolean tryCaptureView(View child, int pointerId); 328 | 329 | /** 330 | * Restrict the motion of the dragged child view along the horizontal 331 | * axis. The default implementation does not allow horizontal motion; 332 | * the extending class must override this method and provide the desired 333 | * clamping. 334 | * 335 | * @param child Child view being dragged 336 | * @param left Attempted motion along the X axis 337 | * @param dx Proposed change in position for left 338 | * @return The new clamped position for left 339 | */ 340 | public int clampViewPositionHorizontal(View child, int left, int dx) { 341 | return 0; 342 | } 343 | 344 | /** 345 | * Restrict the motion of the dragged child view along the vertical 346 | * axis. The default implementation does not allow vertical motion; the 347 | * extending class must override this method and provide the desired 348 | * clamping. 349 | * 350 | * @param child Child view being dragged 351 | * @param top Attempted motion along the Y axis 352 | * @param dy Proposed change in position for top 353 | * @return The new clamped position for top 354 | */ 355 | public int clampViewPositionVertical(View child, int top, int dy) { 356 | return 0; 357 | } 358 | } 359 | 360 | /** 361 | * Interpolator defining the animation curve for mScroller 362 | */ 363 | private static final Interpolator sInterpolator = new Interpolator() { 364 | public float getInterpolation(float t) { 365 | t -= 1.0f; 366 | return t * t * t * t * t + 1.0f; 367 | } 368 | }; 369 | 370 | private final Runnable mSetIdleRunnable = new Runnable() { 371 | public void run() { 372 | setDragState(STATE_IDLE); 373 | } 374 | }; 375 | 376 | /** 377 | * Factory method to create a new ViewDragHelper. 378 | * 379 | * @param forParent Parent view to monitor 380 | * @param cb Callback to provide information and receive events 381 | * @return a new ViewDragHelper instance 382 | */ 383 | public static ViewDragHelper create(ViewGroup forParent, Callback cb) { 384 | return new ViewDragHelper(forParent.getContext(), forParent, cb); 385 | } 386 | 387 | /** 388 | * Factory method to create a new ViewDragHelper. 389 | * 390 | * @param forParent Parent view to monitor 391 | * @param sensitivity Multiplier for how sensitive the helper should be 392 | * about detecting the start of a drag. Larger values are more 393 | * sensitive. 1.0f is normal. 394 | * @param cb Callback to provide information and receive events 395 | * @return a new ViewDragHelper instance 396 | */ 397 | public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) { 398 | final ViewDragHelper helper = create(forParent, cb); 399 | helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity)); 400 | return helper; 401 | } 402 | 403 | /** 404 | * Apps should use ViewDragHelper.create() to get a new instance. This will 405 | * allow VDH to use internal compatibility implementations for different 406 | * platform versions. 407 | * 408 | * @param context Context to initialize config-dependent params from 409 | * @param forParent Parent view to monitor 410 | */ 411 | private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) { 412 | if (forParent == null) { 413 | throw new IllegalArgumentException("Parent view may not be null"); 414 | } 415 | if (cb == null) { 416 | throw new IllegalArgumentException("Callback may not be null"); 417 | } 418 | 419 | mParentView = forParent; 420 | mCallback = cb; 421 | 422 | final ViewConfiguration vc = ViewConfiguration.get(context); 423 | final float density = context.getResources().getDisplayMetrics().density; 424 | mEdgeSize = (int) (EDGE_SIZE * density + 0.5f); 425 | 426 | mTouchSlop = vc.getScaledTouchSlop(); 427 | mMaxVelocity = vc.getScaledMaximumFlingVelocity(); 428 | mMinVelocity = vc.getScaledMinimumFlingVelocity(); 429 | mScroller = ScrollerCompat.create(context, sInterpolator); 430 | } 431 | 432 | /** 433 | * Sets the sensitivity of the dragger. 434 | * 435 | * @param context The application context. 436 | * @param sensitivity value between 0 and 1, the final value for touchSlop = 437 | * ViewConfiguration.getScaledTouchSlop * (1 / s); 438 | */ 439 | public void setSensitivity(Context context, float sensitivity) { 440 | float s = Math.max(0f, Math.min(1.0f, sensitivity)); 441 | ViewConfiguration viewConfiguration = ViewConfiguration.get(context); 442 | mTouchSlop = (int) (viewConfiguration.getScaledTouchSlop() * (1 / s)); 443 | } 444 | 445 | /** 446 | * Set the minimum velocity that will be detected as having a magnitude 447 | * greater than zero in pixels per second. Callback methods accepting a 448 | * velocity will be clamped appropriately. 449 | * 450 | * @param minVel minimum velocity to detect 451 | */ 452 | public void setMinVelocity(float minVel) { 453 | mMinVelocity = minVel; 454 | } 455 | 456 | /** 457 | * Set the max velocity that will be detected as having a magnitude 458 | * greater than zero in pixels per second. Callback methods accepting a 459 | * velocity will be clamped appropriately. 460 | * 461 | * @param maxVel max velocity to detect 462 | */ 463 | public void setMaxVelocity(float maxVel) { 464 | mMaxVelocity = maxVel; 465 | } 466 | 467 | /** 468 | * Return the currently configured minimum velocity. Any flings with a 469 | * magnitude less than this value in pixels per second. Callback methods 470 | * accepting a velocity will receive zero as a velocity value if the real 471 | * detected velocity was below this threshold. 472 | * 473 | * @return the minimum velocity that will be detected 474 | */ 475 | public float getMinVelocity() { 476 | return mMinVelocity; 477 | } 478 | 479 | /** 480 | * Retrieve the current drag state of this helper. This will return one of 481 | * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. 482 | * 483 | * @return The current drag state 484 | */ 485 | public int getViewDragState() { 486 | return mDragState; 487 | } 488 | 489 | /** 490 | * Enable edge tracking for the selected edges of the parent view. The 491 | * callback's 492 | * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeTouched(int, int)} 493 | * and 494 | * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeDragStarted(int, int)} 495 | * methods will only be invoked for edges for which edge tracking has been 496 | * enabled. 497 | * 498 | * @param edgeFlags Combination of edge flags describing the edges to watch 499 | * @see #EDGE_LEFT 500 | * @see #EDGE_TOP 501 | * @see #EDGE_RIGHT 502 | * @see #EDGE_BOTTOM 503 | */ 504 | public void setEdgeTrackingEnabled(int edgeFlags) { 505 | mTrackingEdges = edgeFlags; 506 | } 507 | 508 | /** 509 | * Return the size of an edge. This is the range in pixels along the edges 510 | * of this view that will actively detect edge touches or drags if edge 511 | * tracking is enabled. 512 | * 513 | * @return The size of an edge in pixels 514 | * @see #setEdgeTrackingEnabled(int) 515 | */ 516 | public int getEdgeSize() { 517 | return mEdgeSize; 518 | } 519 | 520 | /** 521 | * Set the size of an edge. This is the range in pixels along the edges of 522 | * this view that will actively detect edge touches or drags if edge 523 | * tracking is enabled. 524 | * 525 | * @param size The size of an edge in pixels 526 | */ 527 | public void setEdgeSize(int size) { 528 | mEdgeSize = size; 529 | } 530 | 531 | /** 532 | * Capture a specific child view for dragging within the parent. The 533 | * callback will be notified but 534 | * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#tryCaptureView(android.view.View, int)} 535 | * will not be asked permission to capture this view. 536 | * 537 | * @param childView Child view to capture 538 | * @param activePointerId ID of the pointer that is dragging the captured 539 | * child view 540 | */ 541 | public void captureChildView(View childView, int activePointerId) { 542 | if (childView.getParent() != mParentView) { 543 | throw new IllegalArgumentException("captureChildView: parameter must be a descendant " 544 | + "of the ViewDragHelper's tracked parent view (" + mParentView + ")"); 545 | } 546 | 547 | mCapturedView = childView; 548 | mActivePointerId = activePointerId; 549 | mCallback.onViewCaptured(childView, activePointerId); 550 | setDragState(STATE_DRAGGING); 551 | } 552 | 553 | /** 554 | * @return The currently captured view, or null if no view has been 555 | * captured. 556 | */ 557 | public View getCapturedView() { 558 | return mCapturedView; 559 | } 560 | 561 | /** 562 | * @return The ID of the pointer currently dragging the captured view, or 563 | * {@link #INVALID_POINTER}. 564 | */ 565 | public int getActivePointerId() { 566 | return mActivePointerId; 567 | } 568 | 569 | /** 570 | * @return The minimum distance in pixels that the user must travel to 571 | * initiate a drag 572 | */ 573 | public int getTouchSlop() { 574 | return mTouchSlop; 575 | } 576 | 577 | /** 578 | * The result of a call to this method is equivalent to 579 | * {@link #processTouchEvent(android.view.MotionEvent)} receiving an 580 | * ACTION_CANCEL event. 581 | */ 582 | public void cancel() { 583 | mActivePointerId = INVALID_POINTER; 584 | clearMotionHistory(); 585 | 586 | if (mVelocityTracker != null) { 587 | mVelocityTracker.recycle(); 588 | mVelocityTracker = null; 589 | } 590 | } 591 | 592 | /** 593 | * {@link #cancel()}, but also abort all motion in progress and snap to the 594 | * end of any animation. 595 | */ 596 | public void abort() { 597 | cancel(); 598 | if (mDragState == STATE_SETTLING) { 599 | final int oldX = mScroller.getCurrX(); 600 | final int oldY = mScroller.getCurrY(); 601 | mScroller.abortAnimation(); 602 | final int newX = mScroller.getCurrX(); 603 | final int newY = mScroller.getCurrY(); 604 | mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY); 605 | } 606 | setDragState(STATE_IDLE); 607 | } 608 | 609 | /** 610 | * Animate the viewchild
to the given (left, top) position. If
611 | * this method returns true, the caller should invoke
612 | * {@link #continueSettling(boolean)} on each subsequent frame to continue
613 | * the motion until it returns false. If this method returns false there is
614 | * no further work to do to complete the movement.
615 | * 616 | * This operation does not count as a capture event, though 617 | * {@link #getCapturedView()} will still report the sliding view while the 618 | * slide is in progress. 619 | *
620 | * 621 | * @param child Child view to capture and animate 622 | * @param finalLeft Final left position of child 623 | * @param finalTop Final top position of child 624 | * @return true if animation should continue through 625 | * {@link #continueSettling(boolean)} calls 626 | */ 627 | public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) { 628 | mCapturedView = child; 629 | mActivePointerId = INVALID_POINTER; 630 | 631 | return forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0); 632 | } 633 | 634 | /** 635 | * Settle the captured view at the given (left, top) position. The 636 | * appropriate velocity from prior motion will be taken into account. If 637 | * this method returns true, the caller should invoke 638 | * {@link #continueSettling(boolean)} on each subsequent frame to continue 639 | * the motion until it returns false. If this method returns false there is 640 | * no further work to do to complete the movement. 641 | * 642 | * @param finalLeft Settled left edge position for the captured view 643 | * @param finalTop Settled top edge position for the captured view 644 | * @return true if animation should continue through 645 | * {@link #continueSettling(boolean)} calls 646 | */ 647 | public boolean settleCapturedViewAt(int finalLeft, int finalTop) { 648 | if (!mReleaseInProgress) { 649 | throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " 650 | + "Callback#onViewReleased"); 651 | } 652 | 653 | return forceSettleCapturedViewAt(finalLeft, finalTop, 654 | (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), 655 | (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId)); 656 | } 657 | 658 | /** 659 | * Settle the captured view at the given (left, top) position. 660 | * 661 | * @param finalLeft Target left position for the captured view 662 | * @param finalTop Target top position for the captured view 663 | * @param xvel Horizontal velocity 664 | * @param yvel Vertical velocity 665 | * @return true if animation should continue through 666 | * {@link #continueSettling(boolean)} calls 667 | */ 668 | private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) { 669 | final int startLeft = mCapturedView.getLeft(); 670 | final int startTop = mCapturedView.getTop(); 671 | final int dx = finalLeft - startLeft; 672 | final int dy = finalTop - startTop; 673 | 674 | if (dx == 0 && dy == 0) { 675 | // Nothing to do. Send callbacks, be done. 676 | mScroller.abortAnimation(); 677 | setDragState(STATE_IDLE); 678 | return false; 679 | } 680 | 681 | final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel); 682 | mScroller.startScroll(startLeft, startTop, dx, dy, duration); 683 | 684 | setDragState(STATE_SETTLING); 685 | return true; 686 | } 687 | 688 | private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) { 689 | xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity); 690 | yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity); 691 | final int absDx = Math.abs(dx); 692 | final int absDy = Math.abs(dy); 693 | final int absXVel = Math.abs(xvel); 694 | final int absYVel = Math.abs(yvel); 695 | final int addedVel = absXVel + absYVel; 696 | final int addedDistance = absDx + absDy; 697 | 698 | final float xweight = xvel != 0 ? (float) absXVel / addedVel : (float) absDx 699 | / addedDistance; 700 | final float yweight = yvel != 0 ? (float) absYVel / addedVel : (float) absDy 701 | / addedDistance; 702 | 703 | int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child)); 704 | int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child)); 705 | 706 | return (int) (xduration * xweight + yduration * yweight); 707 | } 708 | 709 | private int computeAxisDuration(int delta, int velocity, int motionRange) { 710 | if (delta == 0) { 711 | return 0; 712 | } 713 | 714 | final int width = mParentView.getWidth(); 715 | final int halfWidth = width / 2; 716 | final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width); 717 | final float distance = halfWidth + halfWidth 718 | * distanceInfluenceForSnapDuration(distanceRatio); 719 | 720 | int duration; 721 | velocity = Math.abs(velocity); 722 | if (velocity > 0) { 723 | duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 724 | } else { 725 | final float range = (float) Math.abs(delta) / motionRange; 726 | duration = (int) ((range + 1) * BASE_SETTLE_DURATION); 727 | } 728 | return Math.min(duration, MAX_SETTLE_DURATION); 729 | } 730 | 731 | /** 732 | * Clamp the magnitude of value for absMin and absMax. If the value is below 733 | * the minimum, it will be clamped to zero. If the value is above the 734 | * maximum, it will be clamped to the maximum. 735 | * 736 | * @param value Value to clamp 737 | * @param absMin Absolute value of the minimum significant value to return 738 | * @param absMax Absolute value of the maximum value to return 739 | * @return The clamped value with the same sign asvalue
740 | */
741 | private int clampMag(int value, int absMin, int absMax) {
742 | final int absValue = Math.abs(value);
743 | if (absValue < absMin)
744 | return 0;
745 | if (absValue > absMax)
746 | return value > 0 ? absMax : -absMax;
747 | return value;
748 | }
749 |
750 | /**
751 | * Clamp the magnitude of value for absMin and absMax. If the value is below
752 | * the minimum, it will be clamped to zero. If the value is above the
753 | * maximum, it will be clamped to the maximum.
754 | *
755 | * @param value Value to clamp
756 | * @param absMin Absolute value of the minimum significant value to return
757 | * @param absMax Absolute value of the maximum value to return
758 | * @return The clamped value with the same sign as value
759 | */
760 | private float clampMag(float value, float absMin, float absMax) {
761 | final float absValue = Math.abs(value);
762 | if (absValue < absMin)
763 | return 0;
764 | if (absValue > absMax)
765 | return value > 0 ? absMax : -absMax;
766 | return value;
767 | }
768 |
769 | private float distanceInfluenceForSnapDuration(float f) {
770 | f -= 0.5f; // center the values about 0.
771 | f *= 0.3f * Math.PI / 2.0f;
772 | return (float) Math.sin(f);
773 | }
774 |
775 | /**
776 | * Settle the captured view based on standard free-moving fling behavior.
777 | * The caller should invoke {@link #continueSettling(boolean)} on each
778 | * subsequent frame to continue the motion until it returns false.
779 | *
780 | * @param minLeft Minimum X position for the view's left edge
781 | * @param minTop Minimum Y position for the view's top edge
782 | * @param maxLeft Maximum X position for the view's left edge
783 | * @param maxTop Maximum Y position for the view's top edge
784 | */
785 | public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {
786 | if (!mReleaseInProgress) {
787 | throw new IllegalStateException("Cannot flingCapturedView outside of a call to "
788 | + "Callback#onViewReleased");
789 | }
790 |
791 | mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),
792 | (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
793 | (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),
794 | minLeft, maxLeft, minTop, maxTop);
795 |
796 | setDragState(STATE_SETTLING);
797 | }
798 |
799 | /**
800 | * Move the captured settling view by the appropriate amount for the current
801 | * time. If continueSettling
returns true, the caller should
802 | * call it again on the next frame to continue.
803 | *
804 | * @param deferCallbacks true if state callbacks should be deferred via
805 | * posted message. Set this to true if you are calling this
806 | * method from {@link android.view.View#computeScroll()} or
807 | * similar methods invoked as part of layout or drawing.
808 | * @return true if settle is still in progress
809 | */
810 | public boolean continueSettling(boolean deferCallbacks) {
811 | if (mDragState == STATE_SETTLING) {
812 | boolean keepGoing = mScroller.computeScrollOffset();
813 | final int x = mScroller.getCurrX();
814 | final int y = mScroller.getCurrY();
815 | final int dx = x - mCapturedView.getLeft();
816 | final int dy = y - mCapturedView.getTop();
817 |
818 | if (dx != 0) {
819 | mCapturedView.offsetLeftAndRight(dx);
820 | }
821 | if (dy != 0) {
822 | mCapturedView.offsetTopAndBottom(dy);
823 | }
824 |
825 | if (dx != 0 || dy != 0) {
826 | mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy);
827 | }
828 |
829 | if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) {
830 | // Close enough. The interpolator/scroller might think we're
831 | // still moving
832 | // but the user sure doesn't.
833 | mScroller.abortAnimation();
834 | keepGoing = mScroller.isFinished();
835 | }
836 |
837 | if (!keepGoing) {
838 | if (deferCallbacks) {
839 | mParentView.post(mSetIdleRunnable);
840 | } else {
841 | setDragState(STATE_IDLE);
842 | }
843 | }
844 | }
845 |
846 | return mDragState == STATE_SETTLING;
847 | }
848 |
849 | /**
850 | * Like all callback events this must happen on the UI thread, but release
851 | * involves some extra semantics. During a release (mReleaseInProgress) is
852 | * the only time it is valid to call {@link #settleCapturedViewAt(int, int)}
853 | * or {@link #flingCapturedView(int, int, int, int)}.
854 | */
855 | private void dispatchViewReleased(float xvel, float yvel) {
856 | mReleaseInProgress = true;
857 | mCallback.onViewReleased(mCapturedView, xvel, yvel);
858 | mReleaseInProgress = false;
859 |
860 | if (mDragState == STATE_DRAGGING) {
861 | // onViewReleased didn't call a method that would have changed this.
862 | // Go idle.
863 | setDragState(STATE_IDLE);
864 | }
865 | }
866 |
867 | private void clearMotionHistory() {
868 | if (mInitialMotionX == null) {
869 | return;
870 | }
871 | Arrays.fill(mInitialMotionX, 0);
872 | Arrays.fill(mInitialMotionY, 0);
873 | Arrays.fill(mLastMotionX, 0);
874 | Arrays.fill(mLastMotionY, 0);
875 | Arrays.fill(mInitialEdgeTouched, 0);
876 | Arrays.fill(mEdgeDragsInProgress, 0);
877 | Arrays.fill(mEdgeDragsLocked, 0);
878 | mPointersDown = 0;
879 | }
880 |
881 | private void clearMotionHistory(int pointerId) {
882 | if (mInitialMotionX == null) {
883 | return;
884 | }
885 | mInitialMotionX[pointerId] = 0;
886 | mInitialMotionY[pointerId] = 0;
887 | mLastMotionX[pointerId] = 0;
888 | mLastMotionY[pointerId] = 0;
889 | mInitialEdgeTouched[pointerId] = 0;
890 | mEdgeDragsInProgress[pointerId] = 0;
891 | mEdgeDragsLocked[pointerId] = 0;
892 | mPointersDown &= ~(1 << pointerId);
893 | }
894 |
895 | private void ensureMotionHistorySizeForId(int pointerId) {
896 | if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) {
897 | float[] imx = new float[pointerId + 1];
898 | float[] imy = new float[pointerId + 1];
899 | float[] lmx = new float[pointerId + 1];
900 | float[] lmy = new float[pointerId + 1];
901 | int[] iit = new int[pointerId + 1];
902 | int[] edip = new int[pointerId + 1];
903 | int[] edl = new int[pointerId + 1];
904 |
905 | if (mInitialMotionX != null) {
906 | System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length);
907 | System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length);
908 | System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length);
909 | System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length);
910 | System.arraycopy(mInitialEdgeTouched, 0, iit, 0, mInitialEdgeTouched.length);
911 | System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length);
912 | System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length);
913 | }
914 |
915 | mInitialMotionX = imx;
916 | mInitialMotionY = imy;
917 | mLastMotionX = lmx;
918 | mLastMotionY = lmy;
919 | mInitialEdgeTouched = iit;
920 | mEdgeDragsInProgress = edip;
921 | mEdgeDragsLocked = edl;
922 | }
923 | }
924 |
925 | private void saveInitialMotion(float x, float y, int pointerId) {
926 | ensureMotionHistorySizeForId(pointerId);
927 | mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;
928 | mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;
929 | mInitialEdgeTouched[pointerId] = getEdgeTouched((int) x, (int) y);
930 | mPointersDown |= 1 << pointerId;
931 | }
932 |
933 | private void saveLastMotion(MotionEvent ev) {
934 | final int pointerCount = MotionEventCompat.getPointerCount(ev);
935 | for (int i = 0; i < pointerCount; i++) {
936 | final int pointerId = MotionEventCompat.getPointerId(ev, i);
937 | final float x = MotionEventCompat.getX(ev, i);
938 | final float y = MotionEventCompat.getY(ev, i);
939 | mLastMotionX[pointerId] = x;
940 | mLastMotionY[pointerId] = y;
941 | }
942 | }
943 |
944 | /**
945 | * Check if the given pointer ID represents a pointer that is currently down
946 | * (to the best of the ViewDragHelper's knowledge).
947 | * 948 | * The state used to report this information is populated by the methods 949 | * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or 950 | * {@link #processTouchEvent(android.view.MotionEvent)}. If one of these 951 | * methods has not been called for all relevant MotionEvents to track, the 952 | * information reported by this method may be stale or incorrect. 953 | *
954 | * 955 | * @param pointerId pointer ID to check; corresponds to IDs provided by 956 | * MotionEvent 957 | * @return true if the pointer with the given ID is still down 958 | */ 959 | public boolean isPointerDown(int pointerId) { 960 | return (mPointersDown & 1 << pointerId) != 0; 961 | } 962 | 963 | void setDragState(int state) { 964 | if (mDragState != state) { 965 | mDragState = state; 966 | mCallback.onViewDragStateChanged(state); 967 | if (state == STATE_IDLE) { 968 | mCapturedView = null; 969 | } 970 | } 971 | } 972 | 973 | /** 974 | * Attempt to capture the view with the given pointer ID. The callback will 975 | * be involved. This will put us into the "dragging" state. If we've already 976 | * captured this view with this pointer this method will immediately return 977 | * true without consulting the callback. 978 | * 979 | * @param toCapture View to capture 980 | * @param pointerId Pointer to capture with 981 | * @return true if capture was successful 982 | */ 983 | boolean tryCaptureViewForDrag(View toCapture, int pointerId) { 984 | if (toCapture == mCapturedView && mActivePointerId == pointerId) { 985 | // Already done! 986 | return true; 987 | } 988 | if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) { 989 | mActivePointerId = pointerId; 990 | captureChildView(toCapture, pointerId); 991 | return true; 992 | } 993 | return false; 994 | } 995 | 996 | /** 997 | * Tests scrollability within child views of v given a delta of dx. 998 | * 999 | * @param v View to test for horizontal scrollability 1000 | * @param checkV Whether the view v passed should itself be checked for 1001 | * scrollability (true), or just its children (false). 1002 | * @param dx Delta scrolled in pixels along the X axis 1003 | * @param dy Delta scrolled in pixels along the Y axis 1004 | * @param x X coordinate of the active touch point 1005 | * @param y Y coordinate of the active touch point 1006 | * @return true if child views of v can be scrolled by delta of dx. 1007 | */ 1008 | protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) { 1009 | if (v instanceof ViewGroup) { 1010 | final ViewGroup group = (ViewGroup) v; 1011 | final int scrollX = v.getScrollX(); 1012 | final int scrollY = v.getScrollY(); 1013 | final int count = group.getChildCount(); 1014 | // Count backwards - let topmost views consume scroll distance 1015 | // first. 1016 | for (int i = count - 1; i >= 0; i--) { 1017 | // TODO: Add versioned support here for transformed views. 1018 | // This will not work for transformed views in Honeycomb+ 1019 | final View child = group.getChildAt(i); 1020 | if (x + scrollX >= child.getLeft() 1021 | && x + scrollX < child.getRight() 1022 | && y + scrollY >= child.getTop() 1023 | && y + scrollY < child.getBottom() 1024 | && canScroll(child, true, dx, dy, x + scrollX - child.getLeft(), y 1025 | + scrollY - child.getTop())) { 1026 | return true; 1027 | } 1028 | } 1029 | } 1030 | 1031 | return checkV 1032 | && (ViewCompat.canScrollHorizontally(v, -dx) || ViewCompat.canScrollVertically(v, 1033 | -dy)); 1034 | } 1035 | 1036 | /** 1037 | * Check if this event as provided to the parent view's 1038 | * onInterceptTouchEvent should cause the parent to intercept the touch 1039 | * event stream. 1040 | * 1041 | * @param ev MotionEvent provided to onInterceptTouchEvent 1042 | * @return true if the parent view should return true from 1043 | * onInterceptTouchEvent 1044 | */ 1045 | public boolean shouldInterceptTouchEvent(MotionEvent ev) { 1046 | final int action = MotionEventCompat.getActionMasked(ev); 1047 | final int actionIndex = MotionEventCompat.getActionIndex(ev); 1048 | 1049 | if (action == MotionEvent.ACTION_DOWN) { 1050 | // Reset things for a new event stream, just in case we didn't get 1051 | // the whole previous stream. 1052 | cancel(); 1053 | } 1054 | 1055 | if (mVelocityTracker == null) { 1056 | mVelocityTracker = VelocityTracker.obtain(); 1057 | } 1058 | mVelocityTracker.addMovement(ev); 1059 | 1060 | switch (action) { 1061 | case MotionEvent.ACTION_DOWN: { 1062 | final float x = ev.getX(); 1063 | final float y = ev.getY(); 1064 | final int pointerId = MotionEventCompat.getPointerId(ev, 0); 1065 | saveInitialMotion(x, y, pointerId); 1066 | 1067 | final View toCapture = findTopChildUnder((int) x, (int) y); 1068 | 1069 | // Catch a settling view if possible. 1070 | if (toCapture == mCapturedView && mDragState == STATE_SETTLING) { 1071 | tryCaptureViewForDrag(toCapture, pointerId); 1072 | } 1073 | 1074 | final int edgesTouched = mInitialEdgeTouched[pointerId]; 1075 | if ((edgesTouched & mTrackingEdges) != 0) { 1076 | mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); 1077 | } 1078 | break; 1079 | } 1080 | 1081 | case MotionEventCompat.ACTION_POINTER_DOWN: { 1082 | final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); 1083 | final float x = MotionEventCompat.getX(ev, actionIndex); 1084 | final float y = MotionEventCompat.getY(ev, actionIndex); 1085 | 1086 | saveInitialMotion(x, y, pointerId); 1087 | 1088 | // A ViewDragHelper can only manipulate one view at a time. 1089 | if (mDragState == STATE_IDLE) { 1090 | final int edgesTouched = mInitialEdgeTouched[pointerId]; 1091 | if ((edgesTouched & mTrackingEdges) != 0) { 1092 | mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); 1093 | } 1094 | } else if (mDragState == STATE_SETTLING) { 1095 | // Catch a settling view if possible. 1096 | final View toCapture = findTopChildUnder((int) x, (int) y); 1097 | if (toCapture == mCapturedView) { 1098 | tryCaptureViewForDrag(toCapture, pointerId); 1099 | } 1100 | } 1101 | break; 1102 | } 1103 | 1104 | case MotionEvent.ACTION_MOVE: { 1105 | // First to cross a touch slop over a draggable view wins. Also 1106 | // report edge drags. 1107 | final int pointerCount = MotionEventCompat.getPointerCount(ev); 1108 | for (int i = 0; i < pointerCount; i++) { 1109 | final int pointerId = MotionEventCompat.getPointerId(ev, i); 1110 | final float x = MotionEventCompat.getX(ev, i); 1111 | final float y = MotionEventCompat.getY(ev, i); 1112 | final float dx = x - mInitialMotionX[pointerId]; 1113 | final float dy = y - mInitialMotionY[pointerId]; 1114 | 1115 | reportNewEdgeDrags(dx, dy, pointerId); 1116 | if (mDragState == STATE_DRAGGING) { 1117 | // Callback might have started an edge drag 1118 | break; 1119 | } 1120 | 1121 | final View toCapture = findTopChildUnder((int) x, (int) y); 1122 | if (toCapture != null && checkTouchSlop(toCapture, dx, dy) 1123 | && tryCaptureViewForDrag(toCapture, pointerId)) { 1124 | break; 1125 | } 1126 | } 1127 | saveLastMotion(ev); 1128 | break; 1129 | } 1130 | 1131 | case MotionEventCompat.ACTION_POINTER_UP: { 1132 | final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); 1133 | clearMotionHistory(pointerId); 1134 | break; 1135 | } 1136 | 1137 | case MotionEvent.ACTION_UP: 1138 | case MotionEvent.ACTION_CANCEL: { 1139 | cancel(); 1140 | break; 1141 | } 1142 | } 1143 | 1144 | return mDragState == STATE_DRAGGING; 1145 | } 1146 | 1147 | /** 1148 | * Process a touch event received by the parent view. This method will 1149 | * dispatch callback events as needed before returning. The parent view's 1150 | * onTouchEvent implementation should call this. 1151 | * 1152 | * @param ev The touch event received by the parent view 1153 | */ 1154 | public void processTouchEvent(MotionEvent ev) { 1155 | final int action = MotionEventCompat.getActionMasked(ev); 1156 | final int actionIndex = MotionEventCompat.getActionIndex(ev); 1157 | 1158 | if (action == MotionEvent.ACTION_DOWN) { 1159 | // Reset things for a new event stream, just in case we didn't get 1160 | // the whole previous stream. 1161 | cancel(); 1162 | } 1163 | 1164 | if (mVelocityTracker == null) { 1165 | mVelocityTracker = VelocityTracker.obtain(); 1166 | } 1167 | mVelocityTracker.addMovement(ev); 1168 | 1169 | switch (action) { 1170 | case MotionEvent.ACTION_DOWN: { 1171 | final float x = ev.getX(); 1172 | final float y = ev.getY(); 1173 | final int pointerId = MotionEventCompat.getPointerId(ev, 0); 1174 | final View toCapture = findTopChildUnder((int) x, (int) y); 1175 | 1176 | saveInitialMotion(x, y, pointerId); 1177 | 1178 | // Since the parent is already directly processing this touch 1179 | // event, 1180 | // there is no reason to delay for a slop before dragging. 1181 | // Start immediately if possible. 1182 | tryCaptureViewForDrag(toCapture, pointerId); 1183 | 1184 | final int edgesTouched = mInitialEdgeTouched[pointerId]; 1185 | if ((edgesTouched & mTrackingEdges) != 0) { 1186 | mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); 1187 | } 1188 | break; 1189 | } 1190 | 1191 | case MotionEventCompat.ACTION_POINTER_DOWN: { 1192 | final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); 1193 | final float x = MotionEventCompat.getX(ev, actionIndex); 1194 | final float y = MotionEventCompat.getY(ev, actionIndex); 1195 | 1196 | saveInitialMotion(x, y, pointerId); 1197 | 1198 | // A ViewDragHelper can only manipulate one view at a time. 1199 | if (mDragState == STATE_IDLE) { 1200 | // If we're idle we can do anything! Treat it like a normal 1201 | // down event. 1202 | 1203 | final View toCapture = findTopChildUnder((int) x, (int) y); 1204 | tryCaptureViewForDrag(toCapture, pointerId); 1205 | 1206 | final int edgesTouched = mInitialEdgeTouched[pointerId]; 1207 | if ((edgesTouched & mTrackingEdges) != 0) { 1208 | mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); 1209 | } 1210 | } else if (isCapturedViewUnder((int) x, (int) y)) { 1211 | // We're still tracking a captured view. If the same view is 1212 | // under this 1213 | // point, we'll swap to controlling it with this pointer 1214 | // instead. 1215 | // (This will still work if we're "catching" a settling 1216 | // view.) 1217 | 1218 | tryCaptureViewForDrag(mCapturedView, pointerId); 1219 | } 1220 | break; 1221 | } 1222 | 1223 | case MotionEvent.ACTION_MOVE: { 1224 | if (mDragState == STATE_DRAGGING) { 1225 | final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 1226 | final float x = MotionEventCompat.getX(ev, index); 1227 | final float y = MotionEventCompat.getY(ev, index); 1228 | final int idx = (int) (x - mLastMotionX[mActivePointerId]); 1229 | final int idy = (int) (y - mLastMotionY[mActivePointerId]); 1230 | 1231 | dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy); 1232 | 1233 | saveLastMotion(ev); 1234 | } else { 1235 | // Check to see if any pointer is now over a draggable view. 1236 | final int pointerCount = MotionEventCompat.getPointerCount(ev); 1237 | for (int i = 0; i < pointerCount; i++) { 1238 | final int pointerId = MotionEventCompat.getPointerId(ev, i); 1239 | final float x = MotionEventCompat.getX(ev, i); 1240 | final float y = MotionEventCompat.getY(ev, i); 1241 | final float dx = x - mInitialMotionX[pointerId]; 1242 | final float dy = y - mInitialMotionY[pointerId]; 1243 | 1244 | reportNewEdgeDrags(dx, dy, pointerId); 1245 | if (mDragState == STATE_DRAGGING) { 1246 | // Callback might have started an edge drag. 1247 | break; 1248 | } 1249 | 1250 | final View toCapture = findTopChildUnder((int) x, (int) y); 1251 | if (checkTouchSlop(toCapture, dx, dy) 1252 | && tryCaptureViewForDrag(toCapture, pointerId)) { 1253 | break; 1254 | } 1255 | } 1256 | saveLastMotion(ev); 1257 | } 1258 | break; 1259 | } 1260 | 1261 | case MotionEventCompat.ACTION_POINTER_UP: { 1262 | final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); 1263 | if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) { 1264 | // Try to find another pointer that's still holding on to 1265 | // the captured view. 1266 | int newActivePointer = INVALID_POINTER; 1267 | final int pointerCount = MotionEventCompat.getPointerCount(ev); 1268 | for (int i = 0; i < pointerCount; i++) { 1269 | final int id = MotionEventCompat.getPointerId(ev, i); 1270 | if (id == mActivePointerId) { 1271 | // This one's going away, skip. 1272 | continue; 1273 | } 1274 | 1275 | final float x = MotionEventCompat.getX(ev, i); 1276 | final float y = MotionEventCompat.getY(ev, i); 1277 | if (findTopChildUnder((int) x, (int) y) == mCapturedView 1278 | && tryCaptureViewForDrag(mCapturedView, id)) { 1279 | newActivePointer = mActivePointerId; 1280 | break; 1281 | } 1282 | } 1283 | 1284 | if (newActivePointer == INVALID_POINTER) { 1285 | // We didn't find another pointer still touching the 1286 | // view, release it. 1287 | releaseViewForPointerUp(); 1288 | } 1289 | } 1290 | clearMotionHistory(pointerId); 1291 | break; 1292 | } 1293 | 1294 | case MotionEvent.ACTION_UP: { 1295 | if (mDragState == STATE_DRAGGING) { 1296 | releaseViewForPointerUp(); 1297 | } 1298 | cancel(); 1299 | break; 1300 | } 1301 | 1302 | case MotionEvent.ACTION_CANCEL: { 1303 | if (mDragState == STATE_DRAGGING) { 1304 | dispatchViewReleased(0, 0); 1305 | } 1306 | cancel(); 1307 | break; 1308 | } 1309 | } 1310 | } 1311 | 1312 | private void reportNewEdgeDrags(float dx, float dy, int pointerId) { 1313 | int dragsStarted = 0; 1314 | if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) { 1315 | dragsStarted |= EDGE_LEFT; 1316 | } 1317 | if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) { 1318 | dragsStarted |= EDGE_TOP; 1319 | } 1320 | if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) { 1321 | dragsStarted |= EDGE_RIGHT; 1322 | } 1323 | if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) { 1324 | dragsStarted |= EDGE_BOTTOM; 1325 | } 1326 | 1327 | if (dragsStarted != 0) { 1328 | mEdgeDragsInProgress[pointerId] |= dragsStarted; 1329 | mCallback.onEdgeDragStarted(dragsStarted, pointerId); 1330 | } 1331 | } 1332 | 1333 | private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) { 1334 | final float absDelta = Math.abs(delta); 1335 | final float absODelta = Math.abs(odelta); 1336 | 1337 | if ((mInitialEdgeTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0 1338 | || (mEdgeDragsLocked[pointerId] & edge) == edge 1339 | || (mEdgeDragsInProgress[pointerId] & edge) == edge 1340 | || (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) { 1341 | return false; 1342 | } 1343 | if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) { 1344 | mEdgeDragsLocked[pointerId] |= edge; 1345 | return false; 1346 | } 1347 | return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop; 1348 | } 1349 | 1350 | /** 1351 | * Check if we've crossed a reasonable touch slop for the given child view. 1352 | * If the child cannot be dragged along the horizontal or vertical axis, 1353 | * motion along that axis will not count toward the slop check. 1354 | * 1355 | * @param child Child to check 1356 | * @param dx Motion since initial position along X axis 1357 | * @param dy Motion since initial position along Y axis 1358 | * @return true if the touch slop has been crossed 1359 | */ 1360 | private boolean checkTouchSlop(View child, float dx, float dy) { 1361 | if (child == null) { 1362 | return false; 1363 | } 1364 | final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0; 1365 | final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0; 1366 | 1367 | if (checkHorizontal && checkVertical) { 1368 | return dx * dx + dy * dy > mTouchSlop * mTouchSlop; 1369 | } else if (checkHorizontal) { 1370 | return Math.abs(dx) > mTouchSlop; 1371 | } else if (checkVertical) { 1372 | return Math.abs(dy) > mTouchSlop; 1373 | } 1374 | return false; 1375 | } 1376 | 1377 | /** 1378 | * Check if any pointer tracked in the current gesture has crossed the 1379 | * required slop threshold. 1380 | *1381 | * This depends on internal state populated by 1382 | * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or 1383 | * {@link #processTouchEvent(android.view.MotionEvent)}. You should only 1384 | * rely on the results of this method after all currently available touch 1385 | * data has been provided to one of these two methods. 1386 | *
1387 | * 1388 | * @param directions Combination of direction flags, see 1389 | * {@link #DIRECTION_HORIZONTAL}, {@link #DIRECTION_VERTICAL}, 1390 | * {@link #DIRECTION_ALL} 1391 | * @return true if the slop threshold has been crossed, false otherwise 1392 | */ 1393 | public boolean checkTouchSlop(int directions) { 1394 | final int count = mInitialMotionX.length; 1395 | for (int i = 0; i < count; i++) { 1396 | if (checkTouchSlop(directions, i)) { 1397 | return true; 1398 | } 1399 | } 1400 | return false; 1401 | } 1402 | 1403 | /** 1404 | * Check if the specified pointer tracked in the current gesture has crossed 1405 | * the required slop threshold. 1406 | *1407 | * This depends on internal state populated by 1408 | * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or 1409 | * {@link #processTouchEvent(android.view.MotionEvent)}. You should only 1410 | * rely on the results of this method after all currently available touch 1411 | * data has been provided to one of these two methods. 1412 | *
1413 | * 1414 | * @param directions Combination of direction flags, see 1415 | * {@link #DIRECTION_HORIZONTAL}, {@link #DIRECTION_VERTICAL}, 1416 | * {@link #DIRECTION_ALL} 1417 | * @param pointerId ID of the pointer to slop check as specified by 1418 | * MotionEvent 1419 | * @return true if the slop threshold has been crossed, false otherwise 1420 | */ 1421 | public boolean checkTouchSlop(int directions, int pointerId) { 1422 | if (!isPointerDown(pointerId)) { 1423 | return false; 1424 | } 1425 | 1426 | final boolean checkHorizontal = (directions & DIRECTION_HORIZONTAL) == DIRECTION_HORIZONTAL; 1427 | final boolean checkVertical = (directions & DIRECTION_VERTICAL) == DIRECTION_VERTICAL; 1428 | 1429 | final float dx = mLastMotionX[pointerId] - mInitialMotionX[pointerId]; 1430 | final float dy = mLastMotionY[pointerId] - mInitialMotionY[pointerId]; 1431 | 1432 | if (checkHorizontal && checkVertical) { 1433 | return dx * dx + dy * dy > mTouchSlop * mTouchSlop; 1434 | } else if (checkHorizontal) { 1435 | return Math.abs(dx) > mTouchSlop; 1436 | } else if (checkVertical) { 1437 | return Math.abs(dy) > mTouchSlop; 1438 | } 1439 | return false; 1440 | } 1441 | 1442 | /** 1443 | * Check if any of the edges specified were initially touched in the 1444 | * currently active gesture. If there is no currently active gesture this 1445 | * method will return false. 1446 | * 1447 | * @param edges Edges to check for an initial edge touch. See 1448 | * {@link #EDGE_LEFT}, {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, 1449 | * {@link #EDGE_BOTTOM} and {@link #EDGE_ALL} 1450 | * @return true if any of the edges specified were initially touched in the 1451 | * current gesture 1452 | */ 1453 | public boolean isEdgeTouched(int edges) { 1454 | final int count = mInitialEdgeTouched.length; 1455 | for (int i = 0; i < count; i++) { 1456 | if (isEdgeTouched(edges, i)) { 1457 | return true; 1458 | } 1459 | } 1460 | return false; 1461 | } 1462 | 1463 | /** 1464 | * Check if any of the edges specified were initially touched by the pointer 1465 | * with the specified ID. If there is no currently active gesture or if 1466 | * there is no pointer with the given ID currently down this method will 1467 | * return false. 1468 | * 1469 | * @param edges Edges to check for an initial edge touch. See 1470 | * {@link #EDGE_LEFT}, {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, 1471 | * {@link #EDGE_BOTTOM} and {@link #EDGE_ALL} 1472 | * @return true if any of the edges specified were initially touched in the 1473 | * current gesture 1474 | */ 1475 | public boolean isEdgeTouched(int edges, int pointerId) { 1476 | return isPointerDown(pointerId) && (mInitialEdgeTouched[pointerId] & edges) != 0; 1477 | } 1478 | 1479 | private void releaseViewForPointerUp() { 1480 | mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); 1481 | final float xvel = clampMag( 1482 | VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), 1483 | mMinVelocity, mMaxVelocity); 1484 | final float yvel = clampMag( 1485 | VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId), 1486 | mMinVelocity, mMaxVelocity); 1487 | dispatchViewReleased(xvel, yvel); 1488 | } 1489 | 1490 | private void dragTo(int left, int top, int dx, int dy) { 1491 | int clampedX = left; 1492 | int clampedY = top; 1493 | final int oldLeft = mCapturedView.getLeft(); 1494 | final int oldTop = mCapturedView.getTop(); 1495 | if (dx != 0) { 1496 | clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx); 1497 | mCapturedView.offsetLeftAndRight(clampedX - oldLeft); 1498 | } 1499 | if (dy != 0) { 1500 | clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy); 1501 | mCapturedView.offsetTopAndBottom(clampedY - oldTop); 1502 | } 1503 | 1504 | if (dx != 0 || dy != 0) { 1505 | final int clampedDx = clampedX - oldLeft; 1506 | final int clampedDy = clampedY - oldTop; 1507 | mCallback 1508 | .onViewPositionChanged(mCapturedView, clampedX, clampedY, clampedDx, clampedDy); 1509 | } 1510 | } 1511 | 1512 | /** 1513 | * Determine if the currently captured view is under the given point in the 1514 | * parent view's coordinate system. If there is no captured view this method 1515 | * will return false. 1516 | * 1517 | * @param x X position to test in the parent's coordinate system 1518 | * @param y Y position to test in the parent's coordinate system 1519 | * @return true if the captured view is under the given point, false 1520 | * otherwise 1521 | */ 1522 | public boolean isCapturedViewUnder(int x, int y) { 1523 | return isViewUnder(mCapturedView, x, y); 1524 | } 1525 | 1526 | /** 1527 | * Determine if the supplied view is under the given point in the parent 1528 | * view's coordinate system. 1529 | * 1530 | * @param view Child view of the parent to hit test 1531 | * @param x X position to test in the parent's coordinate system 1532 | * @param y Y position to test in the parent's coordinate system 1533 | * @return true if the supplied view is under the given point, false 1534 | * otherwise 1535 | */ 1536 | public boolean isViewUnder(View view, int x, int y) { 1537 | if (view == null) { 1538 | return false; 1539 | } 1540 | return x >= view.getLeft() && x < view.getRight() && y >= view.getTop() 1541 | && y < view.getBottom(); 1542 | } 1543 | 1544 | /** 1545 | * Find the topmost child under the given point within the parent view's 1546 | * coordinate system. The child order is determined using 1547 | * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#getOrderedChildIndex(int)} 1548 | * . 1549 | * 1550 | * @param x X position to test in the parent's coordinate system 1551 | * @param y Y position to test in the parent's coordinate system 1552 | * @return The topmost child view under (x, y) or null if none found. 1553 | */ 1554 | public View findTopChildUnder(int x, int y) { 1555 | final int childCount = mParentView.getChildCount(); 1556 | for (int i = childCount - 1; i >= 0; i--) { 1557 | final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i)); 1558 | if (x >= child.getLeft() && x < child.getRight() && y >= child.getTop() 1559 | && y < child.getBottom()) { 1560 | return child; 1561 | } 1562 | } 1563 | return null; 1564 | } 1565 | 1566 | private int getEdgeTouched(int x, int y) { 1567 | int result = 0; 1568 | 1569 | if (x < mParentView.getLeft() + mEdgeSize) 1570 | result = EDGE_LEFT; 1571 | if (y < mParentView.getTop() + mEdgeSize) 1572 | result = EDGE_TOP; 1573 | if (x > mParentView.getRight() - mEdgeSize) 1574 | result = EDGE_RIGHT; 1575 | if (y > mParentView.getBottom() - mEdgeSize) 1576 | result = EDGE_BOTTOM; 1577 | 1578 | return result; 1579 | } 1580 | } 1581 | -------------------------------------------------------------------------------- /library/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivity.java: -------------------------------------------------------------------------------- 1 | 2 | package me.imid.swipebacklayout.lib.app; 3 | 4 | import android.os.Bundle; 5 | import android.support.v4.app.FragmentActivity; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.View; 8 | 9 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 10 | import me.imid.swipebacklayout.lib.Utils; 11 | 12 | public class SwipeBackActivity extends AppCompatActivity implements SwipeBackActivityBase { 13 | private SwipeBackActivityHelper mHelper; 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | mHelper = new SwipeBackActivityHelper(this); 19 | mHelper.onActivityCreate(); 20 | } 21 | 22 | @Override 23 | protected void onPostCreate(Bundle savedInstanceState) { 24 | super.onPostCreate(savedInstanceState); 25 | mHelper.onPostCreate(); 26 | } 27 | 28 | @Override 29 | public View findViewById(int id) { 30 | View v = super.findViewById(id); 31 | if (v == null && mHelper != null) 32 | return mHelper.findViewById(id); 33 | return v; 34 | } 35 | 36 | @Override 37 | public SwipeBackLayout getSwipeBackLayout() { 38 | return mHelper.getSwipeBackLayout(); 39 | } 40 | 41 | @Override 42 | public void setSwipeBackEnable(boolean enable) { 43 | getSwipeBackLayout().setEnableGesture(enable); 44 | } 45 | 46 | @Override 47 | public void scrollToFinishActivity() { 48 | Utils.convertActivityToTranslucent(this); 49 | getSwipeBackLayout().scrollToFinishActivity(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /library/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivityBase.java: -------------------------------------------------------------------------------- 1 | package me.imid.swipebacklayout.lib.app; 2 | 3 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 4 | /** 5 | * @author Yrom 6 | */ 7 | public interface SwipeBackActivityBase { 8 | /** 9 | * @return the SwipeBackLayout associated with this activity. 10 | */ 11 | public abstract SwipeBackLayout getSwipeBackLayout(); 12 | 13 | public abstract void setSwipeBackEnable(boolean enable); 14 | 15 | /** 16 | * Scroll out contentView and finish the activity 17 | */ 18 | public abstract void scrollToFinishActivity(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /library/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivityHelper.java: -------------------------------------------------------------------------------- 1 | package me.imid.swipebacklayout.lib.app; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | 9 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 10 | import me.imid.swipebacklayout.lib.Utils; 11 | 12 | /** 13 | * @author Yrom 14 | */ 15 | public class SwipeBackActivityHelper { 16 | private Activity mActivity; 17 | 18 | private SwipeBackLayout mSwipeBackLayout; 19 | 20 | public SwipeBackActivityHelper(Activity activity) { 21 | mActivity = activity; 22 | } 23 | 24 | @SuppressWarnings("deprecation") 25 | public void onActivityCreate() { 26 | mActivity.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 27 | mActivity.getWindow().getDecorView().setBackgroundDrawable(null); 28 | mSwipeBackLayout = (SwipeBackLayout) LayoutInflater.from(mActivity).inflate( 29 | me.imid.swipebacklayout.lib.R.layout.swipeback_layout, null); 30 | } 31 | 32 | public void onPostCreate() { 33 | mSwipeBackLayout.attachToActivity(mActivity); 34 | } 35 | 36 | public View findViewById(int id) { 37 | if (mSwipeBackLayout != null) { 38 | return mSwipeBackLayout.findViewById(id); 39 | } 40 | return null; 41 | } 42 | 43 | public SwipeBackLayout getSwipeBackLayout() { 44 | return mSwipeBackLayout; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /library/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackListenerActivityAdapter.java: -------------------------------------------------------------------------------- 1 | package me.imid.swipebacklayout.lib.app; 2 | 3 | import android.app.Activity; 4 | import android.support.annotation.NonNull; 5 | 6 | import java.lang.ref.WeakReference; 7 | 8 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 9 | import me.imid.swipebacklayout.lib.Utils; 10 | 11 | /** 12 | * Created by laysionqet on 2018/4/24. 13 | */ 14 | public class SwipeBackListenerActivityAdapter implements SwipeBackLayout.SwipeListenerEx { 15 | private final WeakReference