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 int slideEdgeTop = edgeTop + slideRgnHeight;
893 final int slideEdgeBottom = edgeBottom - slideRgnHeight;
896 if (mFloatViewMid < slideEdgeTop) {
897 mFirstExpPos = itemPos - 1;
898 mSecondExpPos = itemPos;
899 mSlideFrac = 0.5f * (slideEdgeTop - mFloatViewMid) / (float) slideRgnHeight;
901 // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
902 } else if (mFloatViewMid < slideEdgeBottom) {
903 mFirstExpPos = itemPos;
904 mSecondExpPos = itemPos;
906 mFirstExpPos = itemPos;
907 mSecondExpPos = itemPos + 1;
908 mSlideFrac = 0.5f * (1.0f + (edgeBottom - mFloatViewMid) / (float) slideRgnHeight);
910 // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
914 mFirstExpPos = itemPos;
915 mSecondExpPos = itemPos;
918 // correct for headers and footers
919 if (mFirstExpPos < numHeaders) {
920 itemPos = numHeaders;
921 mFirstExpPos = itemPos;
922 mSecondExpPos = itemPos;
923 } else if (mSecondExpPos >= getCount() - numFooters) {
924 itemPos = getCount() - numFooters - 1;
925 mFirstExpPos = itemPos;
926 mSecondExpPos = itemPos;
929 if (mFirstExpPos != oldFirstExpPos || mSecondExpPos != oldSecondExpPos
930 || mSlideFrac != oldSlideFrac) {
934 if (itemPos != mFloatPos) {
935 if (mDragListener != null) {
936 mDragListener.drag(mFloatPos - numHeaders, itemPos - numHeaders);
950 protected void onDraw(final Canvas canvas) {
951 super.onDraw(canvas);
953 if (mTrackDragSort) {
954 mDragSortTracker.appendState();
959 * Stop a drag in progress. Pass <code>true</code> if you would like to
960 * remove the dragged item from the list.
962 * @param remove Remove the dragged item from the list. Calls a registered
963 * DropListener, if one exists.
964 * @return True if the stop was successful.
966 public boolean stopDrag(final boolean remove) {
967 if (mFloatView != null) {
968 mDragState = STOPPED;
971 dropFloatView(remove);
984 public boolean onTouchEvent(final MotionEvent ev) {
987 return super.onTouchEvent(ev);
990 boolean more = false;
992 final boolean lastCallWasIntercept = mLastCallWasIntercept;
993 mLastCallWasIntercept = false;
995 if (!lastCallWasIntercept) {
999 if (mFloatView != null) {
1000 onDragTouchEvent(ev);
1001 more = true; // give us more!
1003 // what if float view is null b/c we dropped in middle
1004 // of drag touch event?
1006 if (mDragState != STOPPED) {
1007 if (super.onTouchEvent(ev)) {
1012 final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1014 case MotionEvent.ACTION_CANCEL:
1015 case MotionEvent.ACTION_UP:
1016 doActionUpOrCancel();
1020 mCancelMethod = ON_TOUCH_EVENT;
1029 private void doActionUpOrCancel() {
1030 mCancelMethod = NO_CANCEL;
1031 mInTouchEvent = false;
1033 mCurrFloatAlpha = mFloatAlpha;
1036 private void saveTouchCoords(final MotionEvent ev) {
1037 final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1038 if (action != MotionEvent.ACTION_DOWN) {
1041 mX = (int)ev.getX();
1042 mY = (int)ev.getY();
1043 if (action == MotionEvent.ACTION_DOWN) {
1052 public boolean onInterceptTouchEvent(final MotionEvent ev) {
1053 if (!mDragEnabled) {
1054 return super.onInterceptTouchEvent(ev);
1057 saveTouchCoords(ev);
1058 mLastCallWasIntercept = true;
1060 boolean intercept = false;
1062 final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1064 if (action == MotionEvent.ACTION_DOWN) {
1065 mInTouchEvent = true;
1068 // the following deals with calls to super.onInterceptTouchEvent
1069 if (mFloatView != null) {
1070 // super's touch event canceled in startDrag
1073 if (super.onInterceptTouchEvent(ev)) {
1078 case MotionEvent.ACTION_CANCEL:
1079 case MotionEvent.ACTION_UP:
1080 doActionUpOrCancel();
1084 mCancelMethod = ON_TOUCH_EVENT;
1086 mCancelMethod = ON_INTERCEPT_TOUCH_EVENT;
1091 // check for startDragging
1093 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
1094 mInTouchEvent = false;
1101 * Set the width of each drag scroll region by specifying a fraction of the
1104 * @param heightFraction Fraction of ListView height. Capped at 0.5f.
1106 public void setDragScrollStart(final float heightFraction) {
1107 setDragScrollStarts(heightFraction, heightFraction);
1111 * Set the width of each drag scroll region by specifying a fraction of the
1114 * @param upperFrac Fraction of ListView height for up-scroll bound. Capped
1116 * @param lowerFrac Fraction of ListView height for down-scroll bound.
1119 public void setDragScrollStarts(final float upperFrac, final float lowerFrac) {
1120 if (lowerFrac > 0.5f) {
1121 mDragDownScrollStartFrac = 0.5f;
1123 mDragDownScrollStartFrac = lowerFrac;
1126 if (upperFrac > 0.5f) {
1127 mDragUpScrollStartFrac = 0.5f;
1129 mDragUpScrollStartFrac = upperFrac;
1132 if (getHeight() != 0) {
1133 updateScrollStarts();
1137 private void continueDrag(final int x, final int y) {
1139 // Log.d("mobeta", "move");
1142 // if (mTrackDragSort) {
1143 // mDragSortTracker.appendState();
1148 final int minY = Math.min(y, mFloatViewMid + mFloatViewHeightHalf);
1149 final int maxY = Math.max(y, mFloatViewMid - mFloatViewHeightHalf);
1151 // get the current scroll direction
1152 final int currentScrollDir = mDragScroller.getScrollDir();
1154 if (minY > mLastY && minY > mDownScrollStartY && currentScrollDir != DragScroller.DOWN) {
1155 // dragged down, it is below the down scroll start and it is not
1158 if (currentScrollDir != DragScroller.STOP) {
1159 // moved directly from up scroll to down scroll
1160 mDragScroller.stopScrolling(true);
1163 // start scrolling down
1164 mDragScroller.startScrolling(DragScroller.DOWN);
1165 } else if (maxY < mLastY && maxY < mUpScrollStartY && currentScrollDir != DragScroller.UP) {
1166 // dragged up, it is above the up scroll start and it is not
1169 if (currentScrollDir != DragScroller.STOP) {
1170 // moved directly from down scroll to up scroll
1171 mDragScroller.stopScrolling(true);
1174 // start scrolling up
1175 mDragScroller.startScrolling(DragScroller.UP);
1176 } else if (maxY >= mUpScrollStartY && minY <= mDownScrollStartY
1177 && mDragScroller.isScrolling()) {
1178 // not in the upper nor in the lower drag-scroll regions but it is
1181 mDragScroller.stopScrolling(true);
1185 private void updateScrollStarts() {
1186 final int padTop = getPaddingTop();
1187 final int listHeight = getHeight() - padTop - getPaddingBottom();
1189 mUpScrollStartYF = padTop + mDragUpScrollStartFrac * (float) listHeight;
1190 mDownScrollStartYF = padTop + (1.0f - mDragDownScrollStartFrac) * (float) listHeight;
1192 mUpScrollStartY = (int)mUpScrollStartYF;
1193 mDownScrollStartY = (int)mDownScrollStartYF;
1195 mDragUpScrollHeight = mUpScrollStartYF - padTop;
1196 mDragDownScrollHeight = padTop + listHeight - mDownScrollStartYF;
1203 protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
1204 super.onSizeChanged(w, h, oldw, oldh);
1205 updateScrollStarts();
1208 private void dropFloatView(final boolean removeSrcItem) {
1210 mDragScroller.stopScrolling(true);
1212 if (removeSrcItem) {
1213 if (mRemoveListener != null) {
1214 mRemoveListener.remove(mSrcPos - getHeaderViewsCount());
1217 if (mDropListener != null && mFloatPos >= 0 && mFloatPos < getCount()) {
1218 final int numHeaders = getHeaderViewsCount();
1219 mDropListener.drop(mSrcPos - numHeaders, mFloatPos - numHeaders);
1222 // adjustAllItems();
1224 final int firstPos = getFirstVisiblePosition();
1225 if (mSrcPos < firstPos) {
1226 // collapsed src item is off screen;
1227 // adjust the scroll after item heights have been fixed
1228 final View v = getChildAt(0);
1233 // Log.d("mobeta", "top="+top+" fvh="+mFloatViewHeight);
1234 setSelectionFromTop(firstPos - 1, top - getPaddingTop());
1245 if (mTrackDragSort) {
1246 mDragSortTracker.stopTracking();
1250 private void adjustAllItems() {
1251 final int first = getFirstVisiblePosition();
1252 final int last = getLastVisiblePosition();
1254 final int begin = Math.max(0, getHeaderViewsCount() - first);
1255 final int end = Math.min(last - first, getCount() - 1 - getFooterViewsCount() - first);
1257 for (int i = begin; i <= end; ++i) {
1258 final View v = getChildAt(i);
1260 adjustItem(first + i, v, false);
1265 private void adjustItem(final int position, final View v, final boolean needsMeasure) {
1267 final ViewGroup.LayoutParams lp = v.getLayoutParams();
1268 final int oldHeight = lp.height;
1269 int height = oldHeight;
1273 final boolean isSliding = mAnimate && mFirstExpPos != mSecondExpPos;
1274 final int maxNonSrcBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
1275 final int slideHeight = (int)(mSlideFrac * maxNonSrcBlankHeight);
1277 if (position == mSrcPos) {
1278 if (mSrcPos == mFirstExpPos) {
1280 height = slideHeight + mItemHeightCollapsed;
1282 height = mFloatViewHeight;
1284 } else if (mSrcPos == mSecondExpPos) {
1285 // if gets here, we know an item is sliding
1286 height = mFloatViewHeight - slideHeight;
1288 height = mItemHeightCollapsed;
1290 } else if (position == mFirstExpPos || position == mSecondExpPos) {
1291 // position is not src
1293 final ItemHeights itemHeights = new ItemHeights();
1295 measureItemAndGetHeights(position, v, itemHeights);
1297 getItemHeights(position, v, itemHeights);
1300 if (position == mFirstExpPos) {
1302 height = itemHeights.child + slideHeight;
1304 height = itemHeights.child + maxNonSrcBlankHeight;
1306 } else { // position=mSecondExpPos
1307 // we know an item is sliding (b/c 2ndPos != 1stPos)
1308 height = itemHeights.child + maxNonSrcBlankHeight - slideHeight;
1311 height = ViewGroup.LayoutParams.WRAP_CONTENT;
1314 if (height != oldHeight) {
1317 v.setLayoutParams(lp);
1320 // Adjust item gravity
1322 if (position == mFirstExpPos || position == mSecondExpPos) {
1323 if (position < mSrcPos) {
1324 ((RelativeLayout)v).setGravity(Gravity.BOTTOM);
1325 } else if (position > mSrcPos) {
1326 ((RelativeLayout)v).setGravity(Gravity.TOP);
1330 // Finally adjust item visibility
1332 final int oldVis = v.getVisibility();
1333 int vis = View.VISIBLE;
1335 if (position == mSrcPos && mFloatView != null) {
1336 vis = View.INVISIBLE;
1339 if (vis != oldVis) {
1340 v.setVisibility(vis);
1348 public void requestLayout() {
1349 if (!mBlockLayoutRequests) {
1350 super.requestLayout();
1354 private void doDragScroll(final int oldFirstExpPos, final int oldSecondExpPos) {
1355 if (mScrollY == 0) {
1359 final int padTop = getPaddingTop();
1360 final int listHeight = getHeight() - padTop - getPaddingBottom();
1361 final int first = getFirstVisiblePosition();
1362 final int last = getLastVisiblePosition();
1366 if (mScrollY >= 0) {
1367 mScrollY = Math.min(listHeight, mScrollY);
1370 mScrollY = Math.max(-listHeight, mScrollY);
1374 final View moveItem = getChildAt(movePos - first);
1375 int top = moveItem.getTop() + mScrollY;
1377 if (movePos == 0 && top > padTop) {
1381 final ItemHeights itemHeightsBefore = new ItemHeights();
1382 getItemHeights(movePos, moveItem, itemHeightsBefore);
1383 final int moveHeightBefore = itemHeightsBefore.item;
1384 final int moveBlankBefore = moveHeightBefore - itemHeightsBefore.child;
1386 final ItemHeights itemHeightsAfter = new ItemHeights();
1387 measureItemAndGetHeights(movePos, moveItem, itemHeightsAfter);
1388 final int moveHeightAfter = itemHeightsAfter.item;
1389 final int moveBlankAfter = moveHeightAfter - itemHeightsAfter.child;
1391 if (movePos <= oldFirstExpPos) {
1392 if (movePos > mFirstExpPos) {
1393 top += mFloatViewHeight - moveBlankAfter;
1395 } else if (movePos == oldSecondExpPos) {
1396 if (movePos <= mFirstExpPos) {
1397 top += moveBlankBefore - mFloatViewHeight;
1398 } else if (movePos == mSecondExpPos) {
1399 top += moveHeightBefore - moveHeightAfter;
1401 top += moveBlankBefore;
1404 if (movePos <= mFirstExpPos) {
1405 top -= mFloatViewHeight;
1406 } else if (movePos == mSecondExpPos) {
1407 top -= moveBlankAfter;
1411 setSelectionFromTop(movePos, top - padTop);
1416 private void measureFloatView() {
1417 if (mFloatView != null) {
1418 ViewGroup.LayoutParams lp = mFloatView.getLayoutParams();
1420 lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1421 ViewGroup.LayoutParams.WRAP_CONTENT);
1423 final int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft()
1424 + getListPaddingRight(), lp.width);
1426 if (lp.height > 0) {
1427 hspec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
1429 hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1431 mFloatView.measure(wspec, hspec);
1432 mFloatViewHeight = mFloatView.getMeasuredHeight();
1433 mFloatViewHeightHalf = mFloatViewHeight / 2;
1441 protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
1442 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1443 if (mFloatView != null) {
1444 if (mFloatView.isLayoutRequested()) {
1448 mWidthMeasureSpec = widthMeasureSpec;
1449 mDragScroller.setListHeight(getHeight());
1456 protected void layoutChildren() {
1458 if (mFloatView != null) {
1459 mFloatView.layout(0, 0, mFloatView.getMeasuredWidth(), mFloatView.getMeasuredHeight());
1461 // Log.d("mobeta", "layout children");
1462 final int oldFirstExpPos = mFirstExpPos;
1463 final int oldSecondExpPos = mSecondExpPos;
1465 mBlockLayoutRequests = true;
1467 if (getChildCount() > 0 && updatePositions()) {
1471 if (mScrollY != 0) {
1472 doDragScroll(oldFirstExpPos, oldSecondExpPos);
1475 mBlockLayoutRequests = false;
1478 super.layoutChildren();
1481 protected boolean onDragTouchEvent(final MotionEvent ev) {
1482 switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1483 case MotionEvent.ACTION_CANCEL:
1484 case MotionEvent.ACTION_UP:
1486 doActionUpOrCancel();
1488 case MotionEvent.ACTION_MOVE:
1489 continueDrag((int)ev.getX(), (int)ev.getY());
1497 * Start a drag of item at <code>position</code> using the registered
1498 * FloatViewManager. Calls through to
1499 * {@link #startDrag(int,View,int,int,int)} after obtaining the floating
1500 * View from the FloatViewManager.
1502 * @param position Item to drag.
1503 * @param dragFlags Flags that restrict some movements of the floating View.
1504 * For example, set <code>dragFlags |=
1505 * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating View in all
1506 * directions except off the screen to the left.
1507 * @param deltaX Offset in x of the touch coordinate from the left edge of
1508 * the floating View (i.e. touch-x minus float View left).
1509 * @param deltaY Offset in y of the touch coordinate from the top edge of
1510 * the floating View (i.e. touch-y minus float View top).
1511 * @return True if the drag was started, false otherwise. This
1512 * <code>startDrag</code> will fail if we are not currently in a
1513 * touch event, there is no registered FloatViewManager, or the
1514 * FloatViewManager returns a null View.
1516 public boolean startDrag(final int position, final int dragFlags, final int deltaX,
1518 if (!mInTouchEvent || mFloatViewManager == null) {
1522 final View v = mFloatViewManager.onCreateFloatView(position);
1527 return startDrag(position, v, dragFlags, deltaX, deltaY);
1533 * Start a drag of item at <code>position</code> without using a
1536 * @param position Item to drag.
1537 * @param floatView Floating View.
1538 * @param dragFlags Flags that restrict some movements of the floating View.
1539 * For example, set <code>dragFlags |=
1540 * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating View in all
1541 * directions except off the screen to the left.
1542 * @param deltaX Offset in x of the touch coordinate from the left edge of
1543 * the floating View (i.e. touch-x minus float View left).
1544 * @param deltaY Offset in y of the touch coordinate from the top edge of
1545 * the floating View (i.e. touch-y minus float View top).
1546 * @return True if the drag was started, false otherwise. This
1547 * <code>startDrag</code> will fail if we are not currently in a
1548 * touch event, <code>floatView</code> is null, or there is a drag
1551 public boolean startDrag(final int position, final View floatView, final int dragFlags,
1552 final int deltaX, final int deltaY) {
1553 if (!mInTouchEvent || mFloatView != null || floatView == null) {
1557 if (getParent() != null) {
1558 getParent().requestDisallowInterceptTouchEvent(true);
1561 final int pos = position + getHeaderViewsCount();
1563 mSecondExpPos = pos;
1567 // mDragState = dragType;
1568 mDragState = DRAGGING;
1570 mDragFlags |= dragFlags;
1572 mFloatView = floatView;
1573 measureFloatView(); // sets mFloatViewHeight
1575 mDragDeltaX = deltaX;
1576 mDragDeltaY = deltaY;
1577 updateFloatView(mX - mDragDeltaX, mY - mDragDeltaY);
1579 // set src item invisible
1580 final View srcItem = getChildAt(mSrcPos - getFirstVisiblePosition());
1581 if (srcItem != null) {
1582 srcItem.setVisibility(View.INVISIBLE);
1585 if (mTrackDragSort) {
1586 mDragSortTracker.startTracking();
1589 // once float view is created, events are no longer passed
1591 switch (mCancelMethod) {
1592 case ON_TOUCH_EVENT:
1593 super.onTouchEvent(mCancelEvent);
1595 case ON_INTERCEPT_TOUCH_EVENT:
1596 super.onInterceptTouchEvent(mCancelEvent);
1606 * Sets float View location based on suggested values and constraints set in
1609 private void updateFloatView(final int floatX, final int floatY) {
1611 // restrict x motion
1612 final int padLeft = getPaddingLeft();
1613 if ((mDragFlags & DRAG_POS_X) == 0 && floatX > padLeft) {
1614 mFloatViewLeft = padLeft;
1615 } else if ((mDragFlags & DRAG_NEG_X) == 0 && floatX < padLeft) {
1616 mFloatViewLeft = padLeft;
1618 mFloatViewLeft = floatX;
1621 // keep floating view from going past bottom of last header view
1622 final int numHeaders = getHeaderViewsCount();
1623 final int numFooters = getFooterViewsCount();
1624 final int firstPos = getFirstVisiblePosition();
1625 final int lastPos = getLastVisiblePosition();
1628 // "nHead="+numHeaders+" nFoot="+numFooters+" first="+firstPos+" last="+lastPos);
1629 int topLimit = getPaddingTop();
1630 if (firstPos < numHeaders) {
1631 topLimit = getChildAt(numHeaders - firstPos - 1).getBottom();
1633 if ((mDragFlags & DRAG_NEG_Y) == 0) {
1634 if (firstPos <= mSrcPos) {
1635 topLimit = Math.max(getChildAt(mSrcPos - firstPos).getTop(), topLimit);
1638 // bottom limit is top of first footer View or
1639 // bottom of last item in list
1640 int bottomLimit = getHeight() - getPaddingBottom();
1641 if (lastPos >= getCount() - numFooters - 1) {
1642 bottomLimit = getChildAt(getCount() - numFooters - 1 - firstPos).getBottom();
1644 if ((mDragFlags & DRAG_POS_Y) == 0) {
1645 if (lastPos >= mSrcPos) {
1646 bottomLimit = Math.min(getChildAt(mSrcPos - firstPos).getBottom(), bottomLimit);
1650 // Log.d("mobeta", "dragView top=" + (y - mDragDeltaY));
1651 // Log.d("mobeta", "limit=" + limit);
1652 // Log.d("mobeta", "mDragDeltaY=" + mDragDeltaY);
1654 if (floatY < topLimit) {
1655 mFloatViewTop = topLimit;
1656 } else if (floatY + mFloatViewHeight > bottomLimit) {
1657 mFloatViewTop = bottomLimit - mFloatViewHeight;
1659 mFloatViewTop = floatY;
1662 // get y-midpoint of floating view (constrained to ListView bounds)
1663 mFloatViewMid = mFloatViewTop + mFloatViewHeightHalf;
1666 private void dragView(final int x, final int y) {
1667 // Log.d("mobeta", "float view pure x=" + x + " y=" + y);
1669 // proposed position
1670 mFloatLoc.x = x - mDragDeltaX;
1671 mFloatLoc.y = y - mDragDeltaY;
1673 final Point touch = new Point(x, y);
1675 // let manager adjust proposed position first
1676 if (mFloatViewManager != null) {
1677 mFloatViewManager.onDragFloatView(mFloatView, mFloatLoc, touch);
1680 // then we override if manager gives an unsatisfactory
1681 // position (e.g. over a header/footer view). Also,
1682 // dragFlags override manager adjustments.
1683 updateFloatView(mFloatLoc.x, mFloatLoc.y);
1686 private void removeFloatView() {
1687 if (mFloatView != null) {
1688 mFloatView.setVisibility(GONE);
1689 if (mFloatViewManager != null) {
1690 mFloatViewManager.onDestroyFloatView(mFloatView);
1697 * Interface for customization of the floating View appearance and dragging
1698 * behavior. Implement your own and pass it to {@link #setFloatViewManager}.
1699 * If your own is not passed, the default {@link SimpleFloatViewManager}
1700 * implementation is used.
1702 public interface FloatViewManager {
1704 * Return the floating View for item at <code>position</code>.
1705 * DragSortListView will measure and layout this View for you, so feel
1706 * free to just inflate it. You can help DSLV by setting some
1707 * {@link ViewGroup.LayoutParams} on this View; otherwise it will set
1708 * some for you (with a width of FILL_PARENT and a height of
1711 * @param position Position of item to drag (NOTE: <code>position</code>
1712 * excludes header Views; thus, if you want to call
1713 * {@link ListView#getChildAt(int)}, you will need to add
1714 * {@link ListView#getHeaderViewsCount()} to the index).
1715 * @return The View you wish to display as the floating View.
1717 public View onCreateFloatView(int position);
1720 * Called whenever the floating View is dragged. Float View properties
1721 * can be changed here. Also, the upcoming location of the float View
1722 * can be altered by setting <code>location.x</code> and
1723 * <code>location.y</code>.
1725 * @param floatView The floating View.
1726 * @param location The location (top-left; relative to DSLV top-left) at
1727 * which the float View would like to appear, given the
1728 * current touch location and the offset provided in
1729 * {@link DragSortListView#startDrag}.
1730 * @param touch The current touch location (relative to DSLV top-left).
1732 public void onDragFloatView(View floatView, Point location, Point touch);
1735 * Called when the float View is dropped; lets you perform any necessary
1736 * cleanup. The internal DSLV floating View reference is set to null
1737 * immediately after this is called.
1739 * @param floatView The floating View passed to
1740 * {@link #onCreateFloatView(int)}.
1742 public void onDestroyFloatView(View floatView);
1745 public void setFloatViewManager(final FloatViewManager manager) {
1746 mFloatViewManager = manager;
1749 public void setDragListener(final DragListener l) {
1754 * Allows for easy toggling between a DragSortListView and a regular old
1755 * ListView. If enabled, items are draggable, where the drag init mode
1756 * determines how items are lifted (see {@link setDragInitMode(int)}). If
1757 * disabled, items cannot be dragged.
1759 * @param enabled Set <code>true</code> to enable list item dragging
1761 public void setDragEnabled(final boolean enabled) {
1762 mDragEnabled = enabled;
1765 public boolean isDragEnabled() {
1766 return mDragEnabled;
1770 * This better reorder your ListAdapter! DragSortListView does not do this
1771 * for you; doesn't make sense to. Make sure
1772 * {@link BaseAdapter#notifyDataSetChanged()} or something like it is called
1773 * in your implementation.
1777 public void setDropListener(final DropListener l) {
1782 * Probably a no-brainer, but make sure that your remove listener calls
1783 * {@link BaseAdapter#notifyDataSetChanged()} or something like it. When an
1784 * item removal occurs, DragSortListView relies on a redraw of all the items
1785 * to recover invisible views and such. Strictly speaking, if you remove
1786 * something, your dataset has changed...
1790 public void setRemoveListener(final RemoveListener l) {
1791 if (mController != null && l == null) {
1792 mController.setRemoveEnabled(false);
1794 mRemoveListener = l;
1797 public interface DragListener {
1798 public void drag(int from, int to);
1802 * Your implementation of this has to reorder your ListAdapter! Make sure to
1803 * call {@link BaseAdapter#notifyDataSetChanged()} or something like it in
1804 * your implementation.
1808 public interface DropListener {
1809 public void drop(int from, int to);
1813 * Make sure to call {@link BaseAdapter#notifyDataSetChanged()} or something
1814 * like it in your implementation.
1818 public interface RemoveListener {
1819 public void remove(int which);
1822 public interface DragSortListener extends DropListener, DragListener, RemoveListener {
1825 public void setDragSortListener(final DragSortListener l) {
1828 setRemoveListener(l);
1832 * Completely custom scroll speed profile. Default increases linearly with
1833 * position and is constant in time. Create your own by implementing
1834 * {@link DragSortListView.DragScrollProfile}.
1838 public void setDragScrollProfile(final DragScrollProfile ssp) {
1840 mScrollProfile = ssp;
1845 * Interface for controlling scroll speed as a function of touch position
1847 * {@link DragSortListView#setDragScrollProfile(DragScrollProfile)} to set
1852 public interface DragScrollProfile {
1854 * Return a scroll speed in pixels/millisecond. Always return a positive
1857 * @param w Normalized position in scroll region (i.e. w \in [0,1]).
1858 * Small w typically means slow scrolling.
1859 * @param t Time (in milliseconds) since start of scroll (handy if you
1860 * want scroll acceleration).
1861 * @return Scroll speed at position w and time t in pixels/ms.
1863 float getSpeed(float w, long t);
1866 private class DragScroller implements Runnable, AbsListView.OnScrollListener {
1868 private boolean mAbort;
1870 private long mPrevTime;
1876 private long tStart;
1878 private int scrollDir;
1880 public final static int STOP = -1;
1882 public final static int UP = 0;
1884 public final static int DOWN = 1;
1886 private float mScrollSpeed; // pixels per ms
1888 private boolean mScrolling = false;
1890 private int mMaxScrollSpeed;
1892 public boolean isScrolling() {
1896 public int getScrollDir() {
1897 return mScrolling ? scrollDir : STOP;
1900 public DragScroller() {
1903 public void startScrolling(final int dir) {
1905 // Debug.startMethodTracing("dslv-scroll");
1908 tStart = SystemClock.uptimeMillis();
1915 public void stopScrolling(final boolean now) {
1917 removeCallbacks(this);
1925 public void setListHeight(final int height) {
1926 // cap the max scroll speed per frame to be 1/5 of the list height
1927 mMaxScrollSpeed = height / 5;
1940 final int first = getFirstVisiblePosition();
1941 final int last = getLastVisiblePosition();
1942 final int count = getCount();
1943 final int padTop = getPaddingTop();
1944 final int listHeight = getHeight() - padTop - getPaddingBottom();
1946 final int minY = Math.min(mY, mFloatViewMid + mFloatViewHeightHalf);
1947 final int maxY = Math.max(mY, mFloatViewMid - mFloatViewHeightHalf);
1949 if (scrollDir == UP) {
1950 final View v = getChildAt(0);
1955 if (first == 0 && v.getTop() == padTop) {
1960 mScrollSpeed = mScrollProfile.getSpeed((mUpScrollStartYF - maxY)
1961 / mDragUpScrollHeight, mPrevTime);
1963 final View v = getChildAt(last - first);
1968 if (last == count - 1 && v.getBottom() <= listHeight + padTop) {
1973 mScrollSpeed = -mScrollProfile.getSpeed((minY - mDownScrollStartYF)
1974 / mDragDownScrollHeight, mPrevTime);
1977 dt = SystemClock.uptimeMillis() - mPrevTime;
1978 // dy is change in View position of a list item; i.e. positive dy
1979 // means user is scrolling up (list item moves down the screen,
1981 // y=0 is at top of View).
1982 dy = Math.round(mScrollSpeed * dt);
1985 // cap the scroll speed
1986 mScrollY = Math.max(Math.min(mScrollY, mMaxScrollSpeed), -mMaxScrollSpeed);
1999 public void onScroll(final AbsListView view, final int firstVisibleItem,
2000 final int visibleItemCount, final int totalItemCount) {
2001 if (mScrolling && visibleItemCount != 0) {
2010 public void onScrollStateChanged(final AbsListView view, final int scrollState) {
2015 private class DragSortTracker {
2016 StringBuilder mBuilder = new StringBuilder();
2020 private int mNumInBuffer = 0;
2022 private int mNumFlushes = 0;
2024 private boolean mTracking = false;
2026 public void startTracking() {
2027 mBuilder.append("<DSLVStates>\n");
2032 public void appendState() {
2037 mBuilder.append("<DSLVState>\n");
2038 final int children = getChildCount();
2039 final int first = getFirstVisiblePosition();
2040 final ItemHeights itemHeights = new ItemHeights();
2041 mBuilder.append(" <Positions>");
2042 for (int i = 0; i < children; ++i) {
2043 mBuilder.append(first + i).append(",");
2045 mBuilder.append("</Positions>\n");
2047 mBuilder.append(" <Tops>");
2048 for (int i = 0; i < children; ++i) {
2049 mBuilder.append(getChildAt(i).getTop()).append(",");
2051 mBuilder.append("</Tops>\n");
2052 mBuilder.append(" <Bottoms>");
2053 for (int i = 0; i < children; ++i) {
2054 mBuilder.append(getChildAt(i).getBottom()).append(",");
2056 mBuilder.append("</Bottoms>\n");
2058 mBuilder.append(" <FirstExpPos>").append(mFirstExpPos).append("</FirstExpPos>\n");
2059 getItemHeights(mFirstExpPos, itemHeights);
2060 mBuilder.append(" <FirstExpBlankHeight>")
2061 .append(itemHeights.item - itemHeights.child)
2062 .append("</FirstExpBlankHeight>\n");
2063 mBuilder.append(" <SecondExpPos>").append(mSecondExpPos).append("</SecondExpPos>\n");
2064 getItemHeights(mSecondExpPos, itemHeights);
2065 mBuilder.append(" <SecondExpBlankHeight>")
2066 .append(itemHeights.item - itemHeights.child)
2067 .append("</SecondExpBlankHeight>\n");
2068 mBuilder.append(" <SrcPos>").append(mSrcPos).append("</SrcPos>\n");
2069 mBuilder.append(" <SrcHeight>").append(mFloatViewHeight + getDividerHeight())
2070 .append("</SrcHeight>\n");
2071 mBuilder.append(" <ViewHeight>").append(getHeight()).append("</ViewHeight>\n");
2072 mBuilder.append(" <LastY>").append(mLastY).append("</LastY>\n");
2073 mBuilder.append(" <FloatY>").append(mFloatViewMid).append("</FloatY>\n");
2074 mBuilder.append(" <ShuffleEdges>");
2075 for (int i = 0; i < children; ++i) {
2076 mBuilder.append(getShuffleEdge(first + i, getChildAt(i).getTop())).append(",");
2078 mBuilder.append("</ShuffleEdges>\n");
2080 mBuilder.append("</DSLVState>\n");
2083 if (mNumInBuffer > 1000) {
2089 public void flush() {
2094 // save to file on sdcard
2096 boolean append = true;
2097 if (mNumFlushes == 0) {
2100 final FileWriter writer = new FileWriter(mFile, append);
2102 writer.write(mBuilder.toString());
2103 mBuilder.delete(0, mBuilder.length());
2109 } catch (final IOException e) {
2114 public void stopTracking() {
2116 mBuilder.append("</DSLVStates>\n");