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);
397 .setBackgroundColor(getResources().getColor(R.color.accent));
399 mFloatViewManager = mController;
400 setOnTouchListener(mController);
402 mDragScroller = new DragScroller();
403 setOnScrollListener(mDragScroller);
405 mCancelEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0f, 0f, 0, 0f,
408 mObserver = new DataSetObserver() {
409 private void cancel() {
410 if (mDragState == DRAGGING) {
419 public void onChanged() {
427 public void onInvalidated() {
434 * Usually called from a FloatViewManager. The float alpha will be reset to
435 * the xml-defined value every time a drag is stopped.
437 public void setFloatAlpha(final float alpha) {
438 mCurrFloatAlpha = alpha;
441 public float getFloatAlpha() {
442 return mCurrFloatAlpha;
446 * Set maximum drag scroll speed in positions/second. Only applies if using
447 * default ScrollSpeedProfile.
449 * @param max Maximum scroll speed.
451 public void setMaxScrollSpeed(final float max) {
452 mMaxScrollSpeed = max;
459 public void setAdapter(final ListAdapter adapter) {
460 mAdapterWrapper = new AdapterWrapper(adapter);
461 adapter.registerDataSetObserver(mObserver);
462 super.setAdapter(mAdapterWrapper);
466 * As opposed to {@link ListView#getAdapter()}, which returns a heavily
467 * wrapped ListAdapter (DragSortListView wraps the input ListAdapter {\emph
468 * and} ListView wraps the wrapped one).
470 * @return The ListAdapter set as the argument of {@link setAdapter()}
472 public ListAdapter getInputAdapter() {
473 if (mAdapterWrapper == null) {
476 return mAdapterWrapper.getAdapter();
480 private class AdapterWrapper extends HeaderViewListAdapter {
481 private final ListAdapter mAdapter;
483 public AdapterWrapper(final ListAdapter adapter) {
484 super(null, null, adapter);
488 public ListAdapter getAdapter() {
496 public View getView(final int position, final View convertView, final ViewGroup parent) {
500 if (convertView != null) {
502 v = (RelativeLayout)convertView;
503 final View oldChild = v.getChildAt(0);
505 child = mAdapter.getView(position, oldChild, v);
506 if (child != oldChild) {
510 } catch (final Exception nullz) {
514 final AbsListView.LayoutParams params = new AbsListView.LayoutParams(
515 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
516 v = new RelativeLayout(getContext());
517 v.setLayoutParams(params);
519 child = mAdapter.getView(position, null, v);
521 } catch (final Exception todo) {
525 adjustItem(position + getHeaderViewsCount(), v, true);
530 private void drawDivider(final int expPosition, final Canvas canvas) {
532 final Drawable divider = getDivider();
533 final int dividerHeight = getDividerHeight();
535 if (divider != null && dividerHeight != 0) {
536 final ViewGroup expItem = (ViewGroup)getChildAt(expPosition - getFirstVisiblePosition());
537 if (expItem != null) {
538 final int l = getPaddingLeft();
539 final int r = getWidth() - getPaddingRight();
543 final int childHeight = expItem.getChildAt(0).getHeight();
545 if (expPosition > mSrcPos) {
546 t = expItem.getTop() + childHeight;
547 b = t + dividerHeight;
549 b = expItem.getBottom() - childHeight;
550 t = b - dividerHeight;
553 divider.setBounds(l, t, r, b);
554 divider.draw(canvas);
563 protected void dispatchDraw(final Canvas canvas) {
564 super.dispatchDraw(canvas);
566 if (mFloatView != null) {
567 if (mFirstExpPos != mSrcPos) {
568 drawDivider(mFirstExpPos, canvas);
570 if (mSecondExpPos != mFirstExpPos && mSecondExpPos != mSrcPos) {
571 drawDivider(mSecondExpPos, canvas);
574 final int w = mFloatView.getWidth();
575 final int h = mFloatView.getHeight();
576 final int alpha = (int)(255f * mCurrFloatAlpha);
579 canvas.translate(mFloatViewLeft, mFloatViewTop);
580 canvas.clipRect(0, 0, w, h);
582 canvas.saveLayerAlpha(0, 0, w, h, alpha, Canvas.ALL_SAVE_FLAG);
583 mFloatView.draw(canvas);
589 private class ItemHeights {
595 private void measureItemAndGetHeights(final int position, final View item,
596 final ItemHeights heights) {
597 ViewGroup.LayoutParams lp = item.getLayoutParams();
599 final boolean isHeadFoot = position < getHeaderViewsCount()
600 || position >= getCount() - getFooterViewsCount();
602 int height = lp == null ? 0 : lp.height;
604 heights.item = height;
606 // get height of child, measure if we have to
608 heights.child = heights.item;
609 } else if (position == mSrcPos) {
612 final View child = ((ViewGroup)item).getChildAt(0);
613 lp = child.getLayoutParams();
614 height = lp == null ? 0 : lp.height;
616 heights.child = height;
618 final int hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
619 final int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
620 getListPaddingLeft() + getListPaddingRight(), lp.width);
621 child.measure(wspec, hspec);
622 heights.child = child.getMeasuredHeight();
626 final int hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
627 final int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft()
628 + getListPaddingRight(), lp == null ? ViewGroup.LayoutParams.MATCH_PARENT
630 item.measure(wspec, hspec);
632 heights.item = item.getMeasuredHeight();
634 heights.child = heights.item;
635 } else if (position == mSrcPos) {
638 heights.child = ((ViewGroup)item).getChildAt(0).getMeasuredHeight();
644 * Get the height of the given wrapped item and its child.
646 * @param position Position from which item was obtained.
647 * @param item List item (usually obtained from
648 * {@link ListView#getChildAt()}).
649 * @param heights Object to fill with heights of item.
651 private void getItemHeights(final int position, final View item, final ItemHeights heights) {
652 final boolean isHeadFoot = position < getHeaderViewsCount()
653 || position >= getCount() - getFooterViewsCount();
655 heights.item = item.getHeight();
658 heights.child = heights.item;
659 } else if (position == mSrcPos) {
662 heights.child = ((ViewGroup)item).getChildAt(0).getHeight();
667 * This function works for arbitrary positions (could be off-screen). If
668 * requested position is off-screen, this function calls
669 * <code>getView</code> to get height information.
671 * @param position ListView position.
672 * @param heights Object to fill with heights of item at
673 * <code>position</code>.
675 private void getItemHeights(final int position, final ItemHeights heights) {
677 final int first = getFirstVisiblePosition();
678 final int last = getLastVisiblePosition();
680 if (position >= first && position <= last) {
681 getItemHeights(position, getChildAt(position - first), heights);
683 // Log.d("mobeta", "getView for height");
685 final ListAdapter adapter = getAdapter();
686 final int type = adapter.getItemViewType(position);
688 // There might be a better place for checking for the following
689 final int typeCount = adapter.getViewTypeCount();
690 if (typeCount != mSampleViewTypes.length) {
691 mSampleViewTypes = new View[typeCount];
696 if (mSampleViewTypes[type] == null) {
697 v = adapter.getView(position, null, this);
698 mSampleViewTypes[type] = v;
700 v = adapter.getView(position, mSampleViewTypes[type], this);
703 // type is HEADER_OR_FOOTER or IGNORE
704 v = adapter.getView(position, null, this);
707 measureItemAndGetHeights(position, v, heights);
712 private int getShuffleEdge(final int position, final int top) {
713 return getShuffleEdge(position, top, null);
717 * Get the shuffle edge for item at position when top of item is at y-coord
722 * @param height Height of item at position. If -1, this function calculates
724 * @return Shuffle line between position-1 and position (for the given view
725 * of the list; that is, for when top of item at position has
726 * y-coord of given `top`). If floating View (treated as horizontal
727 * line) is dropped immediately above this line, it lands in
728 * position-1. If dropped immediately below this line, it lands in
731 private int getShuffleEdge(final int position, final int top, ItemHeights heights) {
733 final int numHeaders = getHeaderViewsCount();
734 final int numFooters = getFooterViewsCount();
736 // shuffle edges are defined between items that can be
737 // dragged; there are N-1 of them if there are N draggable
740 if (position <= numHeaders || position >= getCount() - numFooters) {
744 final int divHeight = getDividerHeight();
748 final int maxBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
750 if (heights == null) {
751 heights = new ItemHeights();
752 getItemHeights(position, heights);
755 // first calculate top of item given that floating View is
756 // centered over src position
758 if (mSecondExpPos <= mSrcPos) {
759 // items are expanded on and/or above the source position
761 if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
762 if (position == mSrcPos) {
763 otop = top + heights.item - mFloatViewHeight;
765 final int blankHeight = heights.item - heights.child;
766 otop = top + blankHeight - maxBlankHeight;
768 } else if (position > mSecondExpPos && position <= mSrcPos) {
769 otop = top - maxBlankHeight;
773 // items are expanded on and/or below the source position
775 if (position > mSrcPos && position <= mFirstExpPos) {
776 otop = top + maxBlankHeight;
777 } else if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
778 final int blankHeight = heights.item - heights.child;
779 otop = top + blankHeight;
784 if (position <= mSrcPos) {
785 final ItemHeights tmpHeights = new ItemHeights();
786 getItemHeights(position - 1, tmpHeights);
787 edge = otop + (mFloatViewHeight - divHeight - tmpHeights.child) / 2;
789 edge = otop + (heights.child - divHeight - mFloatViewHeight) / 2;
795 private boolean updatePositions() {
797 final int first = getFirstVisiblePosition();
798 int startPos = mFirstExpPos;
799 View startView = getChildAt(startPos - first);
801 if (startView == null) {
802 startPos = first + getChildCount() / 2;
803 startView = getChildAt(startPos - first);
805 final int startTop = startView.getTop() + mScrollY;
807 final ItemHeights itemHeights = new ItemHeights();
808 getItemHeights(startPos, startView, itemHeights);
810 int edge = getShuffleEdge(startPos, startTop, itemHeights);
813 final int divHeight = getDividerHeight();
815 // Log.d("mobeta", "float mid="+mFloatViewMid);
817 int itemPos = startPos;
818 int itemTop = startTop;
819 if (mFloatViewMid < edge) {
820 // scanning up for float position
821 // Log.d("mobeta", " edge="+edge);
822 while (itemPos >= 0) {
824 getItemHeights(itemPos, itemHeights);
828 edge = itemTop - divHeight - itemHeights.item;
833 itemTop -= itemHeights.item + divHeight;
834 edge = getShuffleEdge(itemPos, itemTop, itemHeights);
835 // Log.d("mobeta", " edge="+edge);
837 if (mFloatViewMid >= edge) {
844 // scanning down for float position
845 // Log.d("mobeta", " edge="+edge);
846 final int count = getCount();
847 while (itemPos < count) {
848 if (itemPos == count - 1) {
849 edge = itemTop + divHeight + itemHeights.item;
853 itemTop += divHeight + itemHeights.item;
854 getItemHeights(itemPos + 1, itemHeights);
855 edge = getShuffleEdge(itemPos + 1, itemTop, itemHeights);
856 // Log.d("mobeta", " edge="+edge);
859 if (mFloatViewMid < edge) {
868 final int numHeaders = getHeaderViewsCount();
869 final int numFooters = getFooterViewsCount();
871 boolean updated = false;
873 final int oldFirstExpPos = mFirstExpPos;
874 final int oldSecondExpPos = mSecondExpPos;
875 final float oldSlideFrac = mSlideFrac;
878 final int edgeToEdge = Math.abs(edge - lastEdge);
880 int edgeTop, edgeBottom;
881 if (mFloatViewMid < edge) {
886 edgeBottom = lastEdge;
888 // Log.d("mobeta", "edgeTop="+edgeTop+" edgeBot="+edgeBottom);
890 final int slideRgnHeight = (int)(0.5f * mSlideRegionFrac * edgeToEdge);
891 final int slideEdgeTop = edgeTop + slideRgnHeight;
892 final int slideEdgeBottom = edgeBottom - slideRgnHeight;
895 if (mFloatViewMid < slideEdgeTop) {
896 mFirstExpPos = itemPos - 1;
897 mSecondExpPos = itemPos;
898 mSlideFrac = 0.5f * (slideEdgeTop - mFloatViewMid) / (float) slideRgnHeight;
900 // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
901 } else if (mFloatViewMid < slideEdgeBottom) {
902 mFirstExpPos = itemPos;
903 mSecondExpPos = itemPos;
905 mFirstExpPos = itemPos;
906 mSecondExpPos = itemPos + 1;
907 mSlideFrac = 0.5f * (1.0f + (edgeBottom - mFloatViewMid) / (float) slideRgnHeight);
909 // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
913 mFirstExpPos = itemPos;
914 mSecondExpPos = itemPos;
917 // correct for headers and footers
918 if (mFirstExpPos < numHeaders) {
919 itemPos = numHeaders;
920 mFirstExpPos = itemPos;
921 mSecondExpPos = itemPos;
922 } else if (mSecondExpPos >= getCount() - numFooters) {
923 itemPos = getCount() - numFooters - 1;
924 mFirstExpPos = itemPos;
925 mSecondExpPos = itemPos;
928 if (mFirstExpPos != oldFirstExpPos || mSecondExpPos != oldSecondExpPos
929 || mSlideFrac != oldSlideFrac) {
933 if (itemPos != mFloatPos) {
934 if (mDragListener != null) {
935 mDragListener.drag(mFloatPos - numHeaders, itemPos - numHeaders);
949 protected void onDraw(final Canvas canvas) {
950 super.onDraw(canvas);
952 if (mTrackDragSort) {
953 mDragSortTracker.appendState();
958 * Stop a drag in progress. Pass <code>true</code> if you would like to
959 * remove the dragged item from the list.
961 * @param remove Remove the dragged item from the list. Calls a registered
962 * DropListener, if one exists.
963 * @return True if the stop was successful.
965 public boolean stopDrag(final boolean remove) {
966 if (mFloatView != null) {
967 mDragState = STOPPED;
970 dropFloatView(remove);
983 public boolean onTouchEvent(final MotionEvent ev) {
986 return super.onTouchEvent(ev);
989 boolean more = false;
991 final boolean lastCallWasIntercept = mLastCallWasIntercept;
992 mLastCallWasIntercept = false;
994 if (!lastCallWasIntercept) {
998 if (mFloatView != null) {
999 onDragTouchEvent(ev);
1000 more = true; // give us more!
1002 // what if float view is null b/c we dropped in middle
1003 // of drag touch event?
1005 if (mDragState != STOPPED) {
1006 if (super.onTouchEvent(ev)) {
1011 final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1013 case MotionEvent.ACTION_CANCEL:
1014 case MotionEvent.ACTION_UP:
1015 doActionUpOrCancel();
1019 mCancelMethod = ON_TOUCH_EVENT;
1028 private void doActionUpOrCancel() {
1029 mCancelMethod = NO_CANCEL;
1030 mInTouchEvent = false;
1032 mCurrFloatAlpha = mFloatAlpha;
1035 private void saveTouchCoords(final MotionEvent ev) {
1036 final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1037 if (action != MotionEvent.ACTION_DOWN) {
1040 mX = (int)ev.getX();
1041 mY = (int)ev.getY();
1042 if (action == MotionEvent.ACTION_DOWN) {
1051 public boolean onInterceptTouchEvent(final MotionEvent ev) {
1052 if (!mDragEnabled) {
1053 return super.onInterceptTouchEvent(ev);
1056 saveTouchCoords(ev);
1057 mLastCallWasIntercept = true;
1059 boolean intercept = false;
1061 final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1063 if (action == MotionEvent.ACTION_DOWN) {
1064 mInTouchEvent = true;
1067 // the following deals with calls to super.onInterceptTouchEvent
1068 if (mFloatView != null) {
1069 // super's touch event canceled in startDrag
1072 if (super.onInterceptTouchEvent(ev)) {
1077 case MotionEvent.ACTION_CANCEL:
1078 case MotionEvent.ACTION_UP:
1079 doActionUpOrCancel();
1083 mCancelMethod = ON_TOUCH_EVENT;
1085 mCancelMethod = ON_INTERCEPT_TOUCH_EVENT;
1090 // check for startDragging
1092 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
1093 mInTouchEvent = false;
1100 * Set the width of each drag scroll region by specifying a fraction of the
1103 * @param heightFraction Fraction of ListView height. Capped at 0.5f.
1105 public void setDragScrollStart(final float heightFraction) {
1106 setDragScrollStarts(heightFraction, heightFraction);
1110 * Set the width of each drag scroll region by specifying a fraction of the
1113 * @param upperFrac Fraction of ListView height for up-scroll bound. Capped
1115 * @param lowerFrac Fraction of ListView height for down-scroll bound.
1118 public void setDragScrollStarts(final float upperFrac, final float lowerFrac) {
1119 if (lowerFrac > 0.5f) {
1120 mDragDownScrollStartFrac = 0.5f;
1122 mDragDownScrollStartFrac = lowerFrac;
1125 if (upperFrac > 0.5f) {
1126 mDragUpScrollStartFrac = 0.5f;
1128 mDragUpScrollStartFrac = upperFrac;
1131 if (getHeight() != 0) {
1132 updateScrollStarts();
1136 private void continueDrag(final int x, final int y) {
1138 // Log.d("mobeta", "move");
1141 // if (mTrackDragSort) {
1142 // mDragSortTracker.appendState();
1147 final int minY = Math.min(y, mFloatViewMid + mFloatViewHeightHalf);
1148 final int maxY = Math.max(y, mFloatViewMid - mFloatViewHeightHalf);
1150 // get the current scroll direction
1151 final int currentScrollDir = mDragScroller.getScrollDir();
1153 if (minY > mLastY && minY > mDownScrollStartY && currentScrollDir != DragScroller.DOWN) {
1154 // dragged down, it is below the down scroll start and it is not
1157 if (currentScrollDir != DragScroller.STOP) {
1158 // moved directly from up scroll to down scroll
1159 mDragScroller.stopScrolling(true);
1162 // start scrolling down
1163 mDragScroller.startScrolling(DragScroller.DOWN);
1164 } else if (maxY < mLastY && maxY < mUpScrollStartY && currentScrollDir != DragScroller.UP) {
1165 // dragged up, it is above the up scroll start and it is not
1168 if (currentScrollDir != DragScroller.STOP) {
1169 // moved directly from down scroll to up scroll
1170 mDragScroller.stopScrolling(true);
1173 // start scrolling up
1174 mDragScroller.startScrolling(DragScroller.UP);
1175 } else if (maxY >= mUpScrollStartY && minY <= mDownScrollStartY
1176 && mDragScroller.isScrolling()) {
1177 // not in the upper nor in the lower drag-scroll regions but it is
1180 mDragScroller.stopScrolling(true);
1184 private void updateScrollStarts() {
1185 final int padTop = getPaddingTop();
1186 final int listHeight = getHeight() - padTop - getPaddingBottom();
1188 mUpScrollStartYF = padTop + mDragUpScrollStartFrac * (float) listHeight;
1189 mDownScrollStartYF = padTop + (1.0f - mDragDownScrollStartFrac) * (float) listHeight;
1191 mUpScrollStartY = (int)mUpScrollStartYF;
1192 mDownScrollStartY = (int)mDownScrollStartYF;
1194 mDragUpScrollHeight = mUpScrollStartYF - padTop;
1195 mDragDownScrollHeight = padTop + listHeight - mDownScrollStartYF;
1202 protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
1203 super.onSizeChanged(w, h, oldw, oldh);
1204 updateScrollStarts();
1207 private void dropFloatView(final boolean removeSrcItem) {
1209 mDragScroller.stopScrolling(true);
1211 if (removeSrcItem) {
1212 if (mRemoveListener != null) {
1213 mRemoveListener.remove(mSrcPos - getHeaderViewsCount());
1216 if (mDropListener != null && mFloatPos >= 0 && mFloatPos < getCount()) {
1217 final int numHeaders = getHeaderViewsCount();
1218 mDropListener.drop(mSrcPos - numHeaders, mFloatPos - numHeaders);
1221 // adjustAllItems();
1223 final int firstPos = getFirstVisiblePosition();
1224 if (mSrcPos < firstPos) {
1225 // collapsed src item is off screen;
1226 // adjust the scroll after item heights have been fixed
1227 final View v = getChildAt(0);
1232 // Log.d("mobeta", "top="+top+" fvh="+mFloatViewHeight);
1233 setSelectionFromTop(firstPos - 1, top - getPaddingTop());
1244 if (mTrackDragSort) {
1245 mDragSortTracker.stopTracking();
1249 private void adjustAllItems() {
1250 final int first = getFirstVisiblePosition();
1251 final int last = getLastVisiblePosition();
1253 final int begin = Math.max(0, getHeaderViewsCount() - first);
1254 final int end = Math.min(last - first, getCount() - 1 - getFooterViewsCount() - first);
1256 for (int i = begin; i <= end; ++i) {
1257 final View v = getChildAt(i);
1259 adjustItem(first + i, v, false);
1264 private void adjustItem(final int position, final View v, final boolean needsMeasure) {
1266 final ViewGroup.LayoutParams lp = v.getLayoutParams();
1267 final int oldHeight = lp.height;
1268 int height = oldHeight;
1272 final boolean isSliding = mAnimate && mFirstExpPos != mSecondExpPos;
1273 final int maxNonSrcBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
1274 final int slideHeight = (int)(mSlideFrac * maxNonSrcBlankHeight);
1276 if (position == mSrcPos) {
1277 if (mSrcPos == mFirstExpPos) {
1279 height = slideHeight + mItemHeightCollapsed;
1281 height = mFloatViewHeight;
1283 } else if (mSrcPos == mSecondExpPos) {
1284 // if gets here, we know an item is sliding
1285 height = mFloatViewHeight - slideHeight;
1287 height = mItemHeightCollapsed;
1289 } else if (position == mFirstExpPos || position == mSecondExpPos) {
1290 // position is not src
1292 final ItemHeights itemHeights = new ItemHeights();
1294 measureItemAndGetHeights(position, v, itemHeights);
1296 getItemHeights(position, v, itemHeights);
1299 if (position == mFirstExpPos) {
1301 height = itemHeights.child + slideHeight;
1303 height = itemHeights.child + maxNonSrcBlankHeight;
1305 } else { // position=mSecondExpPos
1306 // we know an item is sliding (b/c 2ndPos != 1stPos)
1307 height = itemHeights.child + maxNonSrcBlankHeight - slideHeight;
1310 height = ViewGroup.LayoutParams.WRAP_CONTENT;
1313 if (height != oldHeight) {
1316 v.setLayoutParams(lp);
1319 // Adjust item gravity
1321 if (position == mFirstExpPos || position == mSecondExpPos) {
1322 if (position < mSrcPos) {
1323 ((RelativeLayout)v).setGravity(Gravity.BOTTOM);
1324 } else if (position > mSrcPos) {
1325 ((RelativeLayout)v).setGravity(Gravity.TOP);
1329 // Finally adjust item visibility
1331 final int oldVis = v.getVisibility();
1332 int vis = View.VISIBLE;
1334 if (position == mSrcPos && mFloatView != null) {
1335 vis = View.INVISIBLE;
1338 if (vis != oldVis) {
1339 v.setVisibility(vis);
1347 public void requestLayout() {
1348 if (!mBlockLayoutRequests) {
1349 super.requestLayout();
1353 private void doDragScroll(final int oldFirstExpPos, final int oldSecondExpPos) {
1354 if (mScrollY == 0) {
1358 final int padTop = getPaddingTop();
1359 final int listHeight = getHeight() - padTop - getPaddingBottom();
1360 final int first = getFirstVisiblePosition();
1361 final int last = getLastVisiblePosition();
1365 if (mScrollY >= 0) {
1366 mScrollY = Math.min(listHeight, mScrollY);
1369 mScrollY = Math.max(-listHeight, mScrollY);
1373 final View moveItem = getChildAt(movePos - first);
1374 int top = moveItem.getTop() + mScrollY;
1376 if (movePos == 0 && top > padTop) {
1380 final ItemHeights itemHeightsBefore = new ItemHeights();
1381 getItemHeights(movePos, moveItem, itemHeightsBefore);
1382 final int moveHeightBefore = itemHeightsBefore.item;
1383 final int moveBlankBefore = moveHeightBefore - itemHeightsBefore.child;
1385 final ItemHeights itemHeightsAfter = new ItemHeights();
1386 measureItemAndGetHeights(movePos, moveItem, itemHeightsAfter);
1387 final int moveHeightAfter = itemHeightsAfter.item;
1388 final int moveBlankAfter = moveHeightAfter - itemHeightsAfter.child;
1390 if (movePos <= oldFirstExpPos) {
1391 if (movePos > mFirstExpPos) {
1392 top += mFloatViewHeight - moveBlankAfter;
1394 } else if (movePos == oldSecondExpPos) {
1395 if (movePos <= mFirstExpPos) {
1396 top += moveBlankBefore - mFloatViewHeight;
1397 } else if (movePos == mSecondExpPos) {
1398 top += moveHeightBefore - moveHeightAfter;
1400 top += moveBlankBefore;
1403 if (movePos <= mFirstExpPos) {
1404 top -= mFloatViewHeight;
1405 } else if (movePos == mSecondExpPos) {
1406 top -= moveBlankAfter;
1410 setSelectionFromTop(movePos, top - padTop);
1415 private void measureFloatView() {
1416 if (mFloatView != null) {
1417 ViewGroup.LayoutParams lp = mFloatView.getLayoutParams();
1419 lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1420 ViewGroup.LayoutParams.WRAP_CONTENT);
1422 final int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft()
1423 + getListPaddingRight(), lp.width);
1425 if (lp.height > 0) {
1426 hspec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
1428 hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1430 mFloatView.measure(wspec, hspec);
1431 mFloatViewHeight = mFloatView.getMeasuredHeight();
1432 mFloatViewHeightHalf = mFloatViewHeight / 2;
1440 protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
1441 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1442 if (mFloatView != null) {
1443 if (mFloatView.isLayoutRequested()) {
1447 mWidthMeasureSpec = widthMeasureSpec;
1448 mDragScroller.setListHeight(getHeight());
1455 protected void layoutChildren() {
1457 if (mFloatView != null) {
1458 mFloatView.layout(0, 0, mFloatView.getMeasuredWidth(), mFloatView.getMeasuredHeight());
1460 // Log.d("mobeta", "layout children");
1461 final int oldFirstExpPos = mFirstExpPos;
1462 final int oldSecondExpPos = mSecondExpPos;
1464 mBlockLayoutRequests = true;
1466 if (getChildCount() > 0 && updatePositions()) {
1470 if (mScrollY != 0) {
1471 doDragScroll(oldFirstExpPos, oldSecondExpPos);
1474 mBlockLayoutRequests = false;
1477 super.layoutChildren();
1480 protected boolean onDragTouchEvent(final MotionEvent ev) {
1481 switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1482 case MotionEvent.ACTION_CANCEL:
1483 case MotionEvent.ACTION_UP:
1485 doActionUpOrCancel();
1487 case MotionEvent.ACTION_MOVE:
1488 continueDrag((int)ev.getX(), (int)ev.getY());
1496 * Start a drag of item at <code>position</code> using the registered
1497 * FloatViewManager. Calls through to
1498 * {@link #startDrag(int,View,int,int,int)} after obtaining the floating
1499 * View from the FloatViewManager.
1501 * @param position Item to drag.
1502 * @param dragFlags Flags that restrict some movements of the floating View.
1503 * For example, set <code>dragFlags |=
1504 * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating View in all
1505 * directions except off the screen to the left.
1506 * @param deltaX Offset in x of the touch coordinate from the left edge of
1507 * the floating View (i.e. touch-x minus float View left).
1508 * @param deltaY Offset in y of the touch coordinate from the top edge of
1509 * the floating View (i.e. touch-y minus float View top).
1510 * @return True if the drag was started, false otherwise. This
1511 * <code>startDrag</code> will fail if we are not currently in a
1512 * touch event, there is no registered FloatViewManager, or the
1513 * FloatViewManager returns a null View.
1515 public boolean startDrag(final int position, final int dragFlags, final int deltaX,
1517 if (!mInTouchEvent || mFloatViewManager == null) {
1521 final View v = mFloatViewManager.onCreateFloatView(position);
1526 return startDrag(position, v, dragFlags, deltaX, deltaY);
1532 * Start a drag of item at <code>position</code> without using a
1535 * @param position Item to drag.
1536 * @param floatView Floating View.
1537 * @param dragFlags Flags that restrict some movements of the floating View.
1538 * For example, set <code>dragFlags |=
1539 * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating View in all
1540 * directions except off the screen to the left.
1541 * @param deltaX Offset in x of the touch coordinate from the left edge of
1542 * the floating View (i.e. touch-x minus float View left).
1543 * @param deltaY Offset in y of the touch coordinate from the top edge of
1544 * the floating View (i.e. touch-y minus float View top).
1545 * @return True if the drag was started, false otherwise. This
1546 * <code>startDrag</code> will fail if we are not currently in a
1547 * touch event, <code>floatView</code> is null, or there is a drag
1550 public boolean startDrag(final int position, final View floatView, final int dragFlags,
1551 final int deltaX, final int deltaY) {
1552 if (!mInTouchEvent || mFloatView != null || floatView == null) {
1556 if (getParent() != null) {
1557 getParent().requestDisallowInterceptTouchEvent(true);
1560 final int pos = position + getHeaderViewsCount();
1562 mSecondExpPos = pos;
1566 // mDragState = dragType;
1567 mDragState = DRAGGING;
1569 mDragFlags |= dragFlags;
1571 mFloatView = floatView;
1572 measureFloatView(); // sets mFloatViewHeight
1574 mDragDeltaX = deltaX;
1575 mDragDeltaY = deltaY;
1576 updateFloatView(mX - mDragDeltaX, mY - mDragDeltaY);
1578 // set src item invisible
1579 final View srcItem = getChildAt(mSrcPos - getFirstVisiblePosition());
1580 if (srcItem != null) {
1581 srcItem.setVisibility(View.INVISIBLE);
1584 if (mTrackDragSort) {
1585 mDragSortTracker.startTracking();
1588 // once float view is created, events are no longer passed
1590 switch (mCancelMethod) {
1591 case ON_TOUCH_EVENT:
1592 super.onTouchEvent(mCancelEvent);
1594 case ON_INTERCEPT_TOUCH_EVENT:
1595 super.onInterceptTouchEvent(mCancelEvent);
1605 * Sets float View location based on suggested values and constraints set in
1608 private void updateFloatView(final int floatX, final int floatY) {
1610 // restrict x motion
1611 final int padLeft = getPaddingLeft();
1612 if ((mDragFlags & DRAG_POS_X) == 0 && floatX > padLeft) {
1613 mFloatViewLeft = padLeft;
1614 } else if ((mDragFlags & DRAG_NEG_X) == 0 && floatX < padLeft) {
1615 mFloatViewLeft = padLeft;
1617 mFloatViewLeft = floatX;
1620 // keep floating view from going past bottom of last header view
1621 final int numHeaders = getHeaderViewsCount();
1622 final int numFooters = getFooterViewsCount();
1623 final int firstPos = getFirstVisiblePosition();
1624 final int lastPos = getLastVisiblePosition();
1627 // "nHead="+numHeaders+" nFoot="+numFooters+" first="+firstPos+" last="+lastPos);
1628 int topLimit = getPaddingTop();
1629 if (firstPos < numHeaders) {
1630 topLimit = getChildAt(numHeaders - firstPos - 1).getBottom();
1632 if ((mDragFlags & DRAG_NEG_Y) == 0) {
1633 if (firstPos <= mSrcPos) {
1634 topLimit = Math.max(getChildAt(mSrcPos - firstPos).getTop(), topLimit);
1637 // bottom limit is top of first footer View or
1638 // bottom of last item in list
1639 int bottomLimit = getHeight() - getPaddingBottom();
1640 if (lastPos >= getCount() - numFooters - 1) {
1641 bottomLimit = getChildAt(getCount() - numFooters - 1 - firstPos).getBottom();
1643 if ((mDragFlags & DRAG_POS_Y) == 0) {
1644 if (lastPos >= mSrcPos) {
1645 bottomLimit = Math.min(getChildAt(mSrcPos - firstPos).getBottom(), bottomLimit);
1649 // Log.d("mobeta", "dragView top=" + (y - mDragDeltaY));
1650 // Log.d("mobeta", "limit=" + limit);
1651 // Log.d("mobeta", "mDragDeltaY=" + mDragDeltaY);
1653 if (floatY < topLimit) {
1654 mFloatViewTop = topLimit;
1655 } else if (floatY + mFloatViewHeight > bottomLimit) {
1656 mFloatViewTop = bottomLimit - mFloatViewHeight;
1658 mFloatViewTop = floatY;
1661 // get y-midpoint of floating view (constrained to ListView bounds)
1662 mFloatViewMid = mFloatViewTop + mFloatViewHeightHalf;
1665 private void dragView(final int x, final int y) {
1666 // Log.d("mobeta", "float view pure x=" + x + " y=" + y);
1668 // proposed position
1669 mFloatLoc.x = x - mDragDeltaX;
1670 mFloatLoc.y = y - mDragDeltaY;
1672 final Point touch = new Point(x, y);
1674 // let manager adjust proposed position first
1675 if (mFloatViewManager != null) {
1676 mFloatViewManager.onDragFloatView(mFloatView, mFloatLoc, touch);
1679 // then we override if manager gives an unsatisfactory
1680 // position (e.g. over a header/footer view). Also,
1681 // dragFlags override manager adjustments.
1682 updateFloatView(mFloatLoc.x, mFloatLoc.y);
1685 private void removeFloatView() {
1686 if (mFloatView != null) {
1687 mFloatView.setVisibility(GONE);
1688 if (mFloatViewManager != null) {
1689 mFloatViewManager.onDestroyFloatView(mFloatView);
1696 * Interface for customization of the floating View appearance and dragging
1697 * behavior. Implement your own and pass it to {@link #setFloatViewManager}.
1698 * If your own is not passed, the default {@link SimpleFloatViewManager}
1699 * implementation is used.
1701 public interface FloatViewManager {
1703 * Return the floating View for item at <code>position</code>.
1704 * DragSortListView will measure and layout this View for you, so feel
1705 * free to just inflate it. You can help DSLV by setting some
1706 * {@link ViewGroup.LayoutParams} on this View; otherwise it will set
1707 * some for you (with a width of FILL_PARENT and a height of
1710 * @param position Position of item to drag (NOTE: <code>position</code>
1711 * excludes header Views; thus, if you want to call
1712 * {@link ListView#getChildAt(int)}, you will need to add
1713 * {@link ListView#getHeaderViewsCount()} to the index).
1714 * @return The View you wish to display as the floating View.
1716 public View onCreateFloatView(int position);
1719 * Called whenever the floating View is dragged. Float View properties
1720 * can be changed here. Also, the upcoming location of the float View
1721 * can be altered by setting <code>location.x</code> and
1722 * <code>location.y</code>.
1724 * @param floatView The floating View.
1725 * @param location The location (top-left; relative to DSLV top-left) at
1726 * which the float View would like to appear, given the
1727 * current touch location and the offset provided in
1728 * {@link DragSortListView#startDrag}.
1729 * @param touch The current touch location (relative to DSLV top-left).
1731 public void onDragFloatView(View floatView, Point location, Point touch);
1734 * Called when the float View is dropped; lets you perform any necessary
1735 * cleanup. The internal DSLV floating View reference is set to null
1736 * immediately after this is called.
1738 * @param floatView The floating View passed to
1739 * {@link #onCreateFloatView(int)}.
1741 public void onDestroyFloatView(View floatView);
1744 public void setFloatViewManager(final FloatViewManager manager) {
1745 mFloatViewManager = manager;
1748 public void setDragListener(final DragListener l) {
1753 * Allows for easy toggling between a DragSortListView and a regular old
1754 * ListView. If enabled, items are draggable, where the drag init mode
1755 * determines how items are lifted (see {@link setDragInitMode(int)}). If
1756 * disabled, items cannot be dragged.
1758 * @param enabled Set <code>true</code> to enable list item dragging
1760 public void setDragEnabled(final boolean enabled) {
1761 mDragEnabled = enabled;
1764 public boolean isDragEnabled() {
1765 return mDragEnabled;
1769 * This better reorder your ListAdapter! DragSortListView does not do this
1770 * for you; doesn't make sense to. Make sure
1771 * {@link BaseAdapter#notifyDataSetChanged()} or something like it is called
1772 * in your implementation.
1776 public void setDropListener(final DropListener l) {
1781 * Probably a no-brainer, but make sure that your remove listener calls
1782 * {@link BaseAdapter#notifyDataSetChanged()} or something like it. When an
1783 * item removal occurs, DragSortListView relies on a redraw of all the items
1784 * to recover invisible views and such. Strictly speaking, if you remove
1785 * something, your dataset has changed...
1789 public void setRemoveListener(final RemoveListener l) {
1790 if (mController != null && l == null) {
1791 mController.setRemoveEnabled(false);
1793 mRemoveListener = l;
1796 public interface DragListener {
1797 public void drag(int from, int to);
1801 * Your implementation of this has to reorder your ListAdapter! Make sure to
1802 * call {@link BaseAdapter#notifyDataSetChanged()} or something like it in
1803 * your implementation.
1807 public interface DropListener {
1808 public void drop(int from, int to);
1812 * Make sure to call {@link BaseAdapter#notifyDataSetChanged()} or something
1813 * like it in your implementation.
1817 public interface RemoveListener {
1818 public void remove(int which);
1821 public interface DragSortListener extends DropListener, DragListener, RemoveListener {
1824 public void setDragSortListener(final DragSortListener l) {
1827 setRemoveListener(l);
1831 * Completely custom scroll speed profile. Default increases linearly with
1832 * position and is constant in time. Create your own by implementing
1833 * {@link DragSortListView.DragScrollProfile}.
1837 public void setDragScrollProfile(final DragScrollProfile ssp) {
1839 mScrollProfile = ssp;
1844 * Interface for controlling scroll speed as a function of touch position
1846 * {@link DragSortListView#setDragScrollProfile(DragScrollProfile)} to set
1851 public interface DragScrollProfile {
1853 * Return a scroll speed in pixels/millisecond. Always return a positive
1856 * @param w Normalized position in scroll region (i.e. w \in [0,1]).
1857 * Small w typically means slow scrolling.
1858 * @param t Time (in milliseconds) since start of scroll (handy if you
1859 * want scroll acceleration).
1860 * @return Scroll speed at position w and time t in pixels/ms.
1862 float getSpeed(float w, long t);
1865 private class DragScroller implements Runnable, AbsListView.OnScrollListener {
1867 private boolean mAbort;
1869 private long mPrevTime;
1875 private long tStart;
1877 private int scrollDir;
1879 public final static int STOP = -1;
1881 public final static int UP = 0;
1883 public final static int DOWN = 1;
1885 private float mScrollSpeed; // pixels per ms
1887 private boolean mScrolling = false;
1889 private int mMaxScrollSpeed;
1891 public boolean isScrolling() {
1895 public int getScrollDir() {
1896 return mScrolling ? scrollDir : STOP;
1899 public DragScroller() {
1902 public void startScrolling(final int dir) {
1904 // Debug.startMethodTracing("dslv-scroll");
1907 tStart = SystemClock.uptimeMillis();
1914 public void stopScrolling(final boolean now) {
1916 removeCallbacks(this);
1924 public void setListHeight(final int height) {
1925 // cap the max scroll speed per frame to be 1/5 of the list height
1926 mMaxScrollSpeed = height / 5;
1939 final int first = getFirstVisiblePosition();
1940 final int last = getLastVisiblePosition();
1941 final int count = getCount();
1942 final int padTop = getPaddingTop();
1943 final int listHeight = getHeight() - padTop - getPaddingBottom();
1945 final int minY = Math.min(mY, mFloatViewMid + mFloatViewHeightHalf);
1946 final int maxY = Math.max(mY, mFloatViewMid - mFloatViewHeightHalf);
1948 if (scrollDir == UP) {
1949 final View v = getChildAt(0);
1954 if (first == 0 && v.getTop() == padTop) {
1959 mScrollSpeed = mScrollProfile.getSpeed((mUpScrollStartYF - maxY)
1960 / mDragUpScrollHeight, mPrevTime);
1962 final View v = getChildAt(last - first);
1967 if (last == count - 1 && v.getBottom() <= listHeight + padTop) {
1972 mScrollSpeed = -mScrollProfile.getSpeed((minY - mDownScrollStartYF)
1973 / mDragDownScrollHeight, mPrevTime);
1976 dt = SystemClock.uptimeMillis() - mPrevTime;
1977 // dy is change in View position of a list item; i.e. positive dy
1978 // means user is scrolling up (list item moves down the screen,
1980 // y=0 is at top of View).
1981 dy = Math.round(mScrollSpeed * dt);
1984 // cap the scroll speed
1985 mScrollY = Math.max(Math.min(mScrollY, mMaxScrollSpeed), -mMaxScrollSpeed);
1998 public void onScroll(final AbsListView view, final int firstVisibleItem,
1999 final int visibleItemCount, final int totalItemCount) {
2000 if (mScrolling && visibleItemCount != 0) {
2009 public void onScrollStateChanged(final AbsListView view, final int scrollState) {
2014 private class DragSortTracker {
2015 StringBuilder mBuilder = new StringBuilder();
2019 private int mNumInBuffer = 0;
2021 private int mNumFlushes = 0;
2023 private boolean mTracking = false;
2025 public void startTracking() {
2026 mBuilder.append("<DSLVStates>\n");
2031 public void appendState() {
2036 mBuilder.append("<DSLVState>\n");
2037 final int children = getChildCount();
2038 final int first = getFirstVisiblePosition();
2039 final ItemHeights itemHeights = new ItemHeights();
2040 mBuilder.append(" <Positions>");
2041 for (int i = 0; i < children; ++i) {
2042 mBuilder.append(first + i).append(",");
2044 mBuilder.append("</Positions>\n");
2046 mBuilder.append(" <Tops>");
2047 for (int i = 0; i < children; ++i) {
2048 mBuilder.append(getChildAt(i).getTop()).append(",");
2050 mBuilder.append("</Tops>\n");
2051 mBuilder.append(" <Bottoms>");
2052 for (int i = 0; i < children; ++i) {
2053 mBuilder.append(getChildAt(i).getBottom()).append(",");
2055 mBuilder.append("</Bottoms>\n");
2057 mBuilder.append(" <FirstExpPos>").append(mFirstExpPos).append("</FirstExpPos>\n");
2058 getItemHeights(mFirstExpPos, itemHeights);
2059 mBuilder.append(" <FirstExpBlankHeight>")
2060 .append(itemHeights.item - itemHeights.child)
2061 .append("</FirstExpBlankHeight>\n");
2062 mBuilder.append(" <SecondExpPos>").append(mSecondExpPos).append("</SecondExpPos>\n");
2063 getItemHeights(mSecondExpPos, itemHeights);
2064 mBuilder.append(" <SecondExpBlankHeight>")
2065 .append(itemHeights.item - itemHeights.child)
2066 .append("</SecondExpBlankHeight>\n");
2067 mBuilder.append(" <SrcPos>").append(mSrcPos).append("</SrcPos>\n");
2068 mBuilder.append(" <SrcHeight>").append(mFloatViewHeight + getDividerHeight())
2069 .append("</SrcHeight>\n");
2070 mBuilder.append(" <ViewHeight>").append(getHeight()).append("</ViewHeight>\n");
2071 mBuilder.append(" <LastY>").append(mLastY).append("</LastY>\n");
2072 mBuilder.append(" <FloatY>").append(mFloatViewMid).append("</FloatY>\n");
2073 mBuilder.append(" <ShuffleEdges>");
2074 for (int i = 0; i < children; ++i) {
2075 mBuilder.append(getShuffleEdge(first + i, getChildAt(i).getTop())).append(",");
2077 mBuilder.append("</ShuffleEdges>\n");
2079 mBuilder.append("</DSLVState>\n");
2082 if (mNumInBuffer > 1000) {
2088 public void flush() {
2093 // save to file on sdcard
2095 boolean append = true;
2096 if (mNumFlushes == 0) {
2099 final FileWriter writer = new FileWriter(mFile, append);
2101 writer.write(mBuilder.toString());
2102 mBuilder.delete(0, mBuilder.length());
2108 } catch (final IOException e) {
2113 public void stopTracking() {
2115 mBuilder.append("</DSLVStates>\n");