2 * DragSortListView. A subclass of the Android ListView component that enables
3 * drag and drop re-ordering of list items. Copyright 2012 Carl Bauer Licensed
4 * under the Apache License, Version 2.0 (the "License"); you may not use this
5 * file except in compliance with the License. You may obtain a copy of the
6 * License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
7 * applicable law or agreed to in writing, software distributed under the
8 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
9 * OF ANY KIND, either express or implied. See the License for the specific
10 * language governing permissions and limitations under the License.
13 package com.cyanogenmod.eleven.dragdrop;
15 import android.content.Context;
16 import android.database.DataSetObserver;
17 import android.graphics.Canvas;
18 import android.graphics.Point;
19 import android.graphics.drawable.Drawable;
20 import android.os.SystemClock;
21 import android.util.AttributeSet;
22 import android.view.Gravity;
23 import android.view.MotionEvent;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.widget.AbsListView;
27 import android.widget.BaseAdapter;
28 import android.widget.HeaderViewListAdapter;
29 import android.widget.ListAdapter;
30 import android.widget.ListView;
31 import android.widget.RelativeLayout;
33 import com.cyanogenmod.eleven.R;
36 import java.io.FileWriter;
37 import java.io.IOException;
40 * ListView subclass that mediates drag and drop resorting of items.
44 public class DragSortListView extends ListView {
47 * The View that floats above the ListView and represents the dragged item.
49 private View mFloatView;
52 * A proposed float View location based on touch location and given deltaX
55 private final Point mFloatLoc = new Point();
58 * The middle (in the y-direction) of the floating View.
60 private int mFloatViewMid;
63 * Left edge of floating View.
65 private int mFloatViewLeft;
68 * Top edge of floating View.
70 private int mFloatViewTop;
73 * Watch the Adapter for data changes. Cancel a drag if coincident with a
76 private final DataSetObserver mObserver;
79 * Transparency for the floating View (XML attribute).
81 private final float mFloatAlpha = 1.0f;
83 private float mCurrFloatAlpha = 1.0f;
86 * While drag-sorting, the current position of the floating View. If
87 * dropped, the dragged item will land in this position.
89 private int mFloatPos;
92 * The amount to scroll during the next layout pass. Used only for
93 * drag-scrolling, not standard ListView scrolling.
95 private int mScrollY = 0;
98 * The first expanded ListView position that helps represent the drop slot
99 * tracking the floating View.
101 private int mFirstExpPos;
104 * The second expanded ListView position that helps represent the drop slot
105 * tracking the floating View. This can equal mFirstExpPos if there is no
106 * slide shuffle occurring; otherwise it is equal to mFirstExpPos + 1.
108 private int mSecondExpPos;
111 * Flag set if slide shuffling is enabled.
113 private boolean mAnimate = false;
116 * The user dragged from this position.
121 * Offset (in x) within the dragged item at which the user picked it up (or
122 * first touched down with the digitalis).
124 private int mDragDeltaX;
127 * Offset (in y) within the dragged item at which the user picked it up (or
128 * first touched down with the digitalis).
130 private int mDragDeltaY;
133 * A listener that receives callbacks whenever the floating View hovers over
136 private DragListener mDragListener;
139 * A listener that receives a callback when the floating View is dropped.
141 private DropListener mDropListener;
144 * A listener that receives a callback when the floating View (or more
145 * precisely the originally dragged item) is removed by one of the provided
148 private RemoveListener mRemoveListener;
151 * Enable/Disable item dragging
153 private boolean mDragEnabled = true;
158 private final static int IDLE = 0;
160 private final static int STOPPED = 1;
162 private final static int DRAGGING = 2;
164 private int mDragState = IDLE;
167 * Height in pixels to which the originally dragged item is collapsed during
168 * a drag-sort. Currently, this value must be greater than zero.
170 private int mItemHeightCollapsed = 1;
173 * Height of the floating View. Stored for the purpose of providing the
174 * tracking drop slot.
176 private int mFloatViewHeight;
179 * Convenience member. See above.
181 private int mFloatViewHeightHalf;
184 * Save the given width spec for use in measuring children
186 private int mWidthMeasureSpec = 0;
189 * Sample Views ultimately used for calculating the height of ListView items
190 * that are off-screen.
192 private View[] mSampleViewTypes = new View[1];
195 * Drag-scroll encapsulator!
197 private final DragScroller mDragScroller;
200 * Determines the start of the upward drag-scroll region at the top of the
201 * ListView. Specified by a fraction of the ListView height, thus screen
202 * resolution agnostic.
204 private float mDragUpScrollStartFrac = 1.0f / 3.0f;
207 * Determines the start of the downward drag-scroll region at the bottom of
208 * the ListView. Specified by a fraction of the ListView height, thus screen
209 * resolution agnostic.
211 private float mDragDownScrollStartFrac = 1.0f / 3.0f;
214 * The following are calculated from the above fracs.
216 private int mUpScrollStartY;
218 private int mDownScrollStartY;
220 private float mDownScrollStartYF;
222 private float mUpScrollStartYF;
225 * Calculated from above above and current ListView height.
227 private float mDragUpScrollHeight;
230 * Calculated from above above and current ListView height.
232 private float mDragDownScrollHeight;
235 * Maximum drag-scroll speed in pixels per ms. Only used with default linear
236 * drag-scroll profile.
238 private float mMaxScrollSpeed = 0.3f;
241 * Defines the scroll speed during a drag-scroll. User can provide their
242 * own; this default is a simple linear profile where scroll speed increases
243 * linearly as the floating View nears the top/bottom of the ListView.
245 private DragScrollProfile mScrollProfile = new DragScrollProfile() {
251 public float getSpeed(final float w, final long t) {
252 return mMaxScrollSpeed * w;
272 * Drag flag bit. Floating View can move in the positive x direction.
274 public final static int DRAG_POS_X = 0x1;
277 * Drag flag bit. Floating View can move in the negative x direction.
279 public final static int DRAG_NEG_X = 0x2;
282 * Drag flag bit. Floating View can move in the positive y direction. This
283 * is subtle. What this actually means is that, if enabled, the floating
284 * View can be dragged below its starting position. Remove in favor of
285 * upper-bounding item position?
287 public final static int DRAG_POS_Y = 0x4;
290 * Drag flag bit. Floating View can move in the negative y direction. This
291 * is subtle. What this actually means is that the floating View can be
292 * dragged above its starting position. Remove in favor of lower-bounding
295 public final static int DRAG_NEG_Y = 0x8;
298 * Flags that determine limits on the motion of the floating View. See flags
301 private int mDragFlags = 0;
304 * Last call to an on*TouchEvent was a call to onInterceptTouchEvent.
306 private boolean mLastCallWasIntercept = false;
309 * A touch event is in progress.
311 private boolean mInTouchEvent = false;
314 * Let the user customize the floating View.
316 private FloatViewManager mFloatViewManager = null;
319 * Given to ListView to cancel its action when a drag-sort begins.
321 private final MotionEvent mCancelEvent;
324 * Enum telling where to cancel the ListView action when a drag-sort begins
326 private static final int NO_CANCEL = 0;
328 private static final int ON_TOUCH_EVENT = 1;
330 private static final int ON_INTERCEPT_TOUCH_EVENT = 2;
333 * Where to cancel the ListView action when a drag-sort begins
335 private int mCancelMethod = NO_CANCEL;
338 * Determines when a slide shuffle animation starts. That is, defines how
339 * close to the edge of the drop slot the floating View must be to initiate
342 private float mSlideRegionFrac = 0.25f;
345 * Number between 0 and 1 indicating the relative location of a sliding item
346 * (only used if drag-sort animations are turned on). Nearly 1 means the
347 * item is at the top of the slide region (nearly full blank item is
350 private float mSlideFrac = 0.0f;
353 * Wraps the user-provided ListAdapter. This is used to wrap each item View
354 * given by the user inside another View (currenly a RelativeLayout) which
355 * expands and collapses to simulate the item shuffling.
357 private AdapterWrapper mAdapterWrapper;
360 * Turn on custom debugger.
362 private final boolean mTrackDragSort = false;
367 private DragSortTracker mDragSortTracker;
370 * Needed for adjusting item heights from within layoutChildren
372 private boolean mBlockLayoutRequests = false;
374 private final DragSortController mController;
377 * @param context The {@link Context} to use
378 * @param attrs The attributes of the XML tag that is inflating the view.
380 public DragSortListView(final Context context, final AttributeSet attrs) {
381 super(context, attrs);
382 mItemHeightCollapsed = 1;
384 mCurrFloatAlpha = mFloatAlpha;
386 mSlideRegionFrac = 0.75f;
388 mAnimate = mSlideRegionFrac > 0.0f;
390 setDragScrollStart(mDragUpScrollStartFrac);
392 mController = new DragSortController(this, R.id.edit_track_list_item_handle,
393 DragSortController.ON_DOWN, DragSortController.FLING_RIGHT_REMOVE);
394 mController.setRemoveEnabled(true);
395 mController.setSortEnabled(true);
396 /* Transparent holo light blue */
398 .setBackgroundColor(getResources().getColor(R.color.holo_blue_light_transparent));
400 mFloatViewManager = mController;
401 setOnTouchListener(mController);
403 mDragScroller = new DragScroller();
404 setOnScrollListener(mDragScroller);
406 mCancelEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0f, 0f, 0, 0f,
409 mObserver = new DataSetObserver() {
410 private void cancel() {
411 if (mDragState == DRAGGING) {
420 public void onChanged() {
428 public void onInvalidated() {
435 * Usually called from a FloatViewManager. The float alpha will be reset to
436 * the xml-defined value every time a drag is stopped.
438 public void setFloatAlpha(final float alpha) {
439 mCurrFloatAlpha = alpha;
442 public float getFloatAlpha() {
443 return mCurrFloatAlpha;
447 * Set maximum drag scroll speed in positions/second. Only applies if using
448 * default ScrollSpeedProfile.
450 * @param max Maximum scroll speed.
452 public void setMaxScrollSpeed(final float max) {
453 mMaxScrollSpeed = max;
460 public void setAdapter(final ListAdapter adapter) {
461 mAdapterWrapper = new AdapterWrapper(adapter);
462 adapter.registerDataSetObserver(mObserver);
463 super.setAdapter(mAdapterWrapper);
467 * As opposed to {@link ListView#getAdapter()}, which returns a heavily
468 * wrapped ListAdapter (DragSortListView wraps the input ListAdapter {\emph
469 * and} ListView wraps the wrapped one).
471 * @return The ListAdapter set as the argument of {@link setAdapter()}
473 public ListAdapter getInputAdapter() {
474 if (mAdapterWrapper == null) {
477 return mAdapterWrapper.getAdapter();
481 private class AdapterWrapper extends HeaderViewListAdapter {
482 private final ListAdapter mAdapter;
484 public AdapterWrapper(final ListAdapter adapter) {
485 super(null, null, adapter);
489 public ListAdapter getAdapter() {
497 public View getView(final int position, final View convertView, final ViewGroup parent) {
501 if (convertView != null) {
503 v = (RelativeLayout)convertView;
504 final View oldChild = v.getChildAt(0);
506 child = mAdapter.getView(position, oldChild, v);
507 if (child != oldChild) {
511 } catch (final Exception nullz) {
515 final AbsListView.LayoutParams params = new AbsListView.LayoutParams(
516 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
517 v = new RelativeLayout(getContext());
518 v.setLayoutParams(params);
520 child = mAdapter.getView(position, null, v);
522 } catch (final Exception todo) {
526 adjustItem(position + getHeaderViewsCount(), v, true);
531 private void drawDivider(final int expPosition, final Canvas canvas) {
533 final Drawable divider = getDivider();
534 final int dividerHeight = getDividerHeight();
536 if (divider != null && dividerHeight != 0) {
537 final ViewGroup expItem = (ViewGroup)getChildAt(expPosition - getFirstVisiblePosition());
538 if (expItem != null) {
539 final int l = getPaddingLeft();
540 final int r = getWidth() - getPaddingRight();
544 final int childHeight = expItem.getChildAt(0).getHeight();
546 if (expPosition > mSrcPos) {
547 t = expItem.getTop() + childHeight;
548 b = t + dividerHeight;
550 b = expItem.getBottom() - childHeight;
551 t = b - dividerHeight;
554 divider.setBounds(l, t, r, b);
555 divider.draw(canvas);
564 protected void dispatchDraw(final Canvas canvas) {
565 super.dispatchDraw(canvas);
567 if (mFloatView != null) {
568 if (mFirstExpPos != mSrcPos) {
569 drawDivider(mFirstExpPos, canvas);
571 if (mSecondExpPos != mFirstExpPos && mSecondExpPos != mSrcPos) {
572 drawDivider(mSecondExpPos, canvas);
575 final int w = mFloatView.getWidth();
576 final int h = mFloatView.getHeight();
577 final int alpha = (int)(255f * mCurrFloatAlpha);
580 canvas.translate(mFloatViewLeft, mFloatViewTop);
581 canvas.clipRect(0, 0, w, h);
583 canvas.saveLayerAlpha(0, 0, w, h, alpha, Canvas.ALL_SAVE_FLAG);
584 mFloatView.draw(canvas);
590 private class ItemHeights {
596 private void measureItemAndGetHeights(final int position, final View item,
597 final ItemHeights heights) {
598 ViewGroup.LayoutParams lp = item.getLayoutParams();
600 final boolean isHeadFoot = position < getHeaderViewsCount()
601 || position >= getCount() - getFooterViewsCount();
603 int height = lp == null ? 0 : lp.height;
605 heights.item = height;
607 // get height of child, measure if we have to
609 heights.child = heights.item;
610 } else if (position == mSrcPos) {
613 final View child = ((ViewGroup)item).getChildAt(0);
614 lp = child.getLayoutParams();
615 height = lp == null ? 0 : lp.height;
617 heights.child = height;
619 final int hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
620 final int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
621 getListPaddingLeft() + getListPaddingRight(), lp.width);
622 child.measure(wspec, hspec);
623 heights.child = child.getMeasuredHeight();
627 final int hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
628 final int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft()
629 + getListPaddingRight(), lp == null ? ViewGroup.LayoutParams.MATCH_PARENT
631 item.measure(wspec, hspec);
633 heights.item = item.getMeasuredHeight();
635 heights.child = heights.item;
636 } else if (position == mSrcPos) {
639 heights.child = ((ViewGroup)item).getChildAt(0).getMeasuredHeight();
645 * Get the height of the given wrapped item and its child.
647 * @param position Position from which item was obtained.
648 * @param item List item (usually obtained from
649 * {@link ListView#getChildAt()}).
650 * @param heights Object to fill with heights of item.
652 private void getItemHeights(final int position, final View item, final ItemHeights heights) {
653 final boolean isHeadFoot = position < getHeaderViewsCount()
654 || position >= getCount() - getFooterViewsCount();
656 heights.item = item.getHeight();
659 heights.child = heights.item;
660 } else if (position == mSrcPos) {
663 heights.child = ((ViewGroup)item).getChildAt(0).getHeight();
668 * This function works for arbitrary positions (could be off-screen). If
669 * requested position is off-screen, this function calls
670 * <code>getView</code> to get height information.
672 * @param position ListView position.
673 * @param heights Object to fill with heights of item at
674 * <code>position</code>.
676 private void getItemHeights(final int position, final ItemHeights heights) {
678 final int first = getFirstVisiblePosition();
679 final int last = getLastVisiblePosition();
681 if (position >= first && position <= last) {
682 getItemHeights(position, getChildAt(position - first), heights);
684 // Log.d("mobeta", "getView for height");
686 final ListAdapter adapter = getAdapter();
687 final int type = adapter.getItemViewType(position);
689 // There might be a better place for checking for the following
690 final int typeCount = adapter.getViewTypeCount();
691 if (typeCount != mSampleViewTypes.length) {
692 mSampleViewTypes = new View[typeCount];
697 if (mSampleViewTypes[type] == null) {
698 v = adapter.getView(position, null, this);
699 mSampleViewTypes[type] = v;
701 v = adapter.getView(position, mSampleViewTypes[type], this);
704 // type is HEADER_OR_FOOTER or IGNORE
705 v = adapter.getView(position, null, this);
708 measureItemAndGetHeights(position, v, heights);
713 private int getShuffleEdge(final int position, final int top) {
714 return getShuffleEdge(position, top, null);
718 * Get the shuffle edge for item at position when top of item is at y-coord
723 * @param height Height of item at position. If -1, this function calculates
725 * @return Shuffle line between position-1 and position (for the given view
726 * of the list; that is, for when top of item at position has
727 * y-coord of given `top`). If floating View (treated as horizontal
728 * line) is dropped immediately above this line, it lands in
729 * position-1. If dropped immediately below this line, it lands in
732 private int getShuffleEdge(final int position, final int top, ItemHeights heights) {
734 final int numHeaders = getHeaderViewsCount();
735 final int numFooters = getFooterViewsCount();
737 // shuffle edges are defined between items that can be
738 // dragged; there are N-1 of them if there are N draggable
741 if (position <= numHeaders || position >= getCount() - numFooters) {
745 final int divHeight = getDividerHeight();
749 final int maxBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
751 if (heights == null) {
752 heights = new ItemHeights();
753 getItemHeights(position, heights);
756 // first calculate top of item given that floating View is
757 // centered over src position
759 if (mSecondExpPos <= mSrcPos) {
760 // items are expanded on and/or above the source position
762 if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
763 if (position == mSrcPos) {
764 otop = top + heights.item - mFloatViewHeight;
766 final int blankHeight = heights.item - heights.child;
767 otop = top + blankHeight - maxBlankHeight;
769 } else if (position > mSecondExpPos && position <= mSrcPos) {
770 otop = top - maxBlankHeight;
774 // items are expanded on and/or below the source position
776 if (position > mSrcPos && position <= mFirstExpPos) {
777 otop = top + maxBlankHeight;
778 } else if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
779 final int blankHeight = heights.item - heights.child;
780 otop = top + blankHeight;
785 if (position <= mSrcPos) {
786 final ItemHeights tmpHeights = new ItemHeights();
787 getItemHeights(position - 1, tmpHeights);
788 edge = otop + (mFloatViewHeight - divHeight - tmpHeights.child) / 2;
790 edge = otop + (heights.child - divHeight - mFloatViewHeight) / 2;
796 private boolean updatePositions() {
798 final int first = getFirstVisiblePosition();
799 int startPos = mFirstExpPos;
800 View startView = getChildAt(startPos - first);
802 if (startView == null) {
803 startPos = first + getChildCount() / 2;
804 startView = getChildAt(startPos - first);
806 final int startTop = startView.getTop() + mScrollY;
808 final ItemHeights itemHeights = new ItemHeights();
809 getItemHeights(startPos, startView, itemHeights);
811 int edge = getShuffleEdge(startPos, startTop, itemHeights);
814 final int divHeight = getDividerHeight();
816 // Log.d("mobeta", "float mid="+mFloatViewMid);
818 int itemPos = startPos;
819 int itemTop = startTop;
820 if (mFloatViewMid < edge) {
821 // scanning up for float position
822 // Log.d("mobeta", " edge="+edge);
823 while (itemPos >= 0) {
825 getItemHeights(itemPos, itemHeights);
829 edge = itemTop - divHeight - itemHeights.item;
834 itemTop -= itemHeights.item + divHeight;
835 edge = getShuffleEdge(itemPos, itemTop, itemHeights);
836 // Log.d("mobeta", " edge="+edge);
838 if (mFloatViewMid >= edge) {
845 // scanning down for float position
846 // Log.d("mobeta", " edge="+edge);
847 final int count = getCount();
848 while (itemPos < count) {
849 if (itemPos == count - 1) {
850 edge = itemTop + divHeight + itemHeights.item;
854 itemTop += divHeight + itemHeights.item;
855 getItemHeights(itemPos + 1, itemHeights);
856 edge = getShuffleEdge(itemPos + 1, itemTop, itemHeights);
857 // Log.d("mobeta", " edge="+edge);
860 if (mFloatViewMid < edge) {
869 final int numHeaders = getHeaderViewsCount();
870 final int numFooters = getFooterViewsCount();
872 boolean updated = false;
874 final int oldFirstExpPos = mFirstExpPos;
875 final int oldSecondExpPos = mSecondExpPos;
876 final float oldSlideFrac = mSlideFrac;
879 final int edgeToEdge = Math.abs(edge - lastEdge);
881 int edgeTop, edgeBottom;
882 if (mFloatViewMid < edge) {
887 edgeBottom = lastEdge;
889 // Log.d("mobeta", "edgeTop="+edgeTop+" edgeBot="+edgeBottom);
891 final int slideRgnHeight = (int)(0.5f * mSlideRegionFrac * edgeToEdge);
892 final float slideRgnHeightF = slideRgnHeight;
893 final int slideEdgeTop = edgeTop + slideRgnHeight;
894 final int slideEdgeBottom = edgeBottom - slideRgnHeight;
897 if (mFloatViewMid < slideEdgeTop) {
898 mFirstExpPos = itemPos - 1;
899 mSecondExpPos = itemPos;
900 mSlideFrac = 0.5f * (slideEdgeTop - mFloatViewMid) / slideRgnHeightF;
902 // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
903 } else if (mFloatViewMid < slideEdgeBottom) {
904 mFirstExpPos = itemPos;
905 mSecondExpPos = itemPos;
907 mFirstExpPos = itemPos;
908 mSecondExpPos = itemPos + 1;
909 mSlideFrac = 0.5f * (1.0f + (edgeBottom - mFloatViewMid) / slideRgnHeightF);
911 // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
915 mFirstExpPos = itemPos;
916 mSecondExpPos = itemPos;
919 // correct for headers and footers
920 if (mFirstExpPos < numHeaders) {
921 itemPos = numHeaders;
922 mFirstExpPos = itemPos;
923 mSecondExpPos = itemPos;
924 } else if (mSecondExpPos >= getCount() - numFooters) {
925 itemPos = getCount() - numFooters - 1;
926 mFirstExpPos = itemPos;
927 mSecondExpPos = itemPos;
930 if (mFirstExpPos != oldFirstExpPos || mSecondExpPos != oldSecondExpPos
931 || mSlideFrac != oldSlideFrac) {
935 if (itemPos != mFloatPos) {
936 if (mDragListener != null) {
937 mDragListener.drag(mFloatPos - numHeaders, itemPos - numHeaders);
951 protected void onDraw(final Canvas canvas) {
952 super.onDraw(canvas);
954 if (mTrackDragSort) {
955 mDragSortTracker.appendState();
960 * Stop a drag in progress. Pass <code>true</code> if you would like to
961 * remove the dragged item from the list.
963 * @param remove Remove the dragged item from the list. Calls a registered
964 * DropListener, if one exists.
965 * @return True if the stop was successful.
967 public boolean stopDrag(final boolean remove) {
968 if (mFloatView != null) {
969 mDragState = STOPPED;
972 dropFloatView(remove);
985 public boolean onTouchEvent(final MotionEvent ev) {
988 return super.onTouchEvent(ev);
991 boolean more = false;
993 final boolean lastCallWasIntercept = mLastCallWasIntercept;
994 mLastCallWasIntercept = false;
996 if (!lastCallWasIntercept) {
1000 if (mFloatView != null) {
1001 onDragTouchEvent(ev);
1002 more = true; // give us more!
1004 // what if float view is null b/c we dropped in middle
1005 // of drag touch event?
1007 if (mDragState != STOPPED) {
1008 if (super.onTouchEvent(ev)) {
1013 final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1015 case MotionEvent.ACTION_CANCEL:
1016 case MotionEvent.ACTION_UP:
1017 doActionUpOrCancel();
1021 mCancelMethod = ON_TOUCH_EVENT;
1030 private void doActionUpOrCancel() {
1031 mCancelMethod = NO_CANCEL;
1032 mInTouchEvent = false;
1034 mCurrFloatAlpha = mFloatAlpha;
1037 private void saveTouchCoords(final MotionEvent ev) {
1038 final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1039 if (action != MotionEvent.ACTION_DOWN) {
1042 mX = (int)ev.getX();
1043 mY = (int)ev.getY();
1044 if (action == MotionEvent.ACTION_DOWN) {
1053 public boolean onInterceptTouchEvent(final MotionEvent ev) {
1054 if (!mDragEnabled) {
1055 return super.onInterceptTouchEvent(ev);
1058 saveTouchCoords(ev);
1059 mLastCallWasIntercept = true;
1061 boolean intercept = false;
1063 final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1065 if (action == MotionEvent.ACTION_DOWN) {
1066 mInTouchEvent = true;
1069 // the following deals with calls to super.onInterceptTouchEvent
1070 if (mFloatView != null) {
1071 // super's touch event canceled in startDrag
1074 if (super.onInterceptTouchEvent(ev)) {
1079 case MotionEvent.ACTION_CANCEL:
1080 case MotionEvent.ACTION_UP:
1081 doActionUpOrCancel();
1085 mCancelMethod = ON_TOUCH_EVENT;
1087 mCancelMethod = ON_INTERCEPT_TOUCH_EVENT;
1092 // check for startDragging
1094 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
1095 mInTouchEvent = false;
1102 * Set the width of each drag scroll region by specifying a fraction of the
1105 * @param heightFraction Fraction of ListView height. Capped at 0.5f.
1107 public void setDragScrollStart(final float heightFraction) {
1108 setDragScrollStarts(heightFraction, heightFraction);
1112 * Set the width of each drag scroll region by specifying a fraction of the
1115 * @param upperFrac Fraction of ListView height for up-scroll bound. Capped
1117 * @param lowerFrac Fraction of ListView height for down-scroll bound.
1120 public void setDragScrollStarts(final float upperFrac, final float lowerFrac) {
1121 if (lowerFrac > 0.5f) {
1122 mDragDownScrollStartFrac = 0.5f;
1124 mDragDownScrollStartFrac = lowerFrac;
1127 if (upperFrac > 0.5f) {
1128 mDragUpScrollStartFrac = 0.5f;
1130 mDragUpScrollStartFrac = upperFrac;
1133 if (getHeight() != 0) {
1134 updateScrollStarts();
1138 private void continueDrag(final int x, final int y) {
1140 // Log.d("mobeta", "move");
1143 // if (mTrackDragSort) {
1144 // mDragSortTracker.appendState();
1149 final int minY = Math.min(y, mFloatViewMid + mFloatViewHeightHalf);
1150 final int maxY = Math.max(y, mFloatViewMid - mFloatViewHeightHalf);
1152 // get the current scroll direction
1153 final int currentScrollDir = mDragScroller.getScrollDir();
1155 if (minY > mLastY && minY > mDownScrollStartY && currentScrollDir != DragScroller.DOWN) {
1156 // dragged down, it is below the down scroll start and it is not
1159 if (currentScrollDir != DragScroller.STOP) {
1160 // moved directly from up scroll to down scroll
1161 mDragScroller.stopScrolling(true);
1164 // start scrolling down
1165 mDragScroller.startScrolling(DragScroller.DOWN);
1166 } else if (maxY < mLastY && maxY < mUpScrollStartY && currentScrollDir != DragScroller.UP) {
1167 // dragged up, it is above the up scroll start and it is not
1170 if (currentScrollDir != DragScroller.STOP) {
1171 // moved directly from down scroll to up scroll
1172 mDragScroller.stopScrolling(true);
1175 // start scrolling up
1176 mDragScroller.startScrolling(DragScroller.UP);
1177 } else if (maxY >= mUpScrollStartY && minY <= mDownScrollStartY
1178 && mDragScroller.isScrolling()) {
1179 // not in the upper nor in the lower drag-scroll regions but it is
1182 mDragScroller.stopScrolling(true);
1186 private void updateScrollStarts() {
1187 final int padTop = getPaddingTop();
1188 final int listHeight = getHeight() - padTop - getPaddingBottom();
1189 final float heightF = listHeight;
1191 mUpScrollStartYF = padTop + mDragUpScrollStartFrac * heightF;
1192 mDownScrollStartYF = padTop + (1.0f - mDragDownScrollStartFrac) * heightF;
1194 mUpScrollStartY = (int)mUpScrollStartYF;
1195 mDownScrollStartY = (int)mDownScrollStartYF;
1197 mDragUpScrollHeight = mUpScrollStartYF - padTop;
1198 mDragDownScrollHeight = padTop + listHeight - mDownScrollStartYF;
1205 protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
1206 super.onSizeChanged(w, h, oldw, oldh);
1207 updateScrollStarts();
1210 private void dropFloatView(final boolean removeSrcItem) {
1212 mDragScroller.stopScrolling(true);
1214 if (removeSrcItem) {
1215 if (mRemoveListener != null) {
1216 mRemoveListener.remove(mSrcPos - getHeaderViewsCount());
1219 if (mDropListener != null && mFloatPos >= 0 && mFloatPos < getCount()) {
1220 final int numHeaders = getHeaderViewsCount();
1221 mDropListener.drop(mSrcPos - numHeaders, mFloatPos - numHeaders);
1224 // adjustAllItems();
1226 final int firstPos = getFirstVisiblePosition();
1227 if (mSrcPos < firstPos) {
1228 // collapsed src item is off screen;
1229 // adjust the scroll after item heights have been fixed
1230 final View v = getChildAt(0);
1235 // Log.d("mobeta", "top="+top+" fvh="+mFloatViewHeight);
1236 setSelectionFromTop(firstPos - 1, top - getPaddingTop());
1247 if (mTrackDragSort) {
1248 mDragSortTracker.stopTracking();
1252 private void adjustAllItems() {
1253 final int first = getFirstVisiblePosition();
1254 final int last = getLastVisiblePosition();
1256 final int begin = Math.max(0, getHeaderViewsCount() - first);
1257 final int end = Math.min(last - first, getCount() - 1 - getFooterViewsCount() - first);
1259 for (int i = begin; i <= end; ++i) {
1260 final View v = getChildAt(i);
1262 adjustItem(first + i, v, false);
1267 private void adjustItem(final int position, final View v, final boolean needsMeasure) {
1269 final ViewGroup.LayoutParams lp = v.getLayoutParams();
1270 final int oldHeight = lp.height;
1271 int height = oldHeight;
1275 final boolean isSliding = mAnimate && mFirstExpPos != mSecondExpPos;
1276 final int maxNonSrcBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
1277 final int slideHeight = (int)(mSlideFrac * maxNonSrcBlankHeight);
1279 if (position == mSrcPos) {
1280 if (mSrcPos == mFirstExpPos) {
1282 height = slideHeight + mItemHeightCollapsed;
1284 height = mFloatViewHeight;
1286 } else if (mSrcPos == mSecondExpPos) {
1287 // if gets here, we know an item is sliding
1288 height = mFloatViewHeight - slideHeight;
1290 height = mItemHeightCollapsed;
1292 } else if (position == mFirstExpPos || position == mSecondExpPos) {
1293 // position is not src
1295 final ItemHeights itemHeights = new ItemHeights();
1297 measureItemAndGetHeights(position, v, itemHeights);
1299 getItemHeights(position, v, itemHeights);
1302 if (position == mFirstExpPos) {
1304 height = itemHeights.child + slideHeight;
1306 height = itemHeights.child + maxNonSrcBlankHeight;
1308 } else { // position=mSecondExpPos
1309 // we know an item is sliding (b/c 2ndPos != 1stPos)
1310 height = itemHeights.child + maxNonSrcBlankHeight - slideHeight;
1313 height = ViewGroup.LayoutParams.WRAP_CONTENT;
1316 if (height != oldHeight) {
1319 v.setLayoutParams(lp);
1322 // Adjust item gravity
1324 if (position == mFirstExpPos || position == mSecondExpPos) {
1325 if (position < mSrcPos) {
1326 ((RelativeLayout)v).setGravity(Gravity.BOTTOM);
1327 } else if (position > mSrcPos) {
1328 ((RelativeLayout)v).setGravity(Gravity.TOP);
1332 // Finally adjust item visibility
1334 final int oldVis = v.getVisibility();
1335 int vis = View.VISIBLE;
1337 if (position == mSrcPos && mFloatView != null) {
1338 vis = View.INVISIBLE;
1341 if (vis != oldVis) {
1342 v.setVisibility(vis);
1350 public void requestLayout() {
1351 if (!mBlockLayoutRequests) {
1352 super.requestLayout();
1356 private void doDragScroll(final int oldFirstExpPos, final int oldSecondExpPos) {
1357 if (mScrollY == 0) {
1361 final int padTop = getPaddingTop();
1362 final int listHeight = getHeight() - padTop - getPaddingBottom();
1363 final int first = getFirstVisiblePosition();
1364 final int last = getLastVisiblePosition();
1368 if (mScrollY >= 0) {
1369 mScrollY = Math.min(listHeight, mScrollY);
1372 mScrollY = Math.max(-listHeight, mScrollY);
1376 final View moveItem = getChildAt(movePos - first);
1377 int top = moveItem.getTop() + mScrollY;
1379 if (movePos == 0 && top > padTop) {
1383 final ItemHeights itemHeightsBefore = new ItemHeights();
1384 getItemHeights(movePos, moveItem, itemHeightsBefore);
1385 final int moveHeightBefore = itemHeightsBefore.item;
1386 final int moveBlankBefore = moveHeightBefore - itemHeightsBefore.child;
1388 final ItemHeights itemHeightsAfter = new ItemHeights();
1389 measureItemAndGetHeights(movePos, moveItem, itemHeightsAfter);
1390 final int moveHeightAfter = itemHeightsAfter.item;
1391 final int moveBlankAfter = moveHeightAfter - itemHeightsAfter.child;
1393 if (movePos <= oldFirstExpPos) {
1394 if (movePos > mFirstExpPos) {
1395 top += mFloatViewHeight - moveBlankAfter;
1397 } else if (movePos == oldSecondExpPos) {
1398 if (movePos <= mFirstExpPos) {
1399 top += moveBlankBefore - mFloatViewHeight;
1400 } else if (movePos == mSecondExpPos) {
1401 top += moveHeightBefore - moveHeightAfter;
1403 top += moveBlankBefore;
1406 if (movePos <= mFirstExpPos) {
1407 top -= mFloatViewHeight;
1408 } else if (movePos == mSecondExpPos) {
1409 top -= moveBlankAfter;
1413 setSelectionFromTop(movePos, top - padTop);
1418 private void measureFloatView() {
1419 if (mFloatView != null) {
1420 ViewGroup.LayoutParams lp = mFloatView.getLayoutParams();
1422 lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1423 ViewGroup.LayoutParams.WRAP_CONTENT);
1425 final int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft()
1426 + getListPaddingRight(), lp.width);
1428 if (lp.height > 0) {
1429 hspec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
1431 hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1433 mFloatView.measure(wspec, hspec);
1434 mFloatViewHeight = mFloatView.getMeasuredHeight();
1435 mFloatViewHeightHalf = mFloatViewHeight / 2;
1443 protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
1444 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1445 if (mFloatView != null) {
1446 if (mFloatView.isLayoutRequested()) {
1450 mWidthMeasureSpec = widthMeasureSpec;
1451 mDragScroller.setListHeight(getHeight());
1458 protected void layoutChildren() {
1460 if (mFloatView != null) {
1461 mFloatView.layout(0, 0, mFloatView.getMeasuredWidth(), mFloatView.getMeasuredHeight());
1463 // Log.d("mobeta", "layout children");
1464 final int oldFirstExpPos = mFirstExpPos;
1465 final int oldSecondExpPos = mSecondExpPos;
1467 mBlockLayoutRequests = true;
1469 if (getChildCount() > 0 && updatePositions()) {
1473 if (mScrollY != 0) {
1474 doDragScroll(oldFirstExpPos, oldSecondExpPos);
1477 mBlockLayoutRequests = false;
1480 super.layoutChildren();
1483 protected boolean onDragTouchEvent(final MotionEvent ev) {
1484 switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1485 case MotionEvent.ACTION_CANCEL:
1486 case MotionEvent.ACTION_UP:
1488 doActionUpOrCancel();
1490 case MotionEvent.ACTION_MOVE:
1491 continueDrag((int)ev.getX(), (int)ev.getY());
1499 * Start a drag of item at <code>position</code> using the registered
1500 * FloatViewManager. Calls through to
1501 * {@link #startDrag(int,View,int,int,int)} after obtaining the floating
1502 * View from the FloatViewManager.
1504 * @param position Item to drag.
1505 * @param dragFlags Flags that restrict some movements of the floating View.
1506 * For example, set <code>dragFlags |=
1507 * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating View in all
1508 * directions except off the screen to the left.
1509 * @param deltaX Offset in x of the touch coordinate from the left edge of
1510 * the floating View (i.e. touch-x minus float View left).
1511 * @param deltaY Offset in y of the touch coordinate from the top edge of
1512 * the floating View (i.e. touch-y minus float View top).
1513 * @return True if the drag was started, false otherwise. This
1514 * <code>startDrag</code> will fail if we are not currently in a
1515 * touch event, there is no registered FloatViewManager, or the
1516 * FloatViewManager returns a null View.
1518 public boolean startDrag(final int position, final int dragFlags, final int deltaX,
1520 if (!mInTouchEvent || mFloatViewManager == null) {
1524 final View v = mFloatViewManager.onCreateFloatView(position);
1529 return startDrag(position, v, dragFlags, deltaX, deltaY);
1535 * Start a drag of item at <code>position</code> without using a
1538 * @param position Item to drag.
1539 * @param floatView Floating View.
1540 * @param dragFlags Flags that restrict some movements of the floating View.
1541 * For example, set <code>dragFlags |=
1542 * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating View in all
1543 * directions except off the screen to the left.
1544 * @param deltaX Offset in x of the touch coordinate from the left edge of
1545 * the floating View (i.e. touch-x minus float View left).
1546 * @param deltaY Offset in y of the touch coordinate from the top edge of
1547 * the floating View (i.e. touch-y minus float View top).
1548 * @return True if the drag was started, false otherwise. This
1549 * <code>startDrag</code> will fail if we are not currently in a
1550 * touch event, <code>floatView</code> is null, or there is a drag
1553 public boolean startDrag(final int position, final View floatView, final int dragFlags,
1554 final int deltaX, final int deltaY) {
1555 if (!mInTouchEvent || mFloatView != null || floatView == null) {
1559 if (getParent() != null) {
1560 getParent().requestDisallowInterceptTouchEvent(true);
1563 final int pos = position + getHeaderViewsCount();
1565 mSecondExpPos = pos;
1569 // mDragState = dragType;
1570 mDragState = DRAGGING;
1572 mDragFlags |= dragFlags;
1574 mFloatView = floatView;
1575 measureFloatView(); // sets mFloatViewHeight
1577 mDragDeltaX = deltaX;
1578 mDragDeltaY = deltaY;
1579 updateFloatView(mX - mDragDeltaX, mY - mDragDeltaY);
1581 // set src item invisible
1582 final View srcItem = getChildAt(mSrcPos - getFirstVisiblePosition());
1583 if (srcItem != null) {
1584 srcItem.setVisibility(View.INVISIBLE);
1587 if (mTrackDragSort) {
1588 mDragSortTracker.startTracking();
1591 // once float view is created, events are no longer passed
1593 switch (mCancelMethod) {
1594 case ON_TOUCH_EVENT:
1595 super.onTouchEvent(mCancelEvent);
1597 case ON_INTERCEPT_TOUCH_EVENT:
1598 super.onInterceptTouchEvent(mCancelEvent);
1608 * Sets float View location based on suggested values and constraints set in
1611 private void updateFloatView(final int floatX, final int floatY) {
1613 // restrict x motion
1614 final int padLeft = getPaddingLeft();
1615 if ((mDragFlags & DRAG_POS_X) == 0 && floatX > padLeft) {
1616 mFloatViewLeft = padLeft;
1617 } else if ((mDragFlags & DRAG_NEG_X) == 0 && floatX < padLeft) {
1618 mFloatViewLeft = padLeft;
1620 mFloatViewLeft = floatX;
1623 // keep floating view from going past bottom of last header view
1624 final int numHeaders = getHeaderViewsCount();
1625 final int numFooters = getFooterViewsCount();
1626 final int firstPos = getFirstVisiblePosition();
1627 final int lastPos = getLastVisiblePosition();
1630 // "nHead="+numHeaders+" nFoot="+numFooters+" first="+firstPos+" last="+lastPos);
1631 int topLimit = getPaddingTop();
1632 if (firstPos < numHeaders) {
1633 topLimit = getChildAt(numHeaders - firstPos - 1).getBottom();
1635 if ((mDragFlags & DRAG_NEG_Y) == 0) {
1636 if (firstPos <= mSrcPos) {
1637 topLimit = Math.max(getChildAt(mSrcPos - firstPos).getTop(), topLimit);
1640 // bottom limit is top of first footer View or
1641 // bottom of last item in list
1642 int bottomLimit = getHeight() - getPaddingBottom();
1643 if (lastPos >= getCount() - numFooters - 1) {
1644 bottomLimit = getChildAt(getCount() - numFooters - 1 - firstPos).getBottom();
1646 if ((mDragFlags & DRAG_POS_Y) == 0) {
1647 if (lastPos >= mSrcPos) {
1648 bottomLimit = Math.min(getChildAt(mSrcPos - firstPos).getBottom(), bottomLimit);
1652 // Log.d("mobeta", "dragView top=" + (y - mDragDeltaY));
1653 // Log.d("mobeta", "limit=" + limit);
1654 // Log.d("mobeta", "mDragDeltaY=" + mDragDeltaY);
1656 if (floatY < topLimit) {
1657 mFloatViewTop = topLimit;
1658 } else if (floatY + mFloatViewHeight > bottomLimit) {
1659 mFloatViewTop = bottomLimit - mFloatViewHeight;
1661 mFloatViewTop = floatY;
1664 // get y-midpoint of floating view (constrained to ListView bounds)
1665 mFloatViewMid = mFloatViewTop + mFloatViewHeightHalf;
1668 private void dragView(final int x, final int y) {
1669 // Log.d("mobeta", "float view pure x=" + x + " y=" + y);
1671 // proposed position
1672 mFloatLoc.x = x - mDragDeltaX;
1673 mFloatLoc.y = y - mDragDeltaY;
1675 final Point touch = new Point(x, y);
1677 // let manager adjust proposed position first
1678 if (mFloatViewManager != null) {
1679 mFloatViewManager.onDragFloatView(mFloatView, mFloatLoc, touch);
1682 // then we override if manager gives an unsatisfactory
1683 // position (e.g. over a header/footer view). Also,
1684 // dragFlags override manager adjustments.
1685 updateFloatView(mFloatLoc.x, mFloatLoc.y);
1688 private void removeFloatView() {
1689 if (mFloatView != null) {
1690 mFloatView.setVisibility(GONE);
1691 if (mFloatViewManager != null) {
1692 mFloatViewManager.onDestroyFloatView(mFloatView);
1699 * Interface for customization of the floating View appearance and dragging
1700 * behavior. Implement your own and pass it to {@link #setFloatViewManager}.
1701 * If your own is not passed, the default {@link SimpleFloatViewManager}
1702 * implementation is used.
1704 public interface FloatViewManager {
1706 * Return the floating View for item at <code>position</code>.
1707 * DragSortListView will measure and layout this View for you, so feel
1708 * free to just inflate it. You can help DSLV by setting some
1709 * {@link ViewGroup.LayoutParams} on this View; otherwise it will set
1710 * some for you (with a width of FILL_PARENT and a height of
1713 * @param position Position of item to drag (NOTE: <code>position</code>
1714 * excludes header Views; thus, if you want to call
1715 * {@link ListView#getChildAt(int)}, you will need to add
1716 * {@link ListView#getHeaderViewsCount()} to the index).
1717 * @return The View you wish to display as the floating View.
1719 public View onCreateFloatView(int position);
1722 * Called whenever the floating View is dragged. Float View properties
1723 * can be changed here. Also, the upcoming location of the float View
1724 * can be altered by setting <code>location.x</code> and
1725 * <code>location.y</code>.
1727 * @param floatView The floating View.
1728 * @param location The location (top-left; relative to DSLV top-left) at
1729 * which the float View would like to appear, given the
1730 * current touch location and the offset provided in
1731 * {@link DragSortListView#startDrag}.
1732 * @param touch The current touch location (relative to DSLV top-left).
1734 public void onDragFloatView(View floatView, Point location, Point touch);
1737 * Called when the float View is dropped; lets you perform any necessary
1738 * cleanup. The internal DSLV floating View reference is set to null
1739 * immediately after this is called.
1741 * @param floatView The floating View passed to
1742 * {@link #onCreateFloatView(int)}.
1744 public void onDestroyFloatView(View floatView);
1747 public void setFloatViewManager(final FloatViewManager manager) {
1748 mFloatViewManager = manager;
1751 public void setDragListener(final DragListener l) {
1756 * Allows for easy toggling between a DragSortListView and a regular old
1757 * ListView. If enabled, items are draggable, where the drag init mode
1758 * determines how items are lifted (see {@link setDragInitMode(int)}). If
1759 * disabled, items cannot be dragged.
1761 * @param enabled Set <code>true</code> to enable list item dragging
1763 public void setDragEnabled(final boolean enabled) {
1764 mDragEnabled = enabled;
1767 public boolean isDragEnabled() {
1768 return mDragEnabled;
1772 * This better reorder your ListAdapter! DragSortListView does not do this
1773 * for you; doesn't make sense to. Make sure
1774 * {@link BaseAdapter#notifyDataSetChanged()} or something like it is called
1775 * in your implementation.
1779 public void setDropListener(final DropListener l) {
1784 * Probably a no-brainer, but make sure that your remove listener calls
1785 * {@link BaseAdapter#notifyDataSetChanged()} or something like it. When an
1786 * item removal occurs, DragSortListView relies on a redraw of all the items
1787 * to recover invisible views and such. Strictly speaking, if you remove
1788 * something, your dataset has changed...
1792 public void setRemoveListener(final RemoveListener l) {
1793 if (mController != null && l == null) {
1794 mController.setRemoveEnabled(false);
1796 mRemoveListener = l;
1799 public interface DragListener {
1800 public void drag(int from, int to);
1804 * Your implementation of this has to reorder your ListAdapter! Make sure to
1805 * call {@link BaseAdapter#notifyDataSetChanged()} or something like it in
1806 * your implementation.
1810 public interface DropListener {
1811 public void drop(int from, int to);
1815 * Make sure to call {@link BaseAdapter#notifyDataSetChanged()} or something
1816 * like it in your implementation.
1820 public interface RemoveListener {
1821 public void remove(int which);
1824 public interface DragSortListener extends DropListener, DragListener, RemoveListener {
1827 public void setDragSortListener(final DragSortListener l) {
1830 setRemoveListener(l);
1834 * Completely custom scroll speed profile. Default increases linearly with
1835 * position and is constant in time. Create your own by implementing
1836 * {@link DragSortListView.DragScrollProfile}.
1840 public void setDragScrollProfile(final DragScrollProfile ssp) {
1842 mScrollProfile = ssp;
1847 * Interface for controlling scroll speed as a function of touch position
1849 * {@link DragSortListView#setDragScrollProfile(DragScrollProfile)} to set
1854 public interface DragScrollProfile {
1856 * Return a scroll speed in pixels/millisecond. Always return a positive
1859 * @param w Normalized position in scroll region (i.e. w \in [0,1]).
1860 * Small w typically means slow scrolling.
1861 * @param t Time (in milliseconds) since start of scroll (handy if you
1862 * want scroll acceleration).
1863 * @return Scroll speed at position w and time t in pixels/ms.
1865 float getSpeed(float w, long t);
1868 private class DragScroller implements Runnable, AbsListView.OnScrollListener {
1870 private boolean mAbort;
1872 private long mPrevTime;
1878 private long tStart;
1880 private int scrollDir;
1882 public final static int STOP = -1;
1884 public final static int UP = 0;
1886 public final static int DOWN = 1;
1888 private float mScrollSpeed; // pixels per ms
1890 private boolean mScrolling = false;
1892 private int mMaxScrollSpeed;
1894 public boolean isScrolling() {
1898 public int getScrollDir() {
1899 return mScrolling ? scrollDir : STOP;
1902 public DragScroller() {
1905 public void startScrolling(final int dir) {
1907 // Debug.startMethodTracing("dslv-scroll");
1910 tStart = SystemClock.uptimeMillis();
1917 public void stopScrolling(final boolean now) {
1919 removeCallbacks(this);
1927 public void setListHeight(final int height) {
1928 // cap the max scroll speed per frame to be 1/5 of the list height
1929 mMaxScrollSpeed = height / 5;
1942 final int first = getFirstVisiblePosition();
1943 final int last = getLastVisiblePosition();
1944 final int count = getCount();
1945 final int padTop = getPaddingTop();
1946 final int listHeight = getHeight() - padTop - getPaddingBottom();
1948 final int minY = Math.min(mY, mFloatViewMid + mFloatViewHeightHalf);
1949 final int maxY = Math.max(mY, mFloatViewMid - mFloatViewHeightHalf);
1951 if (scrollDir == UP) {
1952 final View v = getChildAt(0);
1957 if (first == 0 && v.getTop() == padTop) {
1962 mScrollSpeed = mScrollProfile.getSpeed((mUpScrollStartYF - maxY)
1963 / mDragUpScrollHeight, mPrevTime);
1965 final View v = getChildAt(last - first);
1970 if (last == count - 1 && v.getBottom() <= listHeight + padTop) {
1975 mScrollSpeed = -mScrollProfile.getSpeed((minY - mDownScrollStartYF)
1976 / mDragDownScrollHeight, mPrevTime);
1979 dt = SystemClock.uptimeMillis() - mPrevTime;
1980 // dy is change in View position of a list item; i.e. positive dy
1981 // means user is scrolling up (list item moves down the screen,
1983 // y=0 is at top of View).
1984 dy = Math.round(mScrollSpeed * dt);
1987 // cap the scroll speed
1988 mScrollY = Math.max(Math.min(mScrollY, mMaxScrollSpeed), -mMaxScrollSpeed);
2001 public void onScroll(final AbsListView view, final int firstVisibleItem,
2002 final int visibleItemCount, final int totalItemCount) {
2003 if (mScrolling && visibleItemCount != 0) {
2012 public void onScrollStateChanged(final AbsListView view, final int scrollState) {
2017 private class DragSortTracker {
2018 StringBuilder mBuilder = new StringBuilder();
2022 private int mNumInBuffer = 0;
2024 private int mNumFlushes = 0;
2026 private boolean mTracking = false;
2028 public void startTracking() {
2029 mBuilder.append("<DSLVStates>\n");
2034 public void appendState() {
2039 mBuilder.append("<DSLVState>\n");
2040 final int children = getChildCount();
2041 final int first = getFirstVisiblePosition();
2042 final ItemHeights itemHeights = new ItemHeights();
2043 mBuilder.append(" <Positions>");
2044 for (int i = 0; i < children; ++i) {
2045 mBuilder.append(first + i).append(",");
2047 mBuilder.append("</Positions>\n");
2049 mBuilder.append(" <Tops>");
2050 for (int i = 0; i < children; ++i) {
2051 mBuilder.append(getChildAt(i).getTop()).append(",");
2053 mBuilder.append("</Tops>\n");
2054 mBuilder.append(" <Bottoms>");
2055 for (int i = 0; i < children; ++i) {
2056 mBuilder.append(getChildAt(i).getBottom()).append(",");
2058 mBuilder.append("</Bottoms>\n");
2060 mBuilder.append(" <FirstExpPos>").append(mFirstExpPos).append("</FirstExpPos>\n");
2061 getItemHeights(mFirstExpPos, itemHeights);
2062 mBuilder.append(" <FirstExpBlankHeight>")
2063 .append(itemHeights.item - itemHeights.child)
2064 .append("</FirstExpBlankHeight>\n");
2065 mBuilder.append(" <SecondExpPos>").append(mSecondExpPos).append("</SecondExpPos>\n");
2066 getItemHeights(mSecondExpPos, itemHeights);
2067 mBuilder.append(" <SecondExpBlankHeight>")
2068 .append(itemHeights.item - itemHeights.child)
2069 .append("</SecondExpBlankHeight>\n");
2070 mBuilder.append(" <SrcPos>").append(mSrcPos).append("</SrcPos>\n");
2071 mBuilder.append(" <SrcHeight>").append(mFloatViewHeight + getDividerHeight())
2072 .append("</SrcHeight>\n");
2073 mBuilder.append(" <ViewHeight>").append(getHeight()).append("</ViewHeight>\n");
2074 mBuilder.append(" <LastY>").append(mLastY).append("</LastY>\n");
2075 mBuilder.append(" <FloatY>").append(mFloatViewMid).append("</FloatY>\n");
2076 mBuilder.append(" <ShuffleEdges>");
2077 for (int i = 0; i < children; ++i) {
2078 mBuilder.append(getShuffleEdge(first + i, getChildAt(i).getTop())).append(",");
2080 mBuilder.append("</ShuffleEdges>\n");
2082 mBuilder.append("</DSLVState>\n");
2085 if (mNumInBuffer > 1000) {
2091 public void flush() {
2096 // save to file on sdcard
2098 boolean append = true;
2099 if (mNumFlushes == 0) {
2102 final FileWriter writer = new FileWriter(mFile, append);
2104 writer.write(mBuilder.toString());
2105 mBuilder.delete(0, mBuilder.length());
2111 } catch (final IOException e) {
2116 public void stopTracking() {
2118 mBuilder.append("</DSLVStates>\n");