OSDN Git Service

Eleven: much material
[android-x86/packages-apps-Eleven.git] / src / com / cyanogenmod / eleven / dragdrop / DragSortListView.java
1 /*
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.
11  */
12
13 package com.cyanogenmod.eleven.dragdrop;
14
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;
32
33 import com.cyanogenmod.eleven.R;
34
35 import java.io.File;
36 import java.io.FileWriter;
37 import java.io.IOException;
38
39 /**
40  * ListView subclass that mediates drag and drop resorting of items.
41  * 
42  * @author heycosmo
43  */
44 public class DragSortListView extends ListView {
45
46     /**
47      * The View that floats above the ListView and represents the dragged item.
48      */
49     private View mFloatView;
50
51     /**
52      * A proposed float View location based on touch location and given deltaX
53      * and deltaY.
54      */
55     private final Point mFloatLoc = new Point();
56
57     /**
58      * The middle (in the y-direction) of the floating View.
59      */
60     private int mFloatViewMid;
61
62     /**
63      * Left edge of floating View.
64      */
65     private int mFloatViewLeft;
66
67     /**
68      * Top edge of floating View.
69      */
70     private int mFloatViewTop;
71
72     /**
73      * Watch the Adapter for data changes. Cancel a drag if coincident with a
74      * change.
75      */
76     private final DataSetObserver mObserver;
77
78     /**
79      * Transparency for the floating View (XML attribute).
80      */
81     private final float mFloatAlpha = 1.0f;
82
83     private float mCurrFloatAlpha = 1.0f;
84
85     /**
86      * While drag-sorting, the current position of the floating View. If
87      * dropped, the dragged item will land in this position.
88      */
89     private int mFloatPos;
90
91     /**
92      * The amount to scroll during the next layout pass. Used only for
93      * drag-scrolling, not standard ListView scrolling.
94      */
95     private int mScrollY = 0;
96
97     /**
98      * The first expanded ListView position that helps represent the drop slot
99      * tracking the floating View.
100      */
101     private int mFirstExpPos;
102
103     /**
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.
107      */
108     private int mSecondExpPos;
109
110     /**
111      * Flag set if slide shuffling is enabled.
112      */
113     private boolean mAnimate = false;
114
115     /**
116      * The user dragged from this position.
117      */
118     private int mSrcPos;
119
120     /**
121      * Offset (in x) within the dragged item at which the user picked it up (or
122      * first touched down with the digitalis).
123      */
124     private int mDragDeltaX;
125
126     /**
127      * Offset (in y) within the dragged item at which the user picked it up (or
128      * first touched down with the digitalis).
129      */
130     private int mDragDeltaY;
131
132     /**
133      * A listener that receives callbacks whenever the floating View hovers over
134      * a new position.
135      */
136     private DragListener mDragListener;
137
138     /**
139      * A listener that receives a callback when the floating View is dropped.
140      */
141     private DropListener mDropListener;
142
143     /**
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
146      * gestures.
147      */
148     private RemoveListener mRemoveListener;
149
150     /**
151      * Enable/Disable item dragging
152      */
153     private boolean mDragEnabled = true;
154
155     /**
156      * Drag state enum.
157      */
158     private final static int IDLE = 0;
159
160     private final static int STOPPED = 1;
161
162     private final static int DRAGGING = 2;
163
164     private int mDragState = IDLE;
165
166     /**
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.
169      */
170     private int mItemHeightCollapsed = 1;
171
172     /**
173      * Height of the floating View. Stored for the purpose of providing the
174      * tracking drop slot.
175      */
176     private int mFloatViewHeight;
177
178     /**
179      * Convenience member. See above.
180      */
181     private int mFloatViewHeightHalf;
182
183     /**
184      * Save the given width spec for use in measuring children
185      */
186     private int mWidthMeasureSpec = 0;
187
188     /**
189      * Sample Views ultimately used for calculating the height of ListView items
190      * that are off-screen.
191      */
192     private View[] mSampleViewTypes = new View[1];
193
194     /**
195      * Drag-scroll encapsulator!
196      */
197     private final DragScroller mDragScroller;
198
199     /**
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.
203      */
204     private float mDragUpScrollStartFrac = 1.0f / 3.0f;
205
206     /**
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.
210      */
211     private float mDragDownScrollStartFrac = 1.0f / 3.0f;
212
213     /**
214      * The following are calculated from the above fracs.
215      */
216     private int mUpScrollStartY;
217
218     private int mDownScrollStartY;
219
220     private float mDownScrollStartYF;
221
222     private float mUpScrollStartYF;
223
224     /**
225      * Calculated from above above and current ListView height.
226      */
227     private float mDragUpScrollHeight;
228
229     /**
230      * Calculated from above above and current ListView height.
231      */
232     private float mDragDownScrollHeight;
233
234     /**
235      * Maximum drag-scroll speed in pixels per ms. Only used with default linear
236      * drag-scroll profile.
237      */
238     private float mMaxScrollSpeed = 0.3f;
239
240     /**
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.
244      */
245     private DragScrollProfile mScrollProfile = new DragScrollProfile() {
246
247         /**
248          * {@inheritDoc}
249          */
250         @Override
251         public float getSpeed(final float w, final long t) {
252             return mMaxScrollSpeed * w;
253         }
254     };
255
256     /**
257      * Current touch x.
258      */
259     private int mX;
260
261     /**
262      * Current touch y.
263      */
264     private int mY;
265
266     /**
267      * Last touch y.
268      */
269     private int mLastY;
270
271     /**
272      * Drag flag bit. Floating View can move in the positive x direction.
273      */
274     public final static int DRAG_POS_X = 0x1;
275
276     /**
277      * Drag flag bit. Floating View can move in the negative x direction.
278      */
279     public final static int DRAG_NEG_X = 0x2;
280
281     /**
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?
286      */
287     public final static int DRAG_POS_Y = 0x4;
288
289     /**
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
293      * item position?
294      */
295     public final static int DRAG_NEG_Y = 0x8;
296
297     /**
298      * Flags that determine limits on the motion of the floating View. See flags
299      * above.
300      */
301     private int mDragFlags = 0;
302
303     /**
304      * Last call to an on*TouchEvent was a call to onInterceptTouchEvent.
305      */
306     private boolean mLastCallWasIntercept = false;
307
308     /**
309      * A touch event is in progress.
310      */
311     private boolean mInTouchEvent = false;
312
313     /**
314      * Let the user customize the floating View.
315      */
316     private FloatViewManager mFloatViewManager = null;
317
318     /**
319      * Given to ListView to cancel its action when a drag-sort begins.
320      */
321     private final MotionEvent mCancelEvent;
322
323     /**
324      * Enum telling where to cancel the ListView action when a drag-sort begins
325      */
326     private static final int NO_CANCEL = 0;
327
328     private static final int ON_TOUCH_EVENT = 1;
329
330     private static final int ON_INTERCEPT_TOUCH_EVENT = 2;
331
332     /**
333      * Where to cancel the ListView action when a drag-sort begins
334      */
335     private int mCancelMethod = NO_CANCEL;
336
337     /**
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
340      * the slide.
341      */
342     private float mSlideRegionFrac = 0.25f;
343
344     /**
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
348      * directly below).
349      */
350     private float mSlideFrac = 0.0f;
351
352     /**
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.
356      */
357     private AdapterWrapper mAdapterWrapper;
358
359     /**
360      * Turn on custom debugger.
361      */
362     private final boolean mTrackDragSort = false;
363
364     /**
365      * Debugging class.
366      */
367     private DragSortTracker mDragSortTracker;
368
369     /**
370      * Needed for adjusting item heights from within layoutChildren
371      */
372     private boolean mBlockLayoutRequests = false;
373
374     private final DragSortController mController;
375
376     /**
377      * @param context The {@link Context} to use
378      * @param attrs The attributes of the XML tag that is inflating the view.
379      */
380     public DragSortListView(final Context context, final AttributeSet attrs) {
381         super(context, attrs);
382         mItemHeightCollapsed = 1;
383
384         mCurrFloatAlpha = mFloatAlpha;
385
386         mSlideRegionFrac = 0.75f;
387
388         mAnimate = mSlideRegionFrac > 0.0f;
389
390         setDragScrollStart(mDragUpScrollStartFrac);
391
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         mController
397                 .setBackgroundColor(getResources().getColor(R.color.accent));
398
399         mFloatViewManager = mController;
400         setOnTouchListener(mController);
401
402         mDragScroller = new DragScroller();
403         setOnScrollListener(mDragScroller);
404
405         mCancelEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0f, 0f, 0, 0f,
406                 0f, 0, 0);
407
408         mObserver = new DataSetObserver() {
409             private void cancel() {
410                 if (mDragState == DRAGGING) {
411                     stopDrag(false);
412                 }
413             }
414
415             /**
416              * {@inheritDoc}
417              */
418             @Override
419             public void onChanged() {
420                 cancel();
421             }
422
423             /**
424              * {@inheritDoc}
425              */
426             @Override
427             public void onInvalidated() {
428                 cancel();
429             }
430         };
431     }
432
433     /**
434      * Usually called from a FloatViewManager. The float alpha will be reset to
435      * the xml-defined value every time a drag is stopped.
436      */
437     public void setFloatAlpha(final float alpha) {
438         mCurrFloatAlpha = alpha;
439     }
440
441     public float getFloatAlpha() {
442         return mCurrFloatAlpha;
443     }
444
445     /**
446      * Set maximum drag scroll speed in positions/second. Only applies if using
447      * default ScrollSpeedProfile.
448      * 
449      * @param max Maximum scroll speed.
450      */
451     public void setMaxScrollSpeed(final float max) {
452         mMaxScrollSpeed = max;
453     }
454
455     /**
456      * {@inheritDoc}
457      */
458     @Override
459     public void setAdapter(final ListAdapter adapter) {
460         mAdapterWrapper = new AdapterWrapper(adapter);
461         adapter.registerDataSetObserver(mObserver);
462         super.setAdapter(mAdapterWrapper);
463     }
464
465     /**
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).
469      * 
470      * @return The ListAdapter set as the argument of {@link setAdapter()}
471      */
472     public ListAdapter getInputAdapter() {
473         if (mAdapterWrapper == null) {
474             return null;
475         } else {
476             return mAdapterWrapper.getAdapter();
477         }
478     }
479
480     private class AdapterWrapper extends HeaderViewListAdapter {
481         private final ListAdapter mAdapter;
482
483         public AdapterWrapper(final ListAdapter adapter) {
484             super(null, null, adapter);
485             mAdapter = adapter;
486         }
487
488         public ListAdapter getAdapter() {
489             return mAdapter;
490         }
491
492         /**
493          * {@inheritDoc}
494          */
495         @Override
496         public View getView(final int position, final View convertView, final ViewGroup parent) {
497
498             RelativeLayout v;
499             View child;
500             if (convertView != null) {
501
502                 v = (RelativeLayout)convertView;
503                 final View oldChild = v.getChildAt(0);
504                 try {
505                     child = mAdapter.getView(position, oldChild, v);
506                     if (child != oldChild) {
507                         v.removeViewAt(0);
508                         v.addView(child);
509                     }
510                 } catch (final Exception nullz) {
511
512                 }
513             } else {
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);
518                 try {
519                     child = mAdapter.getView(position, null, v);
520                     v.addView(child);
521                 } catch (final Exception todo) {
522
523                 }
524             }
525             adjustItem(position + getHeaderViewsCount(), v, true);
526             return v;
527         }
528     }
529
530     private void drawDivider(final int expPosition, final Canvas canvas) {
531
532         final Drawable divider = getDivider();
533         final int dividerHeight = getDividerHeight();
534
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();
540                 final int t;
541                 final int b;
542
543                 final int childHeight = expItem.getChildAt(0).getHeight();
544
545                 if (expPosition > mSrcPos) {
546                     t = expItem.getTop() + childHeight;
547                     b = t + dividerHeight;
548                 } else {
549                     b = expItem.getBottom() - childHeight;
550                     t = b - dividerHeight;
551                 }
552
553                 divider.setBounds(l, t, r, b);
554                 divider.draw(canvas);
555             }
556         }
557     }
558
559     /**
560      * {@inheritDoc}
561      */
562     @Override
563     protected void dispatchDraw(final Canvas canvas) {
564         super.dispatchDraw(canvas);
565
566         if (mFloatView != null) {
567             if (mFirstExpPos != mSrcPos) {
568                 drawDivider(mFirstExpPos, canvas);
569             }
570             if (mSecondExpPos != mFirstExpPos && mSecondExpPos != mSrcPos) {
571                 drawDivider(mSecondExpPos, canvas);
572             }
573
574             final int w = mFloatView.getWidth();
575             final int h = mFloatView.getHeight();
576             final int alpha = (int)(255f * mCurrFloatAlpha);
577
578             canvas.save();
579             canvas.translate(mFloatViewLeft, mFloatViewTop);
580             canvas.clipRect(0, 0, w, h);
581
582             canvas.saveLayerAlpha(0, 0, w, h, alpha, Canvas.ALL_SAVE_FLAG);
583             mFloatView.draw(canvas);
584             canvas.restore();
585             canvas.restore();
586         }
587     }
588
589     private class ItemHeights {
590         int item;
591
592         int child;
593     }
594
595     private void measureItemAndGetHeights(final int position, final View item,
596             final ItemHeights heights) {
597         ViewGroup.LayoutParams lp = item.getLayoutParams();
598
599         final boolean isHeadFoot = position < getHeaderViewsCount()
600                 || position >= getCount() - getFooterViewsCount();
601
602         int height = lp == null ? 0 : lp.height;
603         if (height > 0) {
604             heights.item = height;
605
606             // get height of child, measure if we have to
607             if (isHeadFoot) {
608                 heights.child = heights.item;
609             } else if (position == mSrcPos) {
610                 heights.child = 0;
611             } else {
612                 final View child = ((ViewGroup)item).getChildAt(0);
613                 lp = child.getLayoutParams();
614                 height = lp == null ? 0 : lp.height;
615                 if (height > 0) {
616                     heights.child = height;
617                 } else {
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();
623                 }
624             }
625         } else {
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
629                     : lp.width);
630             item.measure(wspec, hspec);
631
632             heights.item = item.getMeasuredHeight();
633             if (isHeadFoot) {
634                 heights.child = heights.item;
635             } else if (position == mSrcPos) {
636                 heights.child = 0;
637             } else {
638                 heights.child = ((ViewGroup)item).getChildAt(0).getMeasuredHeight();
639             }
640         }
641     }
642
643     /**
644      * Get the height of the given wrapped item and its child.
645      * 
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.
650      */
651     private void getItemHeights(final int position, final View item, final ItemHeights heights) {
652         final boolean isHeadFoot = position < getHeaderViewsCount()
653                 || position >= getCount() - getFooterViewsCount();
654
655         heights.item = item.getHeight();
656
657         if (isHeadFoot) {
658             heights.child = heights.item;
659         } else if (position == mSrcPos) {
660             heights.child = 0;
661         } else {
662             heights.child = ((ViewGroup)item).getChildAt(0).getHeight();
663         }
664     }
665
666     /**
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.
670      * 
671      * @param position ListView position.
672      * @param heights Object to fill with heights of item at
673      *            <code>position</code>.
674      */
675     private void getItemHeights(final int position, final ItemHeights heights) {
676
677         final int first = getFirstVisiblePosition();
678         final int last = getLastVisiblePosition();
679
680         if (position >= first && position <= last) {
681             getItemHeights(position, getChildAt(position - first), heights);
682         } else {
683             // Log.d("mobeta", "getView for height");
684
685             final ListAdapter adapter = getAdapter();
686             final int type = adapter.getItemViewType(position);
687
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];
692             }
693
694             View v;
695             if (type >= 0) {
696                 if (mSampleViewTypes[type] == null) {
697                     v = adapter.getView(position, null, this);
698                     mSampleViewTypes[type] = v;
699                 } else {
700                     v = adapter.getView(position, mSampleViewTypes[type], this);
701                 }
702             } else {
703                 // type is HEADER_OR_FOOTER or IGNORE
704                 v = adapter.getView(position, null, this);
705             }
706
707             measureItemAndGetHeights(position, v, heights);
708         }
709
710     }
711
712     private int getShuffleEdge(final int position, final int top) {
713         return getShuffleEdge(position, top, null);
714     }
715
716     /**
717      * Get the shuffle edge for item at position when top of item is at y-coord
718      * top
719      * 
720      * @param position
721      * @param top
722      * @param height Height of item at position. If -1, this function calculates
723      *            this height.
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
729      *         position.
730      */
731     private int getShuffleEdge(final int position, final int top, ItemHeights heights) {
732
733         final int numHeaders = getHeaderViewsCount();
734         final int numFooters = getFooterViewsCount();
735
736         // shuffle edges are defined between items that can be
737         // dragged; there are N-1 of them if there are N draggable
738         // items.
739
740         if (position <= numHeaders || position >= getCount() - numFooters) {
741             return top;
742         }
743
744         final int divHeight = getDividerHeight();
745
746         int edge;
747
748         final int maxBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
749
750         if (heights == null) {
751             heights = new ItemHeights();
752             getItemHeights(position, heights);
753         }
754
755         // first calculate top of item given that floating View is
756         // centered over src position
757         int otop = top;
758         if (mSecondExpPos <= mSrcPos) {
759             // items are expanded on and/or above the source position
760
761             if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
762                 if (position == mSrcPos) {
763                     otop = top + heights.item - mFloatViewHeight;
764                 } else {
765                     final int blankHeight = heights.item - heights.child;
766                     otop = top + blankHeight - maxBlankHeight;
767                 }
768             } else if (position > mSecondExpPos && position <= mSrcPos) {
769                 otop = top - maxBlankHeight;
770             }
771
772         } else {
773             // items are expanded on and/or below the source position
774
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;
780             }
781         }
782
783         // otop is set
784         if (position <= mSrcPos) {
785             final ItemHeights tmpHeights = new ItemHeights();
786             getItemHeights(position - 1, tmpHeights);
787             edge = otop + (mFloatViewHeight - divHeight - tmpHeights.child) / 2;
788         } else {
789             edge = otop + (heights.child - divHeight - mFloatViewHeight) / 2;
790         }
791
792         return edge;
793     }
794
795     private boolean updatePositions() {
796
797         final int first = getFirstVisiblePosition();
798         int startPos = mFirstExpPos;
799         View startView = getChildAt(startPos - first);
800
801         if (startView == null) {
802             startPos = first + getChildCount() / 2;
803             startView = getChildAt(startPos - first);
804         }
805         final int startTop = startView.getTop() + mScrollY;
806
807         final ItemHeights itemHeights = new ItemHeights();
808         getItemHeights(startPos, startView, itemHeights);
809
810         int edge = getShuffleEdge(startPos, startTop, itemHeights);
811         int lastEdge = edge;
812
813         final int divHeight = getDividerHeight();
814
815         // Log.d("mobeta", "float mid="+mFloatViewMid);
816
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) {
823                 itemPos--;
824                 getItemHeights(itemPos, itemHeights);
825
826                 // if (itemPos <= 0)
827                 if (itemPos == 0) {
828                     edge = itemTop - divHeight - itemHeights.item;
829                     // itemPos = 0;
830                     break;
831                 }
832
833                 itemTop -= itemHeights.item + divHeight;
834                 edge = getShuffleEdge(itemPos, itemTop, itemHeights);
835                 // Log.d("mobeta", "    edge="+edge);
836
837                 if (mFloatViewMid >= edge) {
838                     break;
839                 }
840
841                 lastEdge = edge;
842             }
843         } else {
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;
850                     break;
851                 }
852
853                 itemTop += divHeight + itemHeights.item;
854                 getItemHeights(itemPos + 1, itemHeights);
855                 edge = getShuffleEdge(itemPos + 1, itemTop, itemHeights);
856                 // Log.d("mobeta", "    edge="+edge);
857
858                 // test for hit
859                 if (mFloatViewMid < edge) {
860                     break;
861                 }
862
863                 lastEdge = edge;
864                 itemPos++;
865             }
866         }
867
868         final int numHeaders = getHeaderViewsCount();
869         final int numFooters = getFooterViewsCount();
870
871         boolean updated = false;
872
873         final int oldFirstExpPos = mFirstExpPos;
874         final int oldSecondExpPos = mSecondExpPos;
875         final float oldSlideFrac = mSlideFrac;
876
877         if (mAnimate) {
878             final int edgeToEdge = Math.abs(edge - lastEdge);
879
880             int edgeTop, edgeBottom;
881             if (mFloatViewMid < edge) {
882                 edgeBottom = edge;
883                 edgeTop = lastEdge;
884             } else {
885                 edgeTop = edge;
886                 edgeBottom = lastEdge;
887             }
888             // Log.d("mobeta", "edgeTop="+edgeTop+" edgeBot="+edgeBottom);
889
890             final int slideRgnHeight = (int)(0.5f * mSlideRegionFrac * edgeToEdge);
891             final int slideEdgeTop = edgeTop + slideRgnHeight;
892             final int slideEdgeBottom = edgeBottom - slideRgnHeight;
893
894             // Three regions
895             if (mFloatViewMid < slideEdgeTop) {
896                 mFirstExpPos = itemPos - 1;
897                 mSecondExpPos = itemPos;
898                 mSlideFrac = 0.5f * (slideEdgeTop - mFloatViewMid) / (float) slideRgnHeight;
899                 // Log.d("mobeta",
900                 // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
901             } else if (mFloatViewMid < slideEdgeBottom) {
902                 mFirstExpPos = itemPos;
903                 mSecondExpPos = itemPos;
904             } else {
905                 mFirstExpPos = itemPos;
906                 mSecondExpPos = itemPos + 1;
907                 mSlideFrac = 0.5f * (1.0f + (edgeBottom - mFloatViewMid) / (float) slideRgnHeight);
908                 // Log.d("mobeta",
909                 // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
910             }
911
912         } else {
913             mFirstExpPos = itemPos;
914             mSecondExpPos = itemPos;
915         }
916
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;
926         }
927
928         if (mFirstExpPos != oldFirstExpPos || mSecondExpPos != oldSecondExpPos
929                 || mSlideFrac != oldSlideFrac) {
930             updated = true;
931         }
932
933         if (itemPos != mFloatPos) {
934             if (mDragListener != null) {
935                 mDragListener.drag(mFloatPos - numHeaders, itemPos - numHeaders);
936             }
937
938             mFloatPos = itemPos;
939             updated = true;
940         }
941
942         return updated;
943     }
944
945     /**
946      * {@inheritDoc}
947      */
948     @Override
949     protected void onDraw(final Canvas canvas) {
950         super.onDraw(canvas);
951
952         if (mTrackDragSort) {
953             mDragSortTracker.appendState();
954         }
955     }
956
957     /**
958      * Stop a drag in progress. Pass <code>true</code> if you would like to
959      * remove the dragged item from the list.
960      * 
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.
964      */
965     public boolean stopDrag(final boolean remove) {
966         if (mFloatView != null) {
967             mDragState = STOPPED;
968
969             // stop the drag
970             dropFloatView(remove);
971
972             return true;
973         } else {
974             // stop failed
975             return false;
976         }
977     }
978
979     /**
980      * {@inheritDoc}
981      */
982     @Override
983     public boolean onTouchEvent(final MotionEvent ev) {
984
985         if (!mDragEnabled) {
986             return super.onTouchEvent(ev);
987         }
988
989         boolean more = false;
990
991         final boolean lastCallWasIntercept = mLastCallWasIntercept;
992         mLastCallWasIntercept = false;
993
994         if (!lastCallWasIntercept) {
995             saveTouchCoords(ev);
996         }
997
998         if (mFloatView != null) {
999             onDragTouchEvent(ev);
1000             more = true; // give us more!
1001         } else {
1002             // what if float view is null b/c we dropped in middle
1003             // of drag touch event?
1004
1005             if (mDragState != STOPPED) {
1006                 if (super.onTouchEvent(ev)) {
1007                     more = true;
1008                 }
1009             }
1010
1011             final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1012             switch (action) {
1013                 case MotionEvent.ACTION_CANCEL:
1014                 case MotionEvent.ACTION_UP:
1015                     doActionUpOrCancel();
1016                     break;
1017                 default:
1018                     if (more) {
1019                         mCancelMethod = ON_TOUCH_EVENT;
1020                     }
1021             }
1022         }
1023
1024         return more;
1025
1026     }
1027
1028     private void doActionUpOrCancel() {
1029         mCancelMethod = NO_CANCEL;
1030         mInTouchEvent = false;
1031         mDragState = IDLE;
1032         mCurrFloatAlpha = mFloatAlpha;
1033     }
1034
1035     private void saveTouchCoords(final MotionEvent ev) {
1036         final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1037         if (action != MotionEvent.ACTION_DOWN) {
1038             mLastY = mY;
1039         }
1040         mX = (int)ev.getX();
1041         mY = (int)ev.getY();
1042         if (action == MotionEvent.ACTION_DOWN) {
1043             mLastY = mY;
1044         }
1045     }
1046
1047     /**
1048      * {@inheritDoc}
1049      */
1050     @Override
1051     public boolean onInterceptTouchEvent(final MotionEvent ev) {
1052         if (!mDragEnabled) {
1053             return super.onInterceptTouchEvent(ev);
1054         }
1055
1056         saveTouchCoords(ev);
1057         mLastCallWasIntercept = true;
1058
1059         boolean intercept = false;
1060
1061         final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1062
1063         if (action == MotionEvent.ACTION_DOWN) {
1064             mInTouchEvent = true;
1065         }
1066
1067         // the following deals with calls to super.onInterceptTouchEvent
1068         if (mFloatView != null) {
1069             // super's touch event canceled in startDrag
1070             intercept = true;
1071         } else {
1072             if (super.onInterceptTouchEvent(ev)) {
1073                 intercept = true;
1074             }
1075
1076             switch (action) {
1077                 case MotionEvent.ACTION_CANCEL:
1078                 case MotionEvent.ACTION_UP:
1079                     doActionUpOrCancel();
1080                     break;
1081                 default:
1082                     if (intercept) {
1083                         mCancelMethod = ON_TOUCH_EVENT;
1084                     } else {
1085                         mCancelMethod = ON_INTERCEPT_TOUCH_EVENT;
1086                     }
1087             }
1088         }
1089
1090         // check for startDragging
1091
1092         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
1093             mInTouchEvent = false;
1094         }
1095
1096         return intercept;
1097     }
1098
1099     /**
1100      * Set the width of each drag scroll region by specifying a fraction of the
1101      * ListView height.
1102      * 
1103      * @param heightFraction Fraction of ListView height. Capped at 0.5f.
1104      */
1105     public void setDragScrollStart(final float heightFraction) {
1106         setDragScrollStarts(heightFraction, heightFraction);
1107     }
1108
1109     /**
1110      * Set the width of each drag scroll region by specifying a fraction of the
1111      * ListView height.
1112      * 
1113      * @param upperFrac Fraction of ListView height for up-scroll bound. Capped
1114      *            at 0.5f.
1115      * @param lowerFrac Fraction of ListView height for down-scroll bound.
1116      *            Capped at 0.5f.
1117      */
1118     public void setDragScrollStarts(final float upperFrac, final float lowerFrac) {
1119         if (lowerFrac > 0.5f) {
1120             mDragDownScrollStartFrac = 0.5f;
1121         } else {
1122             mDragDownScrollStartFrac = lowerFrac;
1123         }
1124
1125         if (upperFrac > 0.5f) {
1126             mDragUpScrollStartFrac = 0.5f;
1127         } else {
1128             mDragUpScrollStartFrac = upperFrac;
1129         }
1130
1131         if (getHeight() != 0) {
1132             updateScrollStarts();
1133         }
1134     }
1135
1136     private void continueDrag(final int x, final int y) {
1137
1138         // Log.d("mobeta", "move");
1139         dragView(x, y);
1140
1141         // if (mTrackDragSort) {
1142         // mDragSortTracker.appendState();
1143         // }
1144
1145         requestLayout();
1146
1147         final int minY = Math.min(y, mFloatViewMid + mFloatViewHeightHalf);
1148         final int maxY = Math.max(y, mFloatViewMid - mFloatViewHeightHalf);
1149
1150         // get the current scroll direction
1151         final int currentScrollDir = mDragScroller.getScrollDir();
1152
1153         if (minY > mLastY && minY > mDownScrollStartY && currentScrollDir != DragScroller.DOWN) {
1154             // dragged down, it is below the down scroll start and it is not
1155             // scrolling up
1156
1157             if (currentScrollDir != DragScroller.STOP) {
1158                 // moved directly from up scroll to down scroll
1159                 mDragScroller.stopScrolling(true);
1160             }
1161
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
1166             // scrolling up
1167
1168             if (currentScrollDir != DragScroller.STOP) {
1169                 // moved directly from down scroll to up scroll
1170                 mDragScroller.stopScrolling(true);
1171             }
1172
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
1178             // still scrolling
1179
1180             mDragScroller.stopScrolling(true);
1181         }
1182     }
1183
1184     private void updateScrollStarts() {
1185         final int padTop = getPaddingTop();
1186         final int listHeight = getHeight() - padTop - getPaddingBottom();
1187
1188         mUpScrollStartYF = padTop + mDragUpScrollStartFrac * (float) listHeight;
1189         mDownScrollStartYF = padTop + (1.0f - mDragDownScrollStartFrac) * (float) listHeight;
1190
1191         mUpScrollStartY = (int)mUpScrollStartYF;
1192         mDownScrollStartY = (int)mDownScrollStartYF;
1193
1194         mDragUpScrollHeight = mUpScrollStartYF - padTop;
1195         mDragDownScrollHeight = padTop + listHeight - mDownScrollStartYF;
1196     }
1197
1198     /**
1199      * {@inheritDoc}
1200      */
1201     @Override
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();
1205     }
1206
1207     private void dropFloatView(final boolean removeSrcItem) {
1208
1209         mDragScroller.stopScrolling(true);
1210
1211         if (removeSrcItem) {
1212             if (mRemoveListener != null) {
1213                 mRemoveListener.remove(mSrcPos - getHeaderViewsCount());
1214             }
1215         } else {
1216             if (mDropListener != null && mFloatPos >= 0 && mFloatPos < getCount()) {
1217                 final int numHeaders = getHeaderViewsCount();
1218                 mDropListener.drop(mSrcPos - numHeaders, mFloatPos - numHeaders);
1219             }
1220
1221             // adjustAllItems();
1222
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);
1228                 int top = 0;
1229                 if (v != null) {
1230                     top = v.getTop();
1231                 }
1232                 // Log.d("mobeta", "top="+top+" fvh="+mFloatViewHeight);
1233                 setSelectionFromTop(firstPos - 1, top - getPaddingTop());
1234             }
1235         }
1236
1237         mSrcPos = -1;
1238         mFirstExpPos = -1;
1239         mSecondExpPos = -1;
1240         mFloatPos = -1;
1241
1242         removeFloatView();
1243
1244         if (mTrackDragSort) {
1245             mDragSortTracker.stopTracking();
1246         }
1247     }
1248
1249     private void adjustAllItems() {
1250         final int first = getFirstVisiblePosition();
1251         final int last = getLastVisiblePosition();
1252
1253         final int begin = Math.max(0, getHeaderViewsCount() - first);
1254         final int end = Math.min(last - first, getCount() - 1 - getFooterViewsCount() - first);
1255
1256         for (int i = begin; i <= end; ++i) {
1257             final View v = getChildAt(i);
1258             if (v != null) {
1259                 adjustItem(first + i, v, false);
1260             }
1261         }
1262     }
1263
1264     private void adjustItem(final int position, final View v, final boolean needsMeasure) {
1265
1266         final ViewGroup.LayoutParams lp = v.getLayoutParams();
1267         final int oldHeight = lp.height;
1268         int height = oldHeight;
1269
1270         getDividerHeight();
1271
1272         final boolean isSliding = mAnimate && mFirstExpPos != mSecondExpPos;
1273         final int maxNonSrcBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
1274         final int slideHeight = (int)(mSlideFrac * maxNonSrcBlankHeight);
1275
1276         if (position == mSrcPos) {
1277             if (mSrcPos == mFirstExpPos) {
1278                 if (isSliding) {
1279                     height = slideHeight + mItemHeightCollapsed;
1280                 } else {
1281                     height = mFloatViewHeight;
1282                 }
1283             } else if (mSrcPos == mSecondExpPos) {
1284                 // if gets here, we know an item is sliding
1285                 height = mFloatViewHeight - slideHeight;
1286             } else {
1287                 height = mItemHeightCollapsed;
1288             }
1289         } else if (position == mFirstExpPos || position == mSecondExpPos) {
1290             // position is not src
1291
1292             final ItemHeights itemHeights = new ItemHeights();
1293             if (needsMeasure) {
1294                 measureItemAndGetHeights(position, v, itemHeights);
1295             } else {
1296                 getItemHeights(position, v, itemHeights);
1297             }
1298
1299             if (position == mFirstExpPos) {
1300                 if (isSliding) {
1301                     height = itemHeights.child + slideHeight;
1302                 } else {
1303                     height = itemHeights.child + maxNonSrcBlankHeight;
1304                 }
1305             } else { // position=mSecondExpPos
1306                 // we know an item is sliding (b/c 2ndPos != 1stPos)
1307                 height = itemHeights.child + maxNonSrcBlankHeight - slideHeight;
1308             }
1309         } else {
1310             height = ViewGroup.LayoutParams.WRAP_CONTENT;
1311         }
1312
1313         if (height != oldHeight) {
1314             lp.height = height;
1315
1316             v.setLayoutParams(lp);
1317         }
1318
1319         // Adjust item gravity
1320
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);
1326             }
1327         }
1328
1329         // Finally adjust item visibility
1330
1331         final int oldVis = v.getVisibility();
1332         int vis = View.VISIBLE;
1333
1334         if (position == mSrcPos && mFloatView != null) {
1335             vis = View.INVISIBLE;
1336         }
1337
1338         if (vis != oldVis) {
1339             v.setVisibility(vis);
1340         }
1341     }
1342
1343     /**
1344      * {@inheritDoc}
1345      */
1346     @Override
1347     public void requestLayout() {
1348         if (!mBlockLayoutRequests) {
1349             super.requestLayout();
1350         }
1351     }
1352
1353     private void doDragScroll(final int oldFirstExpPos, final int oldSecondExpPos) {
1354         if (mScrollY == 0) {
1355             return;
1356         }
1357
1358         final int padTop = getPaddingTop();
1359         final int listHeight = getHeight() - padTop - getPaddingBottom();
1360         final int first = getFirstVisiblePosition();
1361         final int last = getLastVisiblePosition();
1362
1363         int movePos;
1364
1365         if (mScrollY >= 0) {
1366             mScrollY = Math.min(listHeight, mScrollY);
1367             movePos = first;
1368         } else {
1369             mScrollY = Math.max(-listHeight, mScrollY);
1370             movePos = last;
1371         }
1372
1373         final View moveItem = getChildAt(movePos - first);
1374         int top = moveItem.getTop() + mScrollY;
1375
1376         if (movePos == 0 && top > padTop) {
1377             top = padTop;
1378         }
1379
1380         final ItemHeights itemHeightsBefore = new ItemHeights();
1381         getItemHeights(movePos, moveItem, itemHeightsBefore);
1382         final int moveHeightBefore = itemHeightsBefore.item;
1383         final int moveBlankBefore = moveHeightBefore - itemHeightsBefore.child;
1384
1385         final ItemHeights itemHeightsAfter = new ItemHeights();
1386         measureItemAndGetHeights(movePos, moveItem, itemHeightsAfter);
1387         final int moveHeightAfter = itemHeightsAfter.item;
1388         final int moveBlankAfter = moveHeightAfter - itemHeightsAfter.child;
1389
1390         if (movePos <= oldFirstExpPos) {
1391             if (movePos > mFirstExpPos) {
1392                 top += mFloatViewHeight - moveBlankAfter;
1393             }
1394         } else if (movePos == oldSecondExpPos) {
1395             if (movePos <= mFirstExpPos) {
1396                 top += moveBlankBefore - mFloatViewHeight;
1397             } else if (movePos == mSecondExpPos) {
1398                 top += moveHeightBefore - moveHeightAfter;
1399             } else {
1400                 top += moveBlankBefore;
1401             }
1402         } else {
1403             if (movePos <= mFirstExpPos) {
1404                 top -= mFloatViewHeight;
1405             } else if (movePos == mSecondExpPos) {
1406                 top -= moveBlankAfter;
1407             }
1408         }
1409
1410         setSelectionFromTop(movePos, top - padTop);
1411
1412         mScrollY = 0;
1413     }
1414
1415     private void measureFloatView() {
1416         if (mFloatView != null) {
1417             ViewGroup.LayoutParams lp = mFloatView.getLayoutParams();
1418             if (lp == null) {
1419                 lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1420                         ViewGroup.LayoutParams.WRAP_CONTENT);
1421             }
1422             final int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft()
1423                     + getListPaddingRight(), lp.width);
1424             int hspec;
1425             if (lp.height > 0) {
1426                 hspec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
1427             } else {
1428                 hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1429             }
1430             mFloatView.measure(wspec, hspec);
1431             mFloatViewHeight = mFloatView.getMeasuredHeight();
1432             mFloatViewHeightHalf = mFloatViewHeight / 2;
1433         }
1434     }
1435
1436     /**
1437      * {@inheritDoc}
1438      */
1439     @Override
1440     protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
1441         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1442         if (mFloatView != null) {
1443             if (mFloatView.isLayoutRequested()) {
1444                 measureFloatView();
1445             }
1446         }
1447         mWidthMeasureSpec = widthMeasureSpec;
1448         mDragScroller.setListHeight(getHeight());
1449     }
1450
1451     /**
1452      * {@inheritDoc}
1453      */
1454     @Override
1455     protected void layoutChildren() {
1456
1457         if (mFloatView != null) {
1458             mFloatView.layout(0, 0, mFloatView.getMeasuredWidth(), mFloatView.getMeasuredHeight());
1459
1460             // Log.d("mobeta", "layout children");
1461             final int oldFirstExpPos = mFirstExpPos;
1462             final int oldSecondExpPos = mSecondExpPos;
1463
1464             mBlockLayoutRequests = true;
1465
1466             if (getChildCount() > 0 && updatePositions()) {
1467                 adjustAllItems();
1468             }
1469
1470             if (mScrollY != 0) {
1471                 doDragScroll(oldFirstExpPos, oldSecondExpPos);
1472             }
1473
1474             mBlockLayoutRequests = false;
1475         }
1476
1477         super.layoutChildren();
1478     }
1479
1480     protected boolean onDragTouchEvent(final MotionEvent ev) {
1481         switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1482             case MotionEvent.ACTION_CANCEL:
1483             case MotionEvent.ACTION_UP:
1484                 stopDrag(false);
1485                 doActionUpOrCancel();
1486                 break;
1487             case MotionEvent.ACTION_MOVE:
1488                 continueDrag((int)ev.getX(), (int)ev.getY());
1489                 break;
1490         }
1491
1492         return true;
1493     }
1494
1495     /**
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.
1500      * 
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.
1514      */
1515     public boolean startDrag(final int position, final int dragFlags, final int deltaX,
1516             final int deltaY) {
1517         if (!mInTouchEvent || mFloatViewManager == null) {
1518             return false;
1519         }
1520
1521         final View v = mFloatViewManager.onCreateFloatView(position);
1522
1523         if (v == null) {
1524             return false;
1525         } else {
1526             return startDrag(position, v, dragFlags, deltaX, deltaY);
1527         }
1528
1529     }
1530
1531     /**
1532      * Start a drag of item at <code>position</code> without using a
1533      * FloatViewManager.
1534      * 
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
1548      *         in progress.
1549      */
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) {
1553             return false;
1554         }
1555
1556         if (getParent() != null) {
1557             getParent().requestDisallowInterceptTouchEvent(true);
1558         }
1559
1560         final int pos = position + getHeaderViewsCount();
1561         mFirstExpPos = pos;
1562         mSecondExpPos = pos;
1563         mSrcPos = pos;
1564         mFloatPos = pos;
1565
1566         // mDragState = dragType;
1567         mDragState = DRAGGING;
1568         mDragFlags = 0;
1569         mDragFlags |= dragFlags;
1570
1571         mFloatView = floatView;
1572         measureFloatView(); // sets mFloatViewHeight
1573
1574         mDragDeltaX = deltaX;
1575         mDragDeltaY = deltaY;
1576         updateFloatView(mX - mDragDeltaX, mY - mDragDeltaY);
1577
1578         // set src item invisible
1579         final View srcItem = getChildAt(mSrcPos - getFirstVisiblePosition());
1580         if (srcItem != null) {
1581             srcItem.setVisibility(View.INVISIBLE);
1582         }
1583
1584         if (mTrackDragSort) {
1585             mDragSortTracker.startTracking();
1586         }
1587
1588         // once float view is created, events are no longer passed
1589         // to ListView
1590         switch (mCancelMethod) {
1591             case ON_TOUCH_EVENT:
1592                 super.onTouchEvent(mCancelEvent);
1593                 break;
1594             case ON_INTERCEPT_TOUCH_EVENT:
1595                 super.onInterceptTouchEvent(mCancelEvent);
1596                 break;
1597         }
1598
1599         requestLayout();
1600
1601         return true;
1602     }
1603
1604     /**
1605      * Sets float View location based on suggested values and constraints set in
1606      * mDragFlags.
1607      */
1608     private void updateFloatView(final int floatX, final int floatY) {
1609
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;
1616         } else {
1617             mFloatViewLeft = floatX;
1618         }
1619
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();
1625
1626         // Log.d("mobeta",
1627         // "nHead="+numHeaders+" nFoot="+numFooters+" first="+firstPos+" last="+lastPos);
1628         int topLimit = getPaddingTop();
1629         if (firstPos < numHeaders) {
1630             topLimit = getChildAt(numHeaders - firstPos - 1).getBottom();
1631         }
1632         if ((mDragFlags & DRAG_NEG_Y) == 0) {
1633             if (firstPos <= mSrcPos) {
1634                 topLimit = Math.max(getChildAt(mSrcPos - firstPos).getTop(), topLimit);
1635             }
1636         }
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();
1642         }
1643         if ((mDragFlags & DRAG_POS_Y) == 0) {
1644             if (lastPos >= mSrcPos) {
1645                 bottomLimit = Math.min(getChildAt(mSrcPos - firstPos).getBottom(), bottomLimit);
1646             }
1647         }
1648
1649         // Log.d("mobeta", "dragView top=" + (y - mDragDeltaY));
1650         // Log.d("mobeta", "limit=" + limit);
1651         // Log.d("mobeta", "mDragDeltaY=" + mDragDeltaY);
1652
1653         if (floatY < topLimit) {
1654             mFloatViewTop = topLimit;
1655         } else if (floatY + mFloatViewHeight > bottomLimit) {
1656             mFloatViewTop = bottomLimit - mFloatViewHeight;
1657         } else {
1658             mFloatViewTop = floatY;
1659         }
1660
1661         // get y-midpoint of floating view (constrained to ListView bounds)
1662         mFloatViewMid = mFloatViewTop + mFloatViewHeightHalf;
1663     }
1664
1665     private void dragView(final int x, final int y) {
1666         // Log.d("mobeta", "float view pure x=" + x + " y=" + y);
1667
1668         // proposed position
1669         mFloatLoc.x = x - mDragDeltaX;
1670         mFloatLoc.y = y - mDragDeltaY;
1671
1672         final Point touch = new Point(x, y);
1673
1674         // let manager adjust proposed position first
1675         if (mFloatViewManager != null) {
1676             mFloatViewManager.onDragFloatView(mFloatView, mFloatLoc, touch);
1677         }
1678
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);
1683     }
1684
1685     private void removeFloatView() {
1686         if (mFloatView != null) {
1687             mFloatView.setVisibility(GONE);
1688             if (mFloatViewManager != null) {
1689                 mFloatViewManager.onDestroyFloatView(mFloatView);
1690             }
1691             mFloatView = null;
1692         }
1693     }
1694
1695     /**
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.
1700      */
1701     public interface FloatViewManager {
1702         /**
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
1708          * WRAP_CONTENT).
1709          * 
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.
1715          */
1716         public View onCreateFloatView(int position);
1717
1718         /**
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>.
1723          * 
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).
1730          */
1731         public void onDragFloatView(View floatView, Point location, Point touch);
1732
1733         /**
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.
1737          * 
1738          * @param floatView The floating View passed to
1739          *            {@link #onCreateFloatView(int)}.
1740          */
1741         public void onDestroyFloatView(View floatView);
1742     }
1743
1744     public void setFloatViewManager(final FloatViewManager manager) {
1745         mFloatViewManager = manager;
1746     }
1747
1748     public void setDragListener(final DragListener l) {
1749         mDragListener = l;
1750     }
1751
1752     /**
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.
1757      * 
1758      * @param enabled Set <code>true</code> to enable list item dragging
1759      */
1760     public void setDragEnabled(final boolean enabled) {
1761         mDragEnabled = enabled;
1762     }
1763
1764     public boolean isDragEnabled() {
1765         return mDragEnabled;
1766     }
1767
1768     /**
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.
1773      * 
1774      * @param l
1775      */
1776     public void setDropListener(final DropListener l) {
1777         mDropListener = l;
1778     }
1779
1780     /**
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...
1786      * 
1787      * @param l
1788      */
1789     public void setRemoveListener(final RemoveListener l) {
1790         if (mController != null && l == null) {
1791             mController.setRemoveEnabled(false);
1792         }
1793         mRemoveListener = l;
1794     }
1795
1796     public interface DragListener {
1797         public void drag(int from, int to);
1798     }
1799
1800     /**
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.
1804      * 
1805      * @author heycosmo
1806      */
1807     public interface DropListener {
1808         public void drop(int from, int to);
1809     }
1810
1811     /**
1812      * Make sure to call {@link BaseAdapter#notifyDataSetChanged()} or something
1813      * like it in your implementation.
1814      * 
1815      * @author heycosmo
1816      */
1817     public interface RemoveListener {
1818         public void remove(int which);
1819     }
1820
1821     public interface DragSortListener extends DropListener, DragListener, RemoveListener {
1822     }
1823
1824     public void setDragSortListener(final DragSortListener l) {
1825         setDropListener(l);
1826         setDragListener(l);
1827         setRemoveListener(l);
1828     }
1829
1830     /**
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}.
1834      * 
1835      * @param ssp
1836      */
1837     public void setDragScrollProfile(final DragScrollProfile ssp) {
1838         if (ssp != null) {
1839             mScrollProfile = ssp;
1840         }
1841     }
1842
1843     /**
1844      * Interface for controlling scroll speed as a function of touch position
1845      * and time. Use
1846      * {@link DragSortListView#setDragScrollProfile(DragScrollProfile)} to set
1847      * custom profile.
1848      * 
1849      * @author heycosmo
1850      */
1851     public interface DragScrollProfile {
1852         /**
1853          * Return a scroll speed in pixels/millisecond. Always return a positive
1854          * number.
1855          * 
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.
1861          */
1862         float getSpeed(float w, long t);
1863     }
1864
1865     private class DragScroller implements Runnable, AbsListView.OnScrollListener {
1866
1867         private boolean mAbort;
1868
1869         private long mPrevTime;
1870
1871         private int dy;
1872
1873         private float dt;
1874
1875         private long tStart;
1876
1877         private int scrollDir;
1878
1879         public final static int STOP = -1;
1880
1881         public final static int UP = 0;
1882
1883         public final static int DOWN = 1;
1884
1885         private float mScrollSpeed; // pixels per ms
1886
1887         private boolean mScrolling = false;
1888
1889         private int mMaxScrollSpeed;
1890
1891         public boolean isScrolling() {
1892             return mScrolling;
1893         }
1894
1895         public int getScrollDir() {
1896             return mScrolling ? scrollDir : STOP;
1897         }
1898
1899         public DragScroller() {
1900         }
1901
1902         public void startScrolling(final int dir) {
1903             if (!mScrolling) {
1904                 // Debug.startMethodTracing("dslv-scroll");
1905                 mAbort = false;
1906                 mScrolling = true;
1907                 tStart = SystemClock.uptimeMillis();
1908                 mPrevTime = tStart;
1909                 scrollDir = dir;
1910                 post(this);
1911             }
1912         }
1913
1914         public void stopScrolling(final boolean now) {
1915             if (now) {
1916                 removeCallbacks(this);
1917                 mScrolling = false;
1918             } else {
1919                 mAbort = true;
1920             }
1921
1922         }
1923
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;
1927         }
1928
1929         /**
1930          * {@inheritDoc}
1931          */
1932         @Override
1933         public void run() {
1934             if (mAbort) {
1935                 mScrolling = false;
1936                 return;
1937             }
1938
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();
1944
1945             final int minY = Math.min(mY, mFloatViewMid + mFloatViewHeightHalf);
1946             final int maxY = Math.max(mY, mFloatViewMid - mFloatViewHeightHalf);
1947
1948             if (scrollDir == UP) {
1949                 final View v = getChildAt(0);
1950                 if (v == null) {
1951                     mScrolling = false;
1952                     return;
1953                 } else {
1954                     if (first == 0 && v.getTop() == padTop) {
1955                         mScrolling = false;
1956                         return;
1957                     }
1958                 }
1959                 mScrollSpeed = mScrollProfile.getSpeed((mUpScrollStartYF - maxY)
1960                         / mDragUpScrollHeight, mPrevTime);
1961             } else {
1962                 final View v = getChildAt(last - first);
1963                 if (v == null) {
1964                     mScrolling = false;
1965                     return;
1966                 } else {
1967                     if (last == count - 1 && v.getBottom() <= listHeight + padTop) {
1968                         mScrolling = false;
1969                         return;
1970                     }
1971                 }
1972                 mScrollSpeed = -mScrollProfile.getSpeed((minY - mDownScrollStartYF)
1973                         / mDragDownScrollHeight, mPrevTime);
1974             }
1975
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,
1979             // remember
1980             // y=0 is at top of View).
1981             dy = Math.round(mScrollSpeed * dt);
1982             mScrollY += dy;
1983
1984             // cap the scroll speed
1985             mScrollY = Math.max(Math.min(mScrollY, mMaxScrollSpeed), -mMaxScrollSpeed);
1986
1987             requestLayout();
1988
1989             mPrevTime += dt;
1990
1991             post(this);
1992         }
1993
1994         /**
1995          * {@inheritDoc}
1996          */
1997         @Override
1998         public void onScroll(final AbsListView view, final int firstVisibleItem,
1999                 final int visibleItemCount, final int totalItemCount) {
2000             if (mScrolling && visibleItemCount != 0) {
2001                 dragView(mX, mY);
2002             }
2003         }
2004
2005         /**
2006          * {@inheritDoc}
2007          */
2008         @Override
2009         public void onScrollStateChanged(final AbsListView view, final int scrollState) {
2010         }
2011
2012     }
2013
2014     private class DragSortTracker {
2015         StringBuilder mBuilder = new StringBuilder();
2016
2017         File mFile;
2018
2019         private int mNumInBuffer = 0;
2020
2021         private int mNumFlushes = 0;
2022
2023         private boolean mTracking = false;
2024
2025         public void startTracking() {
2026             mBuilder.append("<DSLVStates>\n");
2027             mNumFlushes = 0;
2028             mTracking = true;
2029         }
2030
2031         public void appendState() {
2032             if (!mTracking) {
2033                 return;
2034             }
2035
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(",");
2043             }
2044             mBuilder.append("</Positions>\n");
2045
2046             mBuilder.append("    <Tops>");
2047             for (int i = 0; i < children; ++i) {
2048                 mBuilder.append(getChildAt(i).getTop()).append(",");
2049             }
2050             mBuilder.append("</Tops>\n");
2051             mBuilder.append("    <Bottoms>");
2052             for (int i = 0; i < children; ++i) {
2053                 mBuilder.append(getChildAt(i).getBottom()).append(",");
2054             }
2055             mBuilder.append("</Bottoms>\n");
2056
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(",");
2076             }
2077             mBuilder.append("</ShuffleEdges>\n");
2078
2079             mBuilder.append("</DSLVState>\n");
2080             mNumInBuffer++;
2081
2082             if (mNumInBuffer > 1000) {
2083                 flush();
2084                 mNumInBuffer = 0;
2085             }
2086         }
2087
2088         public void flush() {
2089             if (!mTracking) {
2090                 return;
2091             }
2092
2093             // save to file on sdcard
2094             try {
2095                 boolean append = true;
2096                 if (mNumFlushes == 0) {
2097                     append = false;
2098                 }
2099                 final FileWriter writer = new FileWriter(mFile, append);
2100
2101                 writer.write(mBuilder.toString());
2102                 mBuilder.delete(0, mBuilder.length());
2103
2104                 writer.flush();
2105                 writer.close();
2106
2107                 mNumFlushes++;
2108             } catch (final IOException e) {
2109                 // do nothing
2110             }
2111         }
2112
2113         public void stopTracking() {
2114             if (mTracking) {
2115                 mBuilder.append("</DSLVStates>\n");
2116                 flush();
2117                 mTracking = false;
2118             }
2119         }
2120
2121     }
2122
2123 }