├── .gitattributes ├── .gitignore ├── DraggableGridViewPager ├── .settings │ ├── org.eclipse.core.resources.prefs │ └── org.eclipse.jdt.core.prefs ├── AndroidManifest.xml ├── libs │ └── android-support-v4.jar ├── project.properties └── src │ └── com │ └── coco │ └── draggablegridviewpager │ └── DraggableGridViewPager.java ├── DraggableGridViewPagerTest ├── .settings │ ├── org.eclipse.core.resources.prefs │ └── org.eclipse.jdt.core.prefs ├── AndroidManifest.xml ├── libs │ └── android-support-v4.jar ├── project.properties ├── res │ ├── drawable-mdpi │ │ └── rootblock_default_bg.jpg │ ├── layout │ │ ├── draggable_grid_item.xml │ │ └── draggable_grid_view_pager_test.xml │ └── values │ │ └── colors.xml └── src │ └── com │ └── coco │ └── draggablegridviewpager │ └── test │ └── DraggableGridViewPagerTestActivity.java ├── LICENSE ├── README.md └── snapshot ├── dragging.png ├── idle.png └── swipe.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | .classpath 20 | .project 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Intellij project files 26 | *.iml 27 | *.ipr 28 | *.iws 29 | .idea/ 30 | -------------------------------------------------------------------------------- /DraggableGridViewPager/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | -------------------------------------------------------------------------------- /DraggableGridViewPager/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 3 | org.eclipse.jdt.core.compiler.compliance=1.6 4 | org.eclipse.jdt.core.compiler.source=1.6 5 | -------------------------------------------------------------------------------- /DraggableGridViewPager/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /DraggableGridViewPager/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzhouj/Android-DraggableGridViewPager/7233dbf9c7e17c33b15a140725b702202d95b6df/DraggableGridViewPager/libs/android-support-v4.jar -------------------------------------------------------------------------------- /DraggableGridViewPager/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | android.library=true 16 | -------------------------------------------------------------------------------- /DraggableGridViewPager/src/com/coco/draggablegridviewpager/DraggableGridViewPager.java: -------------------------------------------------------------------------------- 1 | package com.coco.draggablegridviewpager; 2 | 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2014 justin 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | this software and associated documentation files (the "Software"), to deal in 10 | the Software without restriction, including without limitation the rights to 11 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | the Software, and to permit persons to whom the Software is furnished to do so, 13 | subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 20 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 21 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 22 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | import java.util.ArrayList; 27 | 28 | import android.content.Context; 29 | import android.database.DataSetObserver; 30 | import android.graphics.Rect; 31 | import android.os.Parcel; 32 | import android.os.Parcelable; 33 | import android.support.v4.view.MotionEventCompat; 34 | import android.support.v4.view.VelocityTrackerCompat; 35 | import android.support.v4.view.ViewCompat; 36 | import android.support.v4.view.ViewConfigurationCompat; 37 | import android.util.AttributeSet; 38 | import android.util.Log; 39 | import android.view.HapticFeedbackConstants; 40 | import android.view.MotionEvent; 41 | import android.view.VelocityTracker; 42 | import android.view.View; 43 | import android.view.ViewConfiguration; 44 | import android.view.ViewGroup; 45 | import android.view.ViewParent; 46 | import android.view.animation.AlphaAnimation; 47 | import android.view.animation.AnimationSet; 48 | import android.view.animation.Interpolator; 49 | import android.view.animation.ScaleAnimation; 50 | import android.view.animation.TranslateAnimation; 51 | import android.widget.Adapter; 52 | import android.widget.AdapterView.OnItemClickListener; 53 | import android.widget.AdapterView.OnItemLongClickListener; 54 | import android.widget.Scroller; 55 | 56 | /** 57 | * Zaker style grid view pager, support dragging & rearrange, using as zaker's main screen. 58 | */ 59 | public class DraggableGridViewPager extends ViewGroup { 60 | private static final String TAG = "DraggableGridViewPager"; 61 | private static final boolean DEBUG = false; 62 | private static final boolean USE_CACHE = false; 63 | 64 | private static void DEBUG_LOG(String msg) { 65 | if (DEBUG) { 66 | Log.v(TAG, msg); 67 | } 68 | } 69 | 70 | // layout 71 | private static final int DEFAULT_COL_COUNT = 2; 72 | private static final int DEFAULT_ROW_COUNT = 4; 73 | private static final int DEFAULT_GRID_GAP = 8; // gap between grids (dips) 74 | 75 | private static final int MAX_SETTLE_DURATION = 600; // ms 76 | private static final int MIN_DISTANCE_FOR_FLING = 25; // dips 77 | private static final int MIN_FLING_VELOCITY = 400; // dips 78 | private static final int CLOSE_ENOUGH = 2; // dp 79 | 80 | private static final Interpolator sInterpolator = new Interpolator() { 81 | public float getInterpolation(float t) { 82 | t -= 1.0f; 83 | return t * t * t * t * t + 1.0f; 84 | } 85 | }; 86 | 87 | private static final int INVALID_POINTER = -1; 88 | 89 | public static final int SCROLL_STATE_IDLE = 0; 90 | public static final int SCROLL_STATE_DRAGGING = 1; 91 | public static final int SCROLL_STATE_SETTLING = 2; 92 | 93 | private static final long LONG_CLICK_DURATION = 1000; // ms 94 | private static final long ANIMATION_DURATION = 150; // ms 95 | 96 | private static final int EDGE_LFET = 0; 97 | private static final int EDGE_RIGHT = 1; 98 | 99 | private static final long EDGE_HOLD_DURATION = 1200; // ms 100 | 101 | private int mColCount = DEFAULT_COL_COUNT; 102 | private int mRowCount = DEFAULT_ROW_COUNT; 103 | private int mPageSize = mColCount * mRowCount; 104 | private int mGridGap; 105 | 106 | private int mPageCount; 107 | private int mGridWidth; 108 | private int mGridHeight; 109 | private int mMaxOverScrollSize; 110 | private int mEdgeSize; 111 | 112 | // internal paddings 113 | private int mPaddingLeft; 114 | private int mPaddingTop; 115 | private int mPaddingRight; 116 | private int mPaddingButtom; 117 | 118 | private int mCurItem; // Index of currently displayed page. 119 | private Adapter mAdapter; 120 | private final DataSetObserver mDataSetObserver = new DataSetObserver() { 121 | @Override 122 | public void onChanged() { 123 | dataSetChanged(); 124 | } 125 | 126 | @Override 127 | public void onInvalidated() { 128 | dataSetChanged(); 129 | } 130 | }; 131 | 132 | private Scroller mScroller; 133 | 134 | private boolean mScrollingCacheEnabled; 135 | 136 | private boolean mIsBeingDragged; 137 | private boolean mIsUnableToDrag; 138 | private int mTouchSlop; 139 | 140 | private float mLastMotionX; 141 | private float mLastMotionY; 142 | private float mInitialMotionX; 143 | private float mInitialMotionY; 144 | private int mActivePointerId = INVALID_POINTER; 145 | 146 | private VelocityTracker mVelocityTracker; 147 | private int mMinimumVelocity; 148 | private int mMaximumVelocity; 149 | private int mFlingDistance; 150 | private int mCloseEnough; 151 | 152 | // click & long click 153 | private int mLastPosition = -1; 154 | private long mLastDownTime = Long.MAX_VALUE; 155 | 156 | // rearrange 157 | private int mLastDragged = -1; 158 | private int mLastTarget = -1; 159 | 160 | // edge holding 161 | private int mLastEdge = -1; 162 | private long mLastEdgeTime = Long.MAX_VALUE; 163 | 164 | private ArrayList newPositions = new ArrayList(); 165 | 166 | private boolean mCalledSuper; 167 | 168 | private OnPageChangeListener mOnPageChangeListener; 169 | private OnItemClickListener mOnItemClickListener; 170 | private OnItemLongClickListener mOnItemLongClickListener; 171 | private OnRearrangeListener mOnRearrangeListener; 172 | 173 | private final Runnable mEndScrollRunnable = new Runnable() { 174 | public void run() { 175 | setScrollState(SCROLL_STATE_IDLE); 176 | } 177 | }; 178 | 179 | private int mScrollState = SCROLL_STATE_IDLE; 180 | 181 | /** 182 | * Callback interface for responding to changing state of the selected page. 183 | */ 184 | public interface OnPageChangeListener { 185 | 186 | /** 187 | * This method will be invoked when the current page is scrolled, either as part of a programmatically initiated 188 | * smooth scroll or a user initiated touch scroll. 189 | * 190 | * @param position 191 | * Position index of the first page currently being displayed. Page position+1 will be visible if 192 | * positionOffset is nonzero. 193 | * @param positionOffset 194 | * Value from [0, 1) indicating the offset from the page at position. 195 | * @param positionOffsetPixels 196 | * Value in pixels indicating the offset from position. 197 | */ 198 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); 199 | 200 | /** 201 | * This method will be invoked when a new page becomes selected. Animation is not necessarily complete. 202 | * 203 | * @param position 204 | * Position index of the new selected page. 205 | */ 206 | public void onPageSelected(int position); 207 | 208 | /** 209 | * Called when the scroll state changes. Useful for discovering when the user begins dragging, when the pager is 210 | * automatically settling to the current page, or when it is fully stopped/idle. 211 | * 212 | * @param state 213 | * The new scroll state. 214 | * @see DraggableGridViewPager#SCROLL_STATE_IDLE 215 | * @see DraggableGridViewPager#SCROLL_STATE_DRAGGING 216 | * @see DraggableGridViewPager#SCROLL_STATE_SETTLING 217 | */ 218 | public void onPageScrollStateChanged(int state); 219 | } 220 | 221 | /** 222 | * Simple implementation of the {@link OnPageChangeListener} interface with stub implementations of each method. 223 | * Extend this if you do not intend to override every method of {@link OnPageChangeListener}. 224 | */ 225 | public static class SimpleOnPageChangeListener implements OnPageChangeListener { 226 | @Override 227 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 228 | // This space for rent 229 | } 230 | 231 | @Override 232 | public void onPageSelected(int position) { 233 | // This space for rent 234 | } 235 | 236 | @Override 237 | public void onPageScrollStateChanged(int state) { 238 | // This space for rent 239 | } 240 | } 241 | 242 | public interface OnRearrangeListener { 243 | public abstract void onRearrange(int oldIndex, int newIndex); 244 | } 245 | 246 | public DraggableGridViewPager(Context context) { 247 | super(context); 248 | initDraggableGridViewPager(); 249 | } 250 | 251 | public DraggableGridViewPager(Context context, AttributeSet attrs) { 252 | super(context, attrs); 253 | initDraggableGridViewPager(); 254 | } 255 | 256 | public DraggableGridViewPager(Context context, AttributeSet attrs, int defStyle) { 257 | super(context, attrs, defStyle); 258 | initDraggableGridViewPager(); 259 | } 260 | 261 | private void initDraggableGridViewPager() { 262 | setWillNotDraw(false); 263 | setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 264 | setFocusable(true); 265 | setChildrenDrawingOrderEnabled(true); 266 | 267 | final Context context = getContext(); 268 | final ViewConfiguration configuration = ViewConfiguration.get(context); 269 | final float density = context.getResources().getDisplayMetrics().density; 270 | 271 | mGridGap = (int) (DEFAULT_GRID_GAP * density); 272 | 273 | // internal paddings 274 | mPaddingLeft = getPaddingLeft(); 275 | mPaddingTop = getPaddingTop(); 276 | mPaddingRight = getPaddingRight(); 277 | mPaddingButtom = getPaddingBottom(); 278 | super.setPadding(0, 0, 0, 0); 279 | 280 | mScroller = new Scroller(context, sInterpolator); 281 | mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); 282 | mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density); 283 | mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 284 | 285 | mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density); 286 | mCloseEnough = (int) (CLOSE_ENOUGH * density); 287 | } 288 | 289 | @Override 290 | protected void onDetachedFromWindow() { 291 | removeCallbacks(mEndScrollRunnable); 292 | if (mAdapter != null) { 293 | mAdapter.unregisterDataSetObserver(mDataSetObserver); 294 | } 295 | super.onDetachedFromWindow(); 296 | } 297 | 298 | public int getColCount() { 299 | return mColCount; 300 | } 301 | 302 | public void setColCount(int colCount) { 303 | if (colCount < 1) { 304 | colCount = 1; 305 | } 306 | mColCount = colCount; 307 | mPageSize = mColCount * mRowCount; 308 | requestLayout(); 309 | } 310 | 311 | public int getRowCount() { 312 | return mRowCount; 313 | } 314 | 315 | public void setRowCount(int rowCount) { 316 | if (rowCount < 1) { 317 | rowCount = 1; 318 | } 319 | mRowCount = rowCount; 320 | mPageSize = mColCount * mRowCount; 321 | requestLayout(); 322 | } 323 | 324 | public int getGridGap() { 325 | return mGridGap; 326 | } 327 | 328 | public void setGridGap(int gridGap) { 329 | if (gridGap < 0) { 330 | gridGap = 0; 331 | } 332 | mGridGap = gridGap; 333 | requestLayout(); 334 | } 335 | 336 | public int getPageCount() { 337 | return (getChildCount() + mPageSize - 1) / mPageSize; 338 | } 339 | 340 | @Override 341 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 342 | final int childCount = getChildCount(); 343 | mPageCount = (childCount + mPageSize - 1) / mPageSize; 344 | mGridWidth = (getWidth() - mPaddingLeft - mPaddingRight - (mColCount - 1) * mGridGap) / mColCount; 345 | mGridHeight = (getHeight() - mPaddingTop - mPaddingButtom - (mRowCount - 1) * mGridGap) / mRowCount; 346 | mGridWidth = mGridHeight = Math.min(mGridWidth, mGridHeight); 347 | mMaxOverScrollSize = mGridWidth / 2; 348 | mEdgeSize = mGridWidth / 2; 349 | newPositions.clear(); 350 | for (int i = 0; i < childCount; i++) { 351 | final View child = getChildAt(i); 352 | final Rect rect = getRectByPosition(i); 353 | child.measure(MeasureSpec.makeMeasureSpec(rect.width(), MeasureSpec.EXACTLY), 354 | MeasureSpec.makeMeasureSpec(rect.height(), MeasureSpec.EXACTLY)); 355 | DEBUG_LOG("child.layout position=" + i + ", rect=" + rect); 356 | child.layout(rect.left, rect.top, rect.right, rect.bottom); 357 | newPositions.add(-1); 358 | } 359 | if (mCurItem > 0 && mCurItem < mPageCount) { 360 | final int curItem = mCurItem; 361 | mCurItem = 0; 362 | setCurrentItem(curItem); 363 | } 364 | } 365 | 366 | private void setScrollState(int newState) { 367 | if (mScrollState == newState) { 368 | return; 369 | } 370 | mScrollState = newState; 371 | if (mOnPageChangeListener != null) { 372 | mOnPageChangeListener.onPageScrollStateChanged(newState); 373 | } 374 | } 375 | 376 | public int getCurrentItem() { 377 | return mCurItem; 378 | } 379 | 380 | public void setCurrentItem(int item) { 381 | setCurrentItemInternal(item, false, false); 382 | } 383 | 384 | public void setCurrentItem(int item, boolean smoothScroll) { 385 | setCurrentItemInternal(item, smoothScroll, false); 386 | } 387 | 388 | void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { 389 | setCurrentItemInternal(item, smoothScroll, always, 0); 390 | } 391 | 392 | void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { 393 | if (mPageCount <= 0) { 394 | setScrollingCacheEnabled(false); 395 | return; 396 | } 397 | if (!always && mCurItem == item) { 398 | setScrollingCacheEnabled(false); 399 | return; 400 | } 401 | 402 | if (item < 0) { 403 | item = 0; 404 | } else if (item >= mPageCount) { 405 | item = mPageCount - 1; 406 | } 407 | final boolean dispatchSelected = mCurItem != item; 408 | mCurItem = item; 409 | scrollToItem(item, smoothScroll, velocity, dispatchSelected); 410 | } 411 | 412 | private void scrollToItem(int item, boolean smoothScroll, int velocity, boolean dispatchSelected) { 413 | final int destX = getWidth() * item; 414 | if (smoothScroll) { 415 | smoothScrollTo(destX, 0, velocity); 416 | if (dispatchSelected && mOnPageChangeListener != null) { 417 | mOnPageChangeListener.onPageSelected(item); 418 | } 419 | } else { 420 | if (dispatchSelected && mOnPageChangeListener != null) { 421 | mOnPageChangeListener.onPageSelected(item); 422 | } 423 | completeScroll(false); 424 | scrollTo(destX, 0); 425 | pageScrolled(destX); 426 | } 427 | } 428 | 429 | public void setOnPageChangeListener(OnPageChangeListener listener) { 430 | mOnPageChangeListener = listener; 431 | } 432 | 433 | public void setOnItemClickListener(OnItemClickListener listener) { 434 | mOnItemClickListener = listener; 435 | } 436 | 437 | public void setOnItemLongClickListener(OnItemLongClickListener listener) { 438 | mOnItemLongClickListener = listener; 439 | } 440 | 441 | public void setOnRearrangeListener(OnRearrangeListener listener) { 442 | mOnRearrangeListener = listener; 443 | } 444 | 445 | float distanceInfluenceForSnapDuration(float f) { 446 | f -= 0.5f; // center the values about 0. 447 | f *= 0.3f * Math.PI / 2.0f; 448 | return (float) Math.sin(f); 449 | } 450 | 451 | void smoothScrollTo(int x, int y) { 452 | smoothScrollTo(x, y, 0); 453 | } 454 | 455 | void smoothScrollTo(int x, int y, int velocity) { 456 | if (getChildCount() == 0) { 457 | // Nothing to do. 458 | setScrollingCacheEnabled(false); 459 | return; 460 | } 461 | int sx = getScrollX(); 462 | int sy = getScrollY(); 463 | int dx = x - sx; 464 | int dy = y - sy; 465 | if (dx == 0 && dy == 0) { 466 | completeScroll(false); 467 | setScrollState(SCROLL_STATE_IDLE); 468 | return; 469 | } 470 | 471 | setScrollingCacheEnabled(true); 472 | setScrollState(SCROLL_STATE_SETTLING); 473 | 474 | final int width = getWidth(); 475 | final int halfWidth = width / 2; 476 | final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width); 477 | final float distance = halfWidth + halfWidth * 478 | distanceInfluenceForSnapDuration(distanceRatio); 479 | 480 | int duration = 0; 481 | velocity = Math.abs(velocity); 482 | if (velocity > 0) { 483 | duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 484 | } else { 485 | final float pageDelta = (float) Math.abs(dx) / width; 486 | duration = (int) ((pageDelta + 1) * 100); 487 | } 488 | duration = Math.min(duration, MAX_SETTLE_DURATION); 489 | 490 | mScroller.startScroll(sx, sy, dx, dy, duration); 491 | ViewCompat.postInvalidateOnAnimation(this); 492 | } 493 | 494 | @Override 495 | public void computeScroll() { 496 | if (!mScroller.isFinished() && mScroller.computeScrollOffset()) { 497 | int oldX = getScrollX(); 498 | int oldY = getScrollY(); 499 | int x = mScroller.getCurrX(); 500 | int y = mScroller.getCurrY(); 501 | 502 | if (oldX != x || oldY != y) { 503 | scrollTo(x, y); 504 | if (!pageScrolled(x)) { 505 | mScroller.abortAnimation(); 506 | scrollTo(0, y); 507 | } 508 | } 509 | 510 | // Keep on drawing until the animation has finished. 511 | ViewCompat.postInvalidateOnAnimation(this); 512 | return; 513 | } 514 | 515 | // Done with scroll, clean up state. 516 | completeScroll(true); 517 | } 518 | 519 | private boolean pageScrolled(int xpos) { 520 | if (mPageCount <= 0) { 521 | mCalledSuper = false; 522 | onPageScrolled(0, 0, 0); 523 | if (!mCalledSuper) { 524 | throw new IllegalStateException("onPageScrolled did not call superclass implementation"); 525 | } 526 | return false; 527 | } 528 | final int width = getWidth(); 529 | final int currentPage = xpos / width; 530 | final int offsetPixels = xpos - currentPage * width; 531 | final float pageOffset = (float) offsetPixels / (float) width; 532 | 533 | mCalledSuper = false; 534 | onPageScrolled(currentPage, pageOffset, offsetPixels); 535 | if (!mCalledSuper) { 536 | throw new IllegalStateException("onPageScrolled did not call superclass implementation"); 537 | } 538 | return true; 539 | } 540 | 541 | /** 542 | * This method will be invoked when the current page is scrolled, either as part of a programmatically initiated 543 | * smooth scroll or a user initiated touch scroll. If you override this method you must call through to the 544 | * superclass implementation (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled 545 | * returns. 546 | * 547 | * @param position 548 | * Position index of the first page currently being displayed. Page position+1 will be visible if 549 | * positionOffset is nonzero. 550 | * @param offset 551 | * Value from [0, 1) indicating the offset from the page at position. 552 | * @param offsetPixels 553 | * Value in pixels indicating the offset from position. 554 | */ 555 | protected void onPageScrolled(int position, float offset, int offsetPixels) { 556 | if (mOnPageChangeListener != null) { 557 | mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); 558 | } 559 | mCalledSuper = true; 560 | } 561 | 562 | private void completeScroll(boolean postEvents) { 563 | if (mScrollState == SCROLL_STATE_SETTLING) { 564 | // Done with scroll, no longer want to cache view drawing. 565 | setScrollingCacheEnabled(false); 566 | mScroller.abortAnimation(); 567 | int oldX = getScrollX(); 568 | int oldY = getScrollY(); 569 | int x = mScroller.getCurrX(); 570 | int y = mScroller.getCurrY(); 571 | if (oldX != x || oldY != y) { 572 | scrollTo(x, y); 573 | } 574 | if (postEvents) { 575 | ViewCompat.postOnAnimation(this, mEndScrollRunnable); 576 | } else { 577 | mEndScrollRunnable.run(); 578 | } 579 | } 580 | } 581 | 582 | @Override 583 | public boolean onInterceptTouchEvent(MotionEvent ev) { 584 | /* 585 | * This method JUST determines whether we want to intercept the motion. If we return true, onMotionEvent will be 586 | * called and we do the actual scrolling there. 587 | */ 588 | 589 | final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; 590 | 591 | // Always take care of the touch gesture being complete. 592 | if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 593 | // Release the drag. 594 | DEBUG_LOG("Intercept done!"); 595 | mIsBeingDragged = false; 596 | mIsUnableToDrag = false; 597 | mActivePointerId = INVALID_POINTER; 598 | if (mVelocityTracker != null) { 599 | mVelocityTracker.recycle(); 600 | mVelocityTracker = null; 601 | } 602 | return false; 603 | } 604 | 605 | // Nothing more to do here if we have decided whether or not we 606 | // are dragging. 607 | if (action != MotionEvent.ACTION_DOWN) { 608 | if (mIsBeingDragged || mLastDragged >= 0) { 609 | DEBUG_LOG("Intercept returning true!"); 610 | return true; 611 | } 612 | if (mIsUnableToDrag) { 613 | DEBUG_LOG("Intercept returning false!"); 614 | return false; 615 | } 616 | } 617 | 618 | switch (action) { 619 | case MotionEvent.ACTION_MOVE: { 620 | /* 621 | * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check whether the user has moved 622 | * far enough from his original down touch. 623 | */ 624 | 625 | /* 626 | * Locally do absolute value. mLastMotionY is set to the y value of the down event. 627 | */ 628 | final int activePointerId = mActivePointerId; 629 | if (activePointerId == INVALID_POINTER) { 630 | // If we don't have a valid id, the touch down wasn't on content. 631 | break; 632 | } 633 | 634 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); 635 | final float x = MotionEventCompat.getX(ev, pointerIndex); 636 | final float dx = x - mLastMotionX; 637 | final float xDiff = Math.abs(dx); 638 | final float y = MotionEventCompat.getY(ev, pointerIndex); 639 | final float yDiff = Math.abs(y - mInitialMotionY); 640 | DEBUG_LOG("***Moved to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 641 | 642 | if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) { 643 | DEBUG_LOG("***Starting drag!"); 644 | mIsBeingDragged = true; 645 | requestParentDisallowInterceptTouchEvent(true); 646 | setScrollState(SCROLL_STATE_DRAGGING); 647 | mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop : 648 | mInitialMotionX - mTouchSlop; 649 | mLastMotionY = y; 650 | setScrollingCacheEnabled(true); 651 | } else if (yDiff > mTouchSlop) { 652 | // The finger has moved enough in the vertical 653 | // direction to be counted as a drag... abort 654 | // any attempt to drag horizontally, to work correctly 655 | // with children that have scrolling containers. 656 | DEBUG_LOG("***Unable to drag!"); 657 | mIsUnableToDrag = true; 658 | } 659 | if (mIsBeingDragged) { 660 | // Scroll to follow the motion event 661 | if (performDrag(x)) { 662 | ViewCompat.postInvalidateOnAnimation(this); 663 | } 664 | } 665 | break; 666 | } 667 | 668 | case MotionEvent.ACTION_DOWN: { 669 | /* 670 | * Remember location of down touch. ACTION_DOWN always refers to pointer index 0. 671 | */ 672 | mLastMotionX = mInitialMotionX = ev.getX(); 673 | mLastMotionY = mInitialMotionY = ev.getY(); 674 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 675 | mIsUnableToDrag = false; 676 | 677 | mScroller.computeScrollOffset(); 678 | if (mScrollState == SCROLL_STATE_SETTLING && 679 | Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) { 680 | // Let the user 'catch' the pager as it animates. 681 | mScroller.abortAnimation(); 682 | mIsBeingDragged = true; 683 | requestParentDisallowInterceptTouchEvent(true); 684 | setScrollState(SCROLL_STATE_DRAGGING); 685 | } else { 686 | completeScroll(false); 687 | mIsBeingDragged = false; 688 | } 689 | 690 | DEBUG_LOG("***Down at " + mLastMotionX + "," + mLastMotionY 691 | + " mIsBeingDragged=" + mIsBeingDragged 692 | + " mIsUnableToDrag=" + mIsUnableToDrag); 693 | mLastDragged = -1; 694 | break; 695 | } 696 | 697 | case MotionEventCompat.ACTION_POINTER_UP: 698 | onSecondaryPointerUp(ev); 699 | break; 700 | } 701 | 702 | if (mVelocityTracker == null) { 703 | mVelocityTracker = VelocityTracker.obtain(); 704 | } 705 | mVelocityTracker.addMovement(ev); 706 | 707 | /* 708 | * The only time we want to intercept motion events is if we are in the drag mode. 709 | */ 710 | return mIsBeingDragged; 711 | } 712 | 713 | @Override 714 | public boolean onTouchEvent(MotionEvent ev) { 715 | if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { 716 | // Don't handle edge touches immediately -- they may actually belong to one of our 717 | // descendants. 718 | return false; 719 | } 720 | 721 | if (mPageCount <= 0) { 722 | // Nothing to present or scroll; nothing to touch. 723 | return false; 724 | } 725 | 726 | if (mVelocityTracker == null) { 727 | mVelocityTracker = VelocityTracker.obtain(); 728 | } 729 | mVelocityTracker.addMovement(ev); 730 | 731 | final int action = ev.getAction(); 732 | boolean needsInvalidate = false; 733 | 734 | switch (action & MotionEventCompat.ACTION_MASK) { 735 | case MotionEvent.ACTION_DOWN: { 736 | mScroller.abortAnimation(); 737 | // Remember where the motion event started 738 | mLastMotionX = mInitialMotionX = ev.getX(); 739 | mLastMotionY = mInitialMotionY = ev.getY(); 740 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 741 | 742 | DEBUG_LOG("Down at " + mLastMotionX + "," + mLastMotionY 743 | + " mIsBeingDragged=" + mIsBeingDragged 744 | + " mIsUnableToDrag=" + mIsUnableToDrag); 745 | 746 | if (!mIsBeingDragged && mScrollState == SCROLL_STATE_IDLE) { 747 | mLastPosition = getPositionByXY((int) mLastMotionX, (int) mLastMotionY); 748 | } else { 749 | mLastPosition = -1; 750 | } 751 | if (mLastPosition >= 0) { 752 | mLastDownTime = System.currentTimeMillis(); 753 | } else { 754 | mLastDownTime = Long.MAX_VALUE; 755 | } 756 | DEBUG_LOG("Down at mLastPosition=" + mLastPosition); 757 | mLastDragged = -1; 758 | break; 759 | } 760 | case MotionEvent.ACTION_MOVE: { 761 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 762 | final float x = MotionEventCompat.getX(ev, pointerIndex); 763 | final float y = MotionEventCompat.getY(ev, pointerIndex); 764 | 765 | if (mLastDragged >= 0) { 766 | // change draw location of dragged visual 767 | final View v = getChildAt(mLastDragged); 768 | final int l = getScrollX() + (int) x - v.getWidth() / 2; 769 | final int t = getScrollY() + (int) y - v.getHeight() / 2; 770 | v.layout(l, t, l + v.getWidth(), t + v.getHeight()); 771 | 772 | // check for new target hover 773 | if (mScrollState == SCROLL_STATE_IDLE) { 774 | final int target = getTargetByXY((int) x, (int) y); 775 | if (target != -1 && mLastTarget != target) { 776 | animateGap(target); 777 | mLastTarget = target; 778 | DEBUG_LOG("Moved to mLastTarget=" + mLastTarget); 779 | } 780 | // edge holding 781 | final int edge = getEdgeByXY((int) x, (int) y); 782 | if (mLastEdge == -1) { 783 | if (edge != mLastEdge) { 784 | mLastEdge = edge; 785 | mLastEdgeTime = System.currentTimeMillis(); 786 | } 787 | } else { 788 | if (edge != mLastEdge) { 789 | mLastEdge = -1; 790 | } else { 791 | if ((System.currentTimeMillis() - mLastEdgeTime) >= EDGE_HOLD_DURATION) { 792 | performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 793 | triggerSwipe(edge); 794 | mLastEdge = -1; 795 | } 796 | } 797 | } 798 | } 799 | } else if (!mIsBeingDragged) { 800 | final float xDiff = Math.abs(x - mLastMotionX); 801 | final float yDiff = Math.abs(y - mLastMotionY); 802 | DEBUG_LOG("Moved to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 803 | 804 | if (xDiff > mTouchSlop && xDiff > yDiff) { 805 | DEBUG_LOG("Starting drag!"); 806 | mIsBeingDragged = true; 807 | requestParentDisallowInterceptTouchEvent(true); 808 | mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop : 809 | mInitialMotionX - mTouchSlop; 810 | mLastMotionY = y; 811 | setScrollState(SCROLL_STATE_DRAGGING); 812 | setScrollingCacheEnabled(true); 813 | } 814 | } 815 | // Not else! Note that mIsBeingDragged can be set above. 816 | if (mIsBeingDragged) { 817 | // Scroll to follow the motion event 818 | needsInvalidate |= performDrag(x); 819 | } else if (mLastPosition >= 0) { 820 | final int currentPosition = getPositionByXY((int) x, (int) y); 821 | DEBUG_LOG("Moved to currentPosition=" + currentPosition); 822 | if (currentPosition == mLastPosition) { 823 | if ((System.currentTimeMillis() - mLastDownTime) >= LONG_CLICK_DURATION) { 824 | if (onItemLongClick(currentPosition)) { 825 | performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 826 | mLastDragged = mLastPosition; 827 | requestParentDisallowInterceptTouchEvent(true); 828 | mLastTarget = -1; 829 | animateDragged(); 830 | mLastPosition = -1; 831 | } 832 | mLastDownTime = Long.MAX_VALUE; 833 | } 834 | } else { 835 | mLastPosition = -1; 836 | } 837 | } 838 | break; 839 | } 840 | case MotionEvent.ACTION_UP: { 841 | DEBUG_LOG("Touch up!!!"); 842 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 843 | final float x = MotionEventCompat.getX(ev, pointerIndex); 844 | final float y = MotionEventCompat.getY(ev, pointerIndex); 845 | 846 | if (mLastDragged >= 0) { 847 | rearrange(); 848 | } else if (mIsBeingDragged) { 849 | final VelocityTracker velocityTracker = mVelocityTracker; 850 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 851 | int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(velocityTracker, mActivePointerId); 852 | final int width = getWidth(); 853 | final int scrollX = getScrollX(); 854 | final int currentPage = scrollX / width; 855 | final int offsetPixels = scrollX - currentPage * width; 856 | final float pageOffset = (float) offsetPixels / (float) width; 857 | final int totalDelta = (int) (x - mInitialMotionX); 858 | int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, totalDelta); 859 | setCurrentItemInternal(nextPage, true, true, initialVelocity); 860 | 861 | mActivePointerId = INVALID_POINTER; 862 | endDrag(); 863 | } else if (mLastPosition >= 0) { 864 | final int currentPosition = getPositionByXY((int) x, (int) y); 865 | DEBUG_LOG("Touch up!!! currentPosition=" + currentPosition); 866 | if (currentPosition == mLastPosition) { 867 | onItemClick(currentPosition); 868 | } 869 | } 870 | break; 871 | } 872 | case MotionEvent.ACTION_CANCEL: 873 | DEBUG_LOG("Touch cancel!!!"); 874 | if (mLastDragged >= 0) { 875 | rearrange(); 876 | } else if (mIsBeingDragged) { 877 | scrollToItem(mCurItem, true, 0, false); 878 | mActivePointerId = INVALID_POINTER; 879 | endDrag(); 880 | } 881 | break; 882 | case MotionEventCompat.ACTION_POINTER_DOWN: { 883 | final int index = MotionEventCompat.getActionIndex(ev); 884 | final float x = MotionEventCompat.getX(ev, index); 885 | mLastMotionX = x; 886 | mActivePointerId = MotionEventCompat.getPointerId(ev, index); 887 | break; 888 | } 889 | case MotionEventCompat.ACTION_POINTER_UP: 890 | onSecondaryPointerUp(ev); 891 | mLastMotionX = MotionEventCompat.getX(ev, 892 | MotionEventCompat.findPointerIndex(ev, mActivePointerId)); 893 | break; 894 | } 895 | if (needsInvalidate) { 896 | ViewCompat.postInvalidateOnAnimation(this); 897 | } 898 | return true; 899 | } 900 | 901 | private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) { 902 | final ViewParent parent = getParent(); 903 | if (parent != null) { 904 | parent.requestDisallowInterceptTouchEvent(disallowIntercept); 905 | } 906 | } 907 | 908 | private boolean performDrag(float x) { 909 | boolean needsInvalidate = false; 910 | 911 | final float deltaX = mLastMotionX - x; 912 | mLastMotionX = x; 913 | 914 | float oldScrollX = getScrollX(); 915 | float scrollX = oldScrollX + deltaX; 916 | final int width = getWidth(); 917 | 918 | float leftBound = width * 0; 919 | float rightBound = width * (mPageCount - 1); 920 | 921 | if (scrollX < leftBound) { 922 | final float over = Math.min(leftBound - scrollX, mMaxOverScrollSize); 923 | scrollX = leftBound - over; 924 | } else if (scrollX > rightBound) { 925 | final float over = Math.min(scrollX - rightBound, mMaxOverScrollSize); 926 | scrollX = rightBound + over; 927 | } 928 | // Don't lose the rounded component 929 | mLastMotionX += scrollX - (int) scrollX; 930 | scrollTo((int) scrollX, getScrollY()); 931 | pageScrolled((int) scrollX); 932 | 933 | return needsInvalidate; 934 | } 935 | 936 | private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) { 937 | int targetPage; 938 | if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) { 939 | targetPage = velocity > 0 ? currentPage : currentPage + 1; 940 | } else { 941 | final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f; 942 | targetPage = (int) (currentPage + pageOffset + truncator); 943 | } 944 | return targetPage; 945 | } 946 | 947 | private void onSecondaryPointerUp(MotionEvent ev) { 948 | final int pointerIndex = MotionEventCompat.getActionIndex(ev); 949 | final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 950 | if (pointerId == mActivePointerId) { 951 | // This was our active pointer going up. Choose a new 952 | // active pointer and adjust accordingly. 953 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 954 | mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); 955 | mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 956 | if (mVelocityTracker != null) { 957 | mVelocityTracker.clear(); 958 | } 959 | } 960 | } 961 | 962 | private void endDrag() { 963 | mIsBeingDragged = false; 964 | mIsUnableToDrag = false; 965 | 966 | if (mVelocityTracker != null) { 967 | mVelocityTracker.recycle(); 968 | mVelocityTracker = null; 969 | } 970 | } 971 | 972 | private void setScrollingCacheEnabled(boolean enabled) { 973 | if (mScrollingCacheEnabled != enabled) { 974 | mScrollingCacheEnabled = enabled; 975 | if (USE_CACHE) { 976 | final int size = getChildCount(); 977 | for (int i = 0; i < size; ++i) { 978 | final View child = getChildAt(i); 979 | if (child.getVisibility() != GONE) { 980 | child.setDrawingCacheEnabled(enabled); 981 | } 982 | } 983 | } 984 | } 985 | } 986 | 987 | private void dataSetChanged() { 988 | DEBUG_LOG("dataSetChanged"); 989 | for (int i = 0; i < getChildCount() && i < mAdapter.getCount(); i++) { 990 | final View child = getChildAt(i); 991 | final View newChild = mAdapter.getView(i, child, this); 992 | if (newChild != child) { 993 | removeViewAt(i); 994 | addView(newChild, i); 995 | } 996 | } 997 | for (int i = getChildCount(); i < mAdapter.getCount(); i++) { 998 | final View child = mAdapter.getView(i, null, this); 999 | addView(child); 1000 | } 1001 | while (getChildCount() > mAdapter.getCount()) { 1002 | removeViewAt(getChildCount() - 1); 1003 | } 1004 | } 1005 | 1006 | public void setAdapter(Adapter adapter) { 1007 | if (mAdapter != null) { 1008 | mAdapter.unregisterDataSetObserver(mDataSetObserver); 1009 | removeAllViews(); 1010 | mCurItem = 0; 1011 | scrollTo(0, 0); 1012 | } 1013 | mAdapter = adapter; 1014 | if (mAdapter != null) { 1015 | mAdapter.registerDataSetObserver(mDataSetObserver); 1016 | for (int i = 0; i < mAdapter.getCount(); i++) { 1017 | final View child = mAdapter.getView(i, null, this); 1018 | addView(child); 1019 | } 1020 | } 1021 | } 1022 | 1023 | private Rect getRectByPosition(int position) { 1024 | final int page = position / mPageSize; 1025 | final int col = (position % mPageSize) % mColCount; 1026 | final int row = (position % mPageSize) / mColCount; 1027 | final int left = getWidth() * page + mPaddingLeft + col * (mGridWidth + mGridGap); 1028 | final int top = mPaddingTop + row * (mGridHeight + mGridGap); 1029 | return new Rect(left, top, left + mGridWidth, top + mGridHeight); 1030 | } 1031 | 1032 | private int getPositionByXY(int x, int y) { 1033 | final int col = (x - mPaddingLeft) / (mGridWidth + mGridGap); 1034 | final int row = (y - mPaddingTop) / (mGridHeight + mGridGap); 1035 | if (x < mPaddingLeft || x >= (mPaddingLeft + col * (mGridWidth + mGridGap) + mGridWidth) || 1036 | y < mPaddingTop || y >= (mPaddingTop + row * (mGridHeight + mGridGap) + mGridHeight) || 1037 | col < 0 || col >= mColCount || 1038 | row < 0 || row >= mRowCount) { 1039 | // touch in padding 1040 | return -1; 1041 | } 1042 | final int position = mCurItem * mPageSize + row * mColCount + col; 1043 | if (position < 0 || position >= getChildCount()) { 1044 | // empty item 1045 | return -1; 1046 | } 1047 | return position; 1048 | } 1049 | 1050 | private int getTargetByXY(int x, int y) { 1051 | final int position = getPositionByXY(x, y); 1052 | if (position < 0) { 1053 | return -1; 1054 | } 1055 | final Rect r = getRectByPosition(position); 1056 | final int page = position / mPageSize; 1057 | r.inset(r.width() / 4, r.height() / 4); 1058 | r.offset(-getWidth() * page, 0); 1059 | if (!r.contains(x, y)) { 1060 | return -1; 1061 | } 1062 | return position; 1063 | } 1064 | 1065 | private void onItemClick(int position) { 1066 | DEBUG_LOG("onItemClick position=" + position); 1067 | if (mOnItemClickListener != null) { 1068 | mOnItemClickListener.onItemClick(null, getChildAt(position), position, position / mColCount); 1069 | } 1070 | } 1071 | 1072 | private boolean onItemLongClick(int position) { 1073 | DEBUG_LOG("onItemLongClick position=" + position); 1074 | if (mOnItemLongClickListener != null) { 1075 | return mOnItemLongClickListener.onItemLongClick(null, getChildAt(position), position, position / mColCount); 1076 | } 1077 | return false; 1078 | } 1079 | 1080 | @Override 1081 | protected int getChildDrawingOrder(int childCount, int i) { 1082 | if (mLastDragged == -1) { 1083 | return i; 1084 | } else if (i == childCount - 1) { 1085 | return mLastDragged; 1086 | } else if (i >= mLastDragged) { 1087 | return i + 1; 1088 | } 1089 | return i; 1090 | } 1091 | 1092 | private void animateDragged() { 1093 | if (mLastDragged >= 0) { 1094 | final View v = getChildAt(mLastDragged); 1095 | 1096 | final Rect r = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); 1097 | r.inset(-r.width() / 20, -r.height() / 20); 1098 | v.measure(MeasureSpec.makeMeasureSpec(r.width(), MeasureSpec.EXACTLY), 1099 | MeasureSpec.makeMeasureSpec(r.height(), MeasureSpec.EXACTLY)); 1100 | v.layout(r.left, r.top, r.right, r.bottom); 1101 | 1102 | AnimationSet animSet = new AnimationSet(true); 1103 | ScaleAnimation scale = new ScaleAnimation(0.9091f, 1, 0.9091f, 1, v.getWidth() / 2, v.getHeight() / 2); 1104 | scale.setDuration(ANIMATION_DURATION); 1105 | AlphaAnimation alpha = new AlphaAnimation(1, .5f); 1106 | alpha.setDuration(ANIMATION_DURATION); 1107 | 1108 | animSet.addAnimation(scale); 1109 | animSet.addAnimation(alpha); 1110 | animSet.setFillEnabled(true); 1111 | animSet.setFillAfter(true); 1112 | 1113 | v.clearAnimation(); 1114 | v.startAnimation(animSet); 1115 | } 1116 | } 1117 | 1118 | private void animateGap(int target) { 1119 | for (int i = 0; i < getChildCount(); i++) { 1120 | View v = getChildAt(i); 1121 | if (i == mLastDragged) { 1122 | continue; 1123 | } 1124 | 1125 | int newPos = i; 1126 | if (mLastDragged < target && i >= mLastDragged + 1 && i <= target) { 1127 | newPos--; 1128 | } else if (target < mLastDragged && i >= target && i < mLastDragged) { 1129 | newPos++; 1130 | } 1131 | 1132 | int oldPos = i; 1133 | if (newPositions.get(i) != -1) { 1134 | oldPos = newPositions.get(i); 1135 | } 1136 | 1137 | if (oldPos == newPos) { 1138 | continue; 1139 | } 1140 | 1141 | // animate 1142 | DEBUG_LOG("animateGap from=" + oldPos + ", to=" + newPos); 1143 | final Rect oldRect = getRectByPosition(oldPos); 1144 | final Rect newRect = getRectByPosition(newPos); 1145 | oldRect.offset(-v.getLeft(), -v.getTop()); 1146 | newRect.offset(-v.getLeft(), -v.getTop()); 1147 | 1148 | TranslateAnimation translate = new TranslateAnimation( 1149 | oldRect.left, newRect.left, 1150 | oldRect.top, newRect.top); 1151 | translate.setDuration(ANIMATION_DURATION); 1152 | translate.setFillEnabled(true); 1153 | translate.setFillAfter(true); 1154 | v.clearAnimation(); 1155 | v.startAnimation(translate); 1156 | 1157 | newPositions.set(i, newPos); 1158 | } 1159 | } 1160 | 1161 | private void rearrange() { 1162 | if (mLastDragged >= 0) { 1163 | for (int i = 0; i < getChildCount(); i++) { 1164 | getChildAt(i).clearAnimation(); 1165 | } 1166 | if (mLastTarget >= 0 && mLastDragged != mLastTarget) { 1167 | final View child = getChildAt(mLastDragged); 1168 | removeViewAt(mLastDragged); 1169 | addView(child, mLastTarget); 1170 | if (mOnRearrangeListener != null) { 1171 | mOnRearrangeListener.onRearrange(mLastDragged, mLastTarget); 1172 | } 1173 | } 1174 | mLastDragged = -1; 1175 | mLastTarget = -1; 1176 | requestLayout(); 1177 | invalidate(); 1178 | } 1179 | } 1180 | 1181 | private int getEdgeByXY(int x, int y) { 1182 | if (x < mEdgeSize) { 1183 | return EDGE_LFET; 1184 | } else if (x >= (getWidth() - mEdgeSize)) { 1185 | return EDGE_RIGHT; 1186 | } 1187 | return -1; 1188 | } 1189 | 1190 | private void triggerSwipe(int edge) { 1191 | if (edge == EDGE_LFET && mCurItem > 0) { 1192 | setCurrentItem(mCurItem - 1, true); 1193 | } else if (edge == EDGE_RIGHT && mCurItem < mPageCount - 1) { 1194 | setCurrentItem(mCurItem + 1, true); 1195 | } 1196 | } 1197 | 1198 | @Override 1199 | public void onRestoreInstanceState(Parcelable state) { 1200 | SavedState savedState = (SavedState) state; 1201 | super.onRestoreInstanceState(savedState.getSuperState()); 1202 | mCurItem = savedState.curItem; 1203 | requestLayout(); 1204 | } 1205 | 1206 | @Override 1207 | public Parcelable onSaveInstanceState() { 1208 | Parcelable superState = super.onSaveInstanceState(); 1209 | SavedState savedState = new SavedState(superState); 1210 | savedState.curItem = mCurItem; 1211 | return savedState; 1212 | } 1213 | 1214 | static class SavedState extends BaseSavedState { 1215 | int curItem; 1216 | 1217 | public SavedState(Parcelable superState) { 1218 | super(superState); 1219 | } 1220 | 1221 | private SavedState(Parcel in) { 1222 | super(in); 1223 | curItem = in.readInt(); 1224 | } 1225 | 1226 | @Override 1227 | public void writeToParcel(Parcel dest, int flags) { 1228 | super.writeToParcel(dest, flags); 1229 | dest.writeInt(curItem); 1230 | } 1231 | 1232 | @SuppressWarnings("UnusedDeclaration") 1233 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 1234 | @Override 1235 | public SavedState createFromParcel(Parcel in) { 1236 | return new SavedState(in); 1237 | } 1238 | 1239 | @Override 1240 | public SavedState[] newArray(int size) { 1241 | return new SavedState[size]; 1242 | } 1243 | }; 1244 | } 1245 | 1246 | } 1247 | -------------------------------------------------------------------------------- /DraggableGridViewPagerTest/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | -------------------------------------------------------------------------------- /DraggableGridViewPagerTest/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 3 | org.eclipse.jdt.core.compiler.compliance=1.6 4 | org.eclipse.jdt.core.compiler.source=1.6 5 | -------------------------------------------------------------------------------- /DraggableGridViewPagerTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /DraggableGridViewPagerTest/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzhouj/Android-DraggableGridViewPager/7233dbf9c7e17c33b15a140725b702202d95b6df/DraggableGridViewPagerTest/libs/android-support-v4.jar -------------------------------------------------------------------------------- /DraggableGridViewPagerTest/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | android.library=false 16 | android.library.reference.1=../DraggableGridViewPager 17 | -------------------------------------------------------------------------------- /DraggableGridViewPagerTest/res/drawable-mdpi/rootblock_default_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzhouj/Android-DraggableGridViewPager/7233dbf9c7e17c33b15a140725b702202d95b6df/DraggableGridViewPagerTest/res/drawable-mdpi/rootblock_default_bg.jpg -------------------------------------------------------------------------------- /DraggableGridViewPagerTest/res/layout/draggable_grid_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /DraggableGridViewPagerTest/res/layout/draggable_grid_view_pager_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 20 | 21 | 28 | 29 | 30 | 31 | 36 | 37 |