OSDN Git Service

Update Eleven headers and namespace for open source
[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         /* Transparent holo light blue */
397         mController
398                 .setBackgroundColor(getResources().getColor(R.color.holo_blue_light_transparent));
399
400         mFloatViewManager = mController;
401         setOnTouchListener(mController);
402
403         mDragScroller = new DragScroller();
404         setOnScrollListener(mDragScroller);
405
406         mCancelEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0f, 0f, 0, 0f,
407                 0f, 0, 0);
408
409         mObserver = new DataSetObserver() {
410             private void cancel() {
411                 if (mDragState == DRAGGING) {
412                     stopDrag(false);
413                 }
414             }
415
416             /**
417              * {@inheritDoc}
418              */
419             @Override
420             public void onChanged() {
421                 cancel();
422             }
423
424             /**
425              * {@inheritDoc}
426              */
427             @Override
428             public void onInvalidated() {
429                 cancel();
430             }
431         };
432     }
433
434     /**
435      * Usually called from a FloatViewManager. The float alpha will be reset to
436      * the xml-defined value every time a drag is stopped.
437      */
438     public void setFloatAlpha(final float alpha) {
439         mCurrFloatAlpha = alpha;
440     }
441
442     public float getFloatAlpha() {
443         return mCurrFloatAlpha;
444     }
445
446     /**
447      * Set maximum drag scroll speed in positions/second. Only applies if using
448      * default ScrollSpeedProfile.
449      * 
450      * @param max Maximum scroll speed.
451      */
452     public void setMaxScrollSpeed(final float max) {
453         mMaxScrollSpeed = max;
454     }
455
456     /**
457      * {@inheritDoc}
458      */
459     @Override
460     public void setAdapter(final ListAdapter adapter) {
461         mAdapterWrapper = new AdapterWrapper(adapter);
462         adapter.registerDataSetObserver(mObserver);
463         super.setAdapter(mAdapterWrapper);
464     }
465
466     /**
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).
470      * 
471      * @return The ListAdapter set as the argument of {@link setAdapter()}
472      */
473     public ListAdapter getInputAdapter() {
474         if (mAdapterWrapper == null) {
475             return null;
476         } else {
477             return mAdapterWrapper.getAdapter();
478         }
479     }
480
481     private class AdapterWrapper extends HeaderViewListAdapter {
482         private final ListAdapter mAdapter;
483
484         public AdapterWrapper(final ListAdapter adapter) {
485             super(null, null, adapter);
486             mAdapter = adapter;
487         }
488
489         public ListAdapter getAdapter() {
490             return mAdapter;
491         }
492
493         /**
494          * {@inheritDoc}
495          */
496         @Override
497         public View getView(final int position, final View convertView, final ViewGroup parent) {
498
499             RelativeLayout v;
500             View child;
501             if (convertView != null) {
502
503                 v = (RelativeLayout)convertView;
504                 final View oldChild = v.getChildAt(0);
505                 try {
506                     child = mAdapter.getView(position, oldChild, v);
507                     if (child != oldChild) {
508                         v.removeViewAt(0);
509                         v.addView(child);
510                     }
511                 } catch (final Exception nullz) {
512
513                 }
514             } else {
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);
519                 try {
520                     child = mAdapter.getView(position, null, v);
521                     v.addView(child);
522                 } catch (final Exception todo) {
523
524                 }
525             }
526             adjustItem(position + getHeaderViewsCount(), v, true);
527             return v;
528         }
529     }
530
531     private void drawDivider(final int expPosition, final Canvas canvas) {
532
533         final Drawable divider = getDivider();
534         final int dividerHeight = getDividerHeight();
535
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();
541                 final int t;
542                 final int b;
543
544                 final int childHeight = expItem.getChildAt(0).getHeight();
545
546                 if (expPosition > mSrcPos) {
547                     t = expItem.getTop() + childHeight;
548                     b = t + dividerHeight;
549                 } else {
550                     b = expItem.getBottom() - childHeight;
551                     t = b - dividerHeight;
552                 }
553
554                 divider.setBounds(l, t, r, b);
555                 divider.draw(canvas);
556             }
557         }
558     }
559
560     /**
561      * {@inheritDoc}
562      */
563     @Override
564     protected void dispatchDraw(final Canvas canvas) {
565         super.dispatchDraw(canvas);
566
567         if (mFloatView != null) {
568             if (mFirstExpPos != mSrcPos) {
569                 drawDivider(mFirstExpPos, canvas);
570             }
571             if (mSecondExpPos != mFirstExpPos && mSecondExpPos != mSrcPos) {
572                 drawDivider(mSecondExpPos, canvas);
573             }
574
575             final int w = mFloatView.getWidth();
576             final int h = mFloatView.getHeight();
577             final int alpha = (int)(255f * mCurrFloatAlpha);
578
579             canvas.save();
580             canvas.translate(mFloatViewLeft, mFloatViewTop);
581             canvas.clipRect(0, 0, w, h);
582
583             canvas.saveLayerAlpha(0, 0, w, h, alpha, Canvas.ALL_SAVE_FLAG);
584             mFloatView.draw(canvas);
585             canvas.restore();
586             canvas.restore();
587         }
588     }
589
590     private class ItemHeights {
591         int item;
592
593         int child;
594     }
595
596     private void measureItemAndGetHeights(final int position, final View item,
597             final ItemHeights heights) {
598         ViewGroup.LayoutParams lp = item.getLayoutParams();
599
600         final boolean isHeadFoot = position < getHeaderViewsCount()
601                 || position >= getCount() - getFooterViewsCount();
602
603         int height = lp == null ? 0 : lp.height;
604         if (height > 0) {
605             heights.item = height;
606
607             // get height of child, measure if we have to
608             if (isHeadFoot) {
609                 heights.child = heights.item;
610             } else if (position == mSrcPos) {
611                 heights.child = 0;
612             } else {
613                 final View child = ((ViewGroup)item).getChildAt(0);
614                 lp = child.getLayoutParams();
615                 height = lp == null ? 0 : lp.height;
616                 if (height > 0) {
617                     heights.child = height;
618                 } else {
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();
624                 }
625             }
626         } else {
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
630                     : lp.width);
631             item.measure(wspec, hspec);
632
633             heights.item = item.getMeasuredHeight();
634             if (isHeadFoot) {
635                 heights.child = heights.item;
636             } else if (position == mSrcPos) {
637                 heights.child = 0;
638             } else {
639                 heights.child = ((ViewGroup)item).getChildAt(0).getMeasuredHeight();
640             }
641         }
642     }
643
644     /**
645      * Get the height of the given wrapped item and its child.
646      * 
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.
651      */
652     private void getItemHeights(final int position, final View item, final ItemHeights heights) {
653         final boolean isHeadFoot = position < getHeaderViewsCount()
654                 || position >= getCount() - getFooterViewsCount();
655
656         heights.item = item.getHeight();
657
658         if (isHeadFoot) {
659             heights.child = heights.item;
660         } else if (position == mSrcPos) {
661             heights.child = 0;
662         } else {
663             heights.child = ((ViewGroup)item).getChildAt(0).getHeight();
664         }
665     }
666
667     /**
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.
671      * 
672      * @param position ListView position.
673      * @param heights Object to fill with heights of item at
674      *            <code>position</code>.
675      */
676     private void getItemHeights(final int position, final ItemHeights heights) {
677
678         final int first = getFirstVisiblePosition();
679         final int last = getLastVisiblePosition();
680
681         if (position >= first && position <= last) {
682             getItemHeights(position, getChildAt(position - first), heights);
683         } else {
684             // Log.d("mobeta", "getView for height");
685
686             final ListAdapter adapter = getAdapter();
687             final int type = adapter.getItemViewType(position);
688
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];
693             }
694
695             View v;
696             if (type >= 0) {
697                 if (mSampleViewTypes[type] == null) {
698                     v = adapter.getView(position, null, this);
699                     mSampleViewTypes[type] = v;
700                 } else {
701                     v = adapter.getView(position, mSampleViewTypes[type], this);
702                 }
703             } else {
704                 // type is HEADER_OR_FOOTER or IGNORE
705                 v = adapter.getView(position, null, this);
706             }
707
708             measureItemAndGetHeights(position, v, heights);
709         }
710
711     }
712
713     private int getShuffleEdge(final int position, final int top) {
714         return getShuffleEdge(position, top, null);
715     }
716
717     /**
718      * Get the shuffle edge for item at position when top of item is at y-coord
719      * top
720      * 
721      * @param position
722      * @param top
723      * @param height Height of item at position. If -1, this function calculates
724      *            this height.
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
730      *         position.
731      */
732     private int getShuffleEdge(final int position, final int top, ItemHeights heights) {
733
734         final int numHeaders = getHeaderViewsCount();
735         final int numFooters = getFooterViewsCount();
736
737         // shuffle edges are defined between items that can be
738         // dragged; there are N-1 of them if there are N draggable
739         // items.
740
741         if (position <= numHeaders || position >= getCount() - numFooters) {
742             return top;
743         }
744
745         final int divHeight = getDividerHeight();
746
747         int edge;
748
749         final int maxBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
750
751         if (heights == null) {
752             heights = new ItemHeights();
753             getItemHeights(position, heights);
754         }
755
756         // first calculate top of item given that floating View is
757         // centered over src position
758         int otop = top;
759         if (mSecondExpPos <= mSrcPos) {
760             // items are expanded on and/or above the source position
761
762             if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
763                 if (position == mSrcPos) {
764                     otop = top + heights.item - mFloatViewHeight;
765                 } else {
766                     final int blankHeight = heights.item - heights.child;
767                     otop = top + blankHeight - maxBlankHeight;
768                 }
769             } else if (position > mSecondExpPos && position <= mSrcPos) {
770                 otop = top - maxBlankHeight;
771             }
772
773         } else {
774             // items are expanded on and/or below the source position
775
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;
781             }
782         }
783
784         // otop is set
785         if (position <= mSrcPos) {
786             final ItemHeights tmpHeights = new ItemHeights();
787             getItemHeights(position - 1, tmpHeights);
788             edge = otop + (mFloatViewHeight - divHeight - tmpHeights.child) / 2;
789         } else {
790             edge = otop + (heights.child - divHeight - mFloatViewHeight) / 2;
791         }
792
793         return edge;
794     }
795
796     private boolean updatePositions() {
797
798         final int first = getFirstVisiblePosition();
799         int startPos = mFirstExpPos;
800         View startView = getChildAt(startPos - first);
801
802         if (startView == null) {
803             startPos = first + getChildCount() / 2;
804             startView = getChildAt(startPos - first);
805         }
806         final int startTop = startView.getTop() + mScrollY;
807
808         final ItemHeights itemHeights = new ItemHeights();
809         getItemHeights(startPos, startView, itemHeights);
810
811         int edge = getShuffleEdge(startPos, startTop, itemHeights);
812         int lastEdge = edge;
813
814         final int divHeight = getDividerHeight();
815
816         // Log.d("mobeta", "float mid="+mFloatViewMid);
817
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) {
824                 itemPos--;
825                 getItemHeights(itemPos, itemHeights);
826
827                 // if (itemPos <= 0)
828                 if (itemPos == 0) {
829                     edge = itemTop - divHeight - itemHeights.item;
830                     // itemPos = 0;
831                     break;
832                 }
833
834                 itemTop -= itemHeights.item + divHeight;
835                 edge = getShuffleEdge(itemPos, itemTop, itemHeights);
836                 // Log.d("mobeta", "    edge="+edge);
837
838                 if (mFloatViewMid >= edge) {
839                     break;
840                 }
841
842                 lastEdge = edge;
843             }
844         } else {
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;
851                     break;
852                 }
853
854                 itemTop += divHeight + itemHeights.item;
855                 getItemHeights(itemPos + 1, itemHeights);
856                 edge = getShuffleEdge(itemPos + 1, itemTop, itemHeights);
857                 // Log.d("mobeta", "    edge="+edge);
858
859                 // test for hit
860                 if (mFloatViewMid < edge) {
861                     break;
862                 }
863
864                 lastEdge = edge;
865                 itemPos++;
866             }
867         }
868
869         final int numHeaders = getHeaderViewsCount();
870         final int numFooters = getFooterViewsCount();
871
872         boolean updated = false;
873
874         final int oldFirstExpPos = mFirstExpPos;
875         final int oldSecondExpPos = mSecondExpPos;
876         final float oldSlideFrac = mSlideFrac;
877
878         if (mAnimate) {
879             final int edgeToEdge = Math.abs(edge - lastEdge);
880
881             int edgeTop, edgeBottom;
882             if (mFloatViewMid < edge) {
883                 edgeBottom = edge;
884                 edgeTop = lastEdge;
885             } else {
886                 edgeTop = edge;
887                 edgeBottom = lastEdge;
888             }
889             // Log.d("mobeta", "edgeTop="+edgeTop+" edgeBot="+edgeBottom);
890
891             final int slideRgnHeight = (int)(0.5f * mSlideRegionFrac * edgeToEdge);
892             final float slideRgnHeightF = slideRgnHeight;
893             final int slideEdgeTop = edgeTop + slideRgnHeight;
894             final int slideEdgeBottom = edgeBottom - slideRgnHeight;
895
896             // Three regions
897             if (mFloatViewMid < slideEdgeTop) {
898                 mFirstExpPos = itemPos - 1;
899                 mSecondExpPos = itemPos;
900                 mSlideFrac = 0.5f * (slideEdgeTop - mFloatViewMid) / slideRgnHeightF;
901                 // Log.d("mobeta",
902                 // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
903             } else if (mFloatViewMid < slideEdgeBottom) {
904                 mFirstExpPos = itemPos;
905                 mSecondExpPos = itemPos;
906             } else {
907                 mFirstExpPos = itemPos;
908                 mSecondExpPos = itemPos + 1;
909                 mSlideFrac = 0.5f * (1.0f + (edgeBottom - mFloatViewMid) / slideRgnHeightF);
910                 // Log.d("mobeta",
911                 // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
912             }
913
914         } else {
915             mFirstExpPos = itemPos;
916             mSecondExpPos = itemPos;
917         }
918
919         // correct for headers and footers
920         if (mFirstExpPos < numHeaders) {
921             itemPos = numHeaders;
922             mFirstExpPos = itemPos;
923             mSecondExpPos = itemPos;
924         } else if (mSecondExpPos >= getCount() - numFooters) {
925             itemPos = getCount() - numFooters - 1;
926             mFirstExpPos = itemPos;
927             mSecondExpPos = itemPos;
928         }
929
930         if (mFirstExpPos != oldFirstExpPos || mSecondExpPos != oldSecondExpPos
931                 || mSlideFrac != oldSlideFrac) {
932             updated = true;
933         }
934
935         if (itemPos != mFloatPos) {
936             if (mDragListener != null) {
937                 mDragListener.drag(mFloatPos - numHeaders, itemPos - numHeaders);
938             }
939
940             mFloatPos = itemPos;
941             updated = true;
942         }
943
944         return updated;
945     }
946
947     /**
948      * {@inheritDoc}
949      */
950     @Override
951     protected void onDraw(final Canvas canvas) {
952         super.onDraw(canvas);
953
954         if (mTrackDragSort) {
955             mDragSortTracker.appendState();
956         }
957     }
958
959     /**
960      * Stop a drag in progress. Pass <code>true</code> if you would like to
961      * remove the dragged item from the list.
962      * 
963      * @param remove Remove the dragged item from the list. Calls a registered
964      *            DropListener, if one exists.
965      * @return True if the stop was successful.
966      */
967     public boolean stopDrag(final boolean remove) {
968         if (mFloatView != null) {
969             mDragState = STOPPED;
970
971             // stop the drag
972             dropFloatView(remove);
973
974             return true;
975         } else {
976             // stop failed
977             return false;
978         }
979     }
980
981     /**
982      * {@inheritDoc}
983      */
984     @Override
985     public boolean onTouchEvent(final MotionEvent ev) {
986
987         if (!mDragEnabled) {
988             return super.onTouchEvent(ev);
989         }
990
991         boolean more = false;
992
993         final boolean lastCallWasIntercept = mLastCallWasIntercept;
994         mLastCallWasIntercept = false;
995
996         if (!lastCallWasIntercept) {
997             saveTouchCoords(ev);
998         }
999
1000         if (mFloatView != null) {
1001             onDragTouchEvent(ev);
1002             more = true; // give us more!
1003         } else {
1004             // what if float view is null b/c we dropped in middle
1005             // of drag touch event?
1006
1007             if (mDragState != STOPPED) {
1008                 if (super.onTouchEvent(ev)) {
1009                     more = true;
1010                 }
1011             }
1012
1013             final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1014             switch (action) {
1015                 case MotionEvent.ACTION_CANCEL:
1016                 case MotionEvent.ACTION_UP:
1017                     doActionUpOrCancel();
1018                     break;
1019                 default:
1020                     if (more) {
1021                         mCancelMethod = ON_TOUCH_EVENT;
1022                     }
1023             }
1024         }
1025
1026         return more;
1027
1028     }
1029
1030     private void doActionUpOrCancel() {
1031         mCancelMethod = NO_CANCEL;
1032         mInTouchEvent = false;
1033         mDragState = IDLE;
1034         mCurrFloatAlpha = mFloatAlpha;
1035     }
1036
1037     private void saveTouchCoords(final MotionEvent ev) {
1038         final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1039         if (action != MotionEvent.ACTION_DOWN) {
1040             mLastY = mY;
1041         }
1042         mX = (int)ev.getX();
1043         mY = (int)ev.getY();
1044         if (action == MotionEvent.ACTION_DOWN) {
1045             mLastY = mY;
1046         }
1047     }
1048
1049     /**
1050      * {@inheritDoc}
1051      */
1052     @Override
1053     public boolean onInterceptTouchEvent(final MotionEvent ev) {
1054         if (!mDragEnabled) {
1055             return super.onInterceptTouchEvent(ev);
1056         }
1057
1058         saveTouchCoords(ev);
1059         mLastCallWasIntercept = true;
1060
1061         boolean intercept = false;
1062
1063         final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1064
1065         if (action == MotionEvent.ACTION_DOWN) {
1066             mInTouchEvent = true;
1067         }
1068
1069         // the following deals with calls to super.onInterceptTouchEvent
1070         if (mFloatView != null) {
1071             // super's touch event canceled in startDrag
1072             intercept = true;
1073         } else {
1074             if (super.onInterceptTouchEvent(ev)) {
1075                 intercept = true;
1076             }
1077
1078             switch (action) {
1079                 case MotionEvent.ACTION_CANCEL:
1080                 case MotionEvent.ACTION_UP:
1081                     doActionUpOrCancel();
1082                     break;
1083                 default:
1084                     if (intercept) {
1085                         mCancelMethod = ON_TOUCH_EVENT;
1086                     } else {
1087                         mCancelMethod = ON_INTERCEPT_TOUCH_EVENT;
1088                     }
1089             }
1090         }
1091
1092         // check for startDragging
1093
1094         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
1095             mInTouchEvent = false;
1096         }
1097
1098         return intercept;
1099     }
1100
1101     /**
1102      * Set the width of each drag scroll region by specifying a fraction of the
1103      * ListView height.
1104      * 
1105      * @param heightFraction Fraction of ListView height. Capped at 0.5f.
1106      */
1107     public void setDragScrollStart(final float heightFraction) {
1108         setDragScrollStarts(heightFraction, heightFraction);
1109     }
1110
1111     /**
1112      * Set the width of each drag scroll region by specifying a fraction of the
1113      * ListView height.
1114      * 
1115      * @param upperFrac Fraction of ListView height for up-scroll bound. Capped
1116      *            at 0.5f.
1117      * @param lowerFrac Fraction of ListView height for down-scroll bound.
1118      *            Capped at 0.5f.
1119      */
1120     public void setDragScrollStarts(final float upperFrac, final float lowerFrac) {
1121         if (lowerFrac > 0.5f) {
1122             mDragDownScrollStartFrac = 0.5f;
1123         } else {
1124             mDragDownScrollStartFrac = lowerFrac;
1125         }
1126
1127         if (upperFrac > 0.5f) {
1128             mDragUpScrollStartFrac = 0.5f;
1129         } else {
1130             mDragUpScrollStartFrac = upperFrac;
1131         }
1132
1133         if (getHeight() != 0) {
1134             updateScrollStarts();
1135         }
1136     }
1137
1138     private void continueDrag(final int x, final int y) {
1139
1140         // Log.d("mobeta", "move");
1141         dragView(x, y);
1142
1143         // if (mTrackDragSort) {
1144         // mDragSortTracker.appendState();
1145         // }
1146
1147         requestLayout();
1148
1149         final int minY = Math.min(y, mFloatViewMid + mFloatViewHeightHalf);
1150         final int maxY = Math.max(y, mFloatViewMid - mFloatViewHeightHalf);
1151
1152         // get the current scroll direction
1153         final int currentScrollDir = mDragScroller.getScrollDir();
1154
1155         if (minY > mLastY && minY > mDownScrollStartY && currentScrollDir != DragScroller.DOWN) {
1156             // dragged down, it is below the down scroll start and it is not
1157             // scrolling up
1158
1159             if (currentScrollDir != DragScroller.STOP) {
1160                 // moved directly from up scroll to down scroll
1161                 mDragScroller.stopScrolling(true);
1162             }
1163
1164             // start scrolling down
1165             mDragScroller.startScrolling(DragScroller.DOWN);
1166         } else if (maxY < mLastY && maxY < mUpScrollStartY && currentScrollDir != DragScroller.UP) {
1167             // dragged up, it is above the up scroll start and it is not
1168             // scrolling up
1169
1170             if (currentScrollDir != DragScroller.STOP) {
1171                 // moved directly from down scroll to up scroll
1172                 mDragScroller.stopScrolling(true);
1173             }
1174
1175             // start scrolling up
1176             mDragScroller.startScrolling(DragScroller.UP);
1177         } else if (maxY >= mUpScrollStartY && minY <= mDownScrollStartY
1178                 && mDragScroller.isScrolling()) {
1179             // not in the upper nor in the lower drag-scroll regions but it is
1180             // still scrolling
1181
1182             mDragScroller.stopScrolling(true);
1183         }
1184     }
1185
1186     private void updateScrollStarts() {
1187         final int padTop = getPaddingTop();
1188         final int listHeight = getHeight() - padTop - getPaddingBottom();
1189         final float heightF = listHeight;
1190
1191         mUpScrollStartYF = padTop + mDragUpScrollStartFrac * heightF;
1192         mDownScrollStartYF = padTop + (1.0f - mDragDownScrollStartFrac) * heightF;
1193
1194         mUpScrollStartY = (int)mUpScrollStartYF;
1195         mDownScrollStartY = (int)mDownScrollStartYF;
1196
1197         mDragUpScrollHeight = mUpScrollStartYF - padTop;
1198         mDragDownScrollHeight = padTop + listHeight - mDownScrollStartYF;
1199     }
1200
1201     /**
1202      * {@inheritDoc}
1203      */
1204     @Override
1205     protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
1206         super.onSizeChanged(w, h, oldw, oldh);
1207         updateScrollStarts();
1208     }
1209
1210     private void dropFloatView(final boolean removeSrcItem) {
1211
1212         mDragScroller.stopScrolling(true);
1213
1214         if (removeSrcItem) {
1215             if (mRemoveListener != null) {
1216                 mRemoveListener.remove(mSrcPos - getHeaderViewsCount());
1217             }
1218         } else {
1219             if (mDropListener != null && mFloatPos >= 0 && mFloatPos < getCount()) {
1220                 final int numHeaders = getHeaderViewsCount();
1221                 mDropListener.drop(mSrcPos - numHeaders, mFloatPos - numHeaders);
1222             }
1223
1224             // adjustAllItems();
1225
1226             final int firstPos = getFirstVisiblePosition();
1227             if (mSrcPos < firstPos) {
1228                 // collapsed src item is off screen;
1229                 // adjust the scroll after item heights have been fixed
1230                 final View v = getChildAt(0);
1231                 int top = 0;
1232                 if (v != null) {
1233                     top = v.getTop();
1234                 }
1235                 // Log.d("mobeta", "top="+top+" fvh="+mFloatViewHeight);
1236                 setSelectionFromTop(firstPos - 1, top - getPaddingTop());
1237             }
1238         }
1239
1240         mSrcPos = -1;
1241         mFirstExpPos = -1;
1242         mSecondExpPos = -1;
1243         mFloatPos = -1;
1244
1245         removeFloatView();
1246
1247         if (mTrackDragSort) {
1248             mDragSortTracker.stopTracking();
1249         }
1250     }
1251
1252     private void adjustAllItems() {
1253         final int first = getFirstVisiblePosition();
1254         final int last = getLastVisiblePosition();
1255
1256         final int begin = Math.max(0, getHeaderViewsCount() - first);
1257         final int end = Math.min(last - first, getCount() - 1 - getFooterViewsCount() - first);
1258
1259         for (int i = begin; i <= end; ++i) {
1260             final View v = getChildAt(i);
1261             if (v != null) {
1262                 adjustItem(first + i, v, false);
1263             }
1264         }
1265     }
1266
1267     private void adjustItem(final int position, final View v, final boolean needsMeasure) {
1268
1269         final ViewGroup.LayoutParams lp = v.getLayoutParams();
1270         final int oldHeight = lp.height;
1271         int height = oldHeight;
1272
1273         getDividerHeight();
1274
1275         final boolean isSliding = mAnimate && mFirstExpPos != mSecondExpPos;
1276         final int maxNonSrcBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
1277         final int slideHeight = (int)(mSlideFrac * maxNonSrcBlankHeight);
1278
1279         if (position == mSrcPos) {
1280             if (mSrcPos == mFirstExpPos) {
1281                 if (isSliding) {
1282                     height = slideHeight + mItemHeightCollapsed;
1283                 } else {
1284                     height = mFloatViewHeight;
1285                 }
1286             } else if (mSrcPos == mSecondExpPos) {
1287                 // if gets here, we know an item is sliding
1288                 height = mFloatViewHeight - slideHeight;
1289             } else {
1290                 height = mItemHeightCollapsed;
1291             }
1292         } else if (position == mFirstExpPos || position == mSecondExpPos) {
1293             // position is not src
1294
1295             final ItemHeights itemHeights = new ItemHeights();
1296             if (needsMeasure) {
1297                 measureItemAndGetHeights(position, v, itemHeights);
1298             } else {
1299                 getItemHeights(position, v, itemHeights);
1300             }
1301
1302             if (position == mFirstExpPos) {
1303                 if (isSliding) {
1304                     height = itemHeights.child + slideHeight;
1305                 } else {
1306                     height = itemHeights.child + maxNonSrcBlankHeight;
1307                 }
1308             } else { // position=mSecondExpPos
1309                 // we know an item is sliding (b/c 2ndPos != 1stPos)
1310                 height = itemHeights.child + maxNonSrcBlankHeight - slideHeight;
1311             }
1312         } else {
1313             height = ViewGroup.LayoutParams.WRAP_CONTENT;
1314         }
1315
1316         if (height != oldHeight) {
1317             lp.height = height;
1318
1319             v.setLayoutParams(lp);
1320         }
1321
1322         // Adjust item gravity
1323
1324         if (position == mFirstExpPos || position == mSecondExpPos) {
1325             if (position < mSrcPos) {
1326                 ((RelativeLayout)v).setGravity(Gravity.BOTTOM);
1327             } else if (position > mSrcPos) {
1328                 ((RelativeLayout)v).setGravity(Gravity.TOP);
1329             }
1330         }
1331
1332         // Finally adjust item visibility
1333
1334         final int oldVis = v.getVisibility();
1335         int vis = View.VISIBLE;
1336
1337         if (position == mSrcPos && mFloatView != null) {
1338             vis = View.INVISIBLE;
1339         }
1340
1341         if (vis != oldVis) {
1342             v.setVisibility(vis);
1343         }
1344     }
1345
1346     /**
1347      * {@inheritDoc}
1348      */
1349     @Override
1350     public void requestLayout() {
1351         if (!mBlockLayoutRequests) {
1352             super.requestLayout();
1353         }
1354     }
1355
1356     private void doDragScroll(final int oldFirstExpPos, final int oldSecondExpPos) {
1357         if (mScrollY == 0) {
1358             return;
1359         }
1360
1361         final int padTop = getPaddingTop();
1362         final int listHeight = getHeight() - padTop - getPaddingBottom();
1363         final int first = getFirstVisiblePosition();
1364         final int last = getLastVisiblePosition();
1365
1366         int movePos;
1367
1368         if (mScrollY >= 0) {
1369             mScrollY = Math.min(listHeight, mScrollY);
1370             movePos = first;
1371         } else {
1372             mScrollY = Math.max(-listHeight, mScrollY);
1373             movePos = last;
1374         }
1375
1376         final View moveItem = getChildAt(movePos - first);
1377         int top = moveItem.getTop() + mScrollY;
1378
1379         if (movePos == 0 && top > padTop) {
1380             top = padTop;
1381         }
1382
1383         final ItemHeights itemHeightsBefore = new ItemHeights();
1384         getItemHeights(movePos, moveItem, itemHeightsBefore);
1385         final int moveHeightBefore = itemHeightsBefore.item;
1386         final int moveBlankBefore = moveHeightBefore - itemHeightsBefore.child;
1387
1388         final ItemHeights itemHeightsAfter = new ItemHeights();
1389         measureItemAndGetHeights(movePos, moveItem, itemHeightsAfter);
1390         final int moveHeightAfter = itemHeightsAfter.item;
1391         final int moveBlankAfter = moveHeightAfter - itemHeightsAfter.child;
1392
1393         if (movePos <= oldFirstExpPos) {
1394             if (movePos > mFirstExpPos) {
1395                 top += mFloatViewHeight - moveBlankAfter;
1396             }
1397         } else if (movePos == oldSecondExpPos) {
1398             if (movePos <= mFirstExpPos) {
1399                 top += moveBlankBefore - mFloatViewHeight;
1400             } else if (movePos == mSecondExpPos) {
1401                 top += moveHeightBefore - moveHeightAfter;
1402             } else {
1403                 top += moveBlankBefore;
1404             }
1405         } else {
1406             if (movePos <= mFirstExpPos) {
1407                 top -= mFloatViewHeight;
1408             } else if (movePos == mSecondExpPos) {
1409                 top -= moveBlankAfter;
1410             }
1411         }
1412
1413         setSelectionFromTop(movePos, top - padTop);
1414
1415         mScrollY = 0;
1416     }
1417
1418     private void measureFloatView() {
1419         if (mFloatView != null) {
1420             ViewGroup.LayoutParams lp = mFloatView.getLayoutParams();
1421             if (lp == null) {
1422                 lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1423                         ViewGroup.LayoutParams.WRAP_CONTENT);
1424             }
1425             final int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft()
1426                     + getListPaddingRight(), lp.width);
1427             int hspec;
1428             if (lp.height > 0) {
1429                 hspec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
1430             } else {
1431                 hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1432             }
1433             mFloatView.measure(wspec, hspec);
1434             mFloatViewHeight = mFloatView.getMeasuredHeight();
1435             mFloatViewHeightHalf = mFloatViewHeight / 2;
1436         }
1437     }
1438
1439     /**
1440      * {@inheritDoc}
1441      */
1442     @Override
1443     protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
1444         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1445         if (mFloatView != null) {
1446             if (mFloatView.isLayoutRequested()) {
1447                 measureFloatView();
1448             }
1449         }
1450         mWidthMeasureSpec = widthMeasureSpec;
1451         mDragScroller.setListHeight(getHeight());
1452     }
1453
1454     /**
1455      * {@inheritDoc}
1456      */
1457     @Override
1458     protected void layoutChildren() {
1459
1460         if (mFloatView != null) {
1461             mFloatView.layout(0, 0, mFloatView.getMeasuredWidth(), mFloatView.getMeasuredHeight());
1462
1463             // Log.d("mobeta", "layout children");
1464             final int oldFirstExpPos = mFirstExpPos;
1465             final int oldSecondExpPos = mSecondExpPos;
1466
1467             mBlockLayoutRequests = true;
1468
1469             if (getChildCount() > 0 && updatePositions()) {
1470                 adjustAllItems();
1471             }
1472
1473             if (mScrollY != 0) {
1474                 doDragScroll(oldFirstExpPos, oldSecondExpPos);
1475             }
1476
1477             mBlockLayoutRequests = false;
1478         }
1479
1480         super.layoutChildren();
1481     }
1482
1483     protected boolean onDragTouchEvent(final MotionEvent ev) {
1484         switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1485             case MotionEvent.ACTION_CANCEL:
1486             case MotionEvent.ACTION_UP:
1487                 stopDrag(false);
1488                 doActionUpOrCancel();
1489                 break;
1490             case MotionEvent.ACTION_MOVE:
1491                 continueDrag((int)ev.getX(), (int)ev.getY());
1492                 break;
1493         }
1494
1495         return true;
1496     }
1497
1498     /**
1499      * Start a drag of item at <code>position</code> using the registered
1500      * FloatViewManager. Calls through to
1501      * {@link #startDrag(int,View,int,int,int)} after obtaining the floating
1502      * View from the FloatViewManager.
1503      * 
1504      * @param position Item to drag.
1505      * @param dragFlags Flags that restrict some movements of the floating View.
1506      *            For example, set <code>dragFlags |= 
1507      * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating View in all
1508      *            directions except off the screen to the left.
1509      * @param deltaX Offset in x of the touch coordinate from the left edge of
1510      *            the floating View (i.e. touch-x minus float View left).
1511      * @param deltaY Offset in y of the touch coordinate from the top edge of
1512      *            the floating View (i.e. touch-y minus float View top).
1513      * @return True if the drag was started, false otherwise. This
1514      *         <code>startDrag</code> will fail if we are not currently in a
1515      *         touch event, there is no registered FloatViewManager, or the
1516      *         FloatViewManager returns a null View.
1517      */
1518     public boolean startDrag(final int position, final int dragFlags, final int deltaX,
1519             final int deltaY) {
1520         if (!mInTouchEvent || mFloatViewManager == null) {
1521             return false;
1522         }
1523
1524         final View v = mFloatViewManager.onCreateFloatView(position);
1525
1526         if (v == null) {
1527             return false;
1528         } else {
1529             return startDrag(position, v, dragFlags, deltaX, deltaY);
1530         }
1531
1532     }
1533
1534     /**
1535      * Start a drag of item at <code>position</code> without using a
1536      * FloatViewManager.
1537      * 
1538      * @param position Item to drag.
1539      * @param floatView Floating View.
1540      * @param dragFlags Flags that restrict some movements of the floating View.
1541      *            For example, set <code>dragFlags |= 
1542      * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating View in all
1543      *            directions except off the screen to the left.
1544      * @param deltaX Offset in x of the touch coordinate from the left edge of
1545      *            the floating View (i.e. touch-x minus float View left).
1546      * @param deltaY Offset in y of the touch coordinate from the top edge of
1547      *            the floating View (i.e. touch-y minus float View top).
1548      * @return True if the drag was started, false otherwise. This
1549      *         <code>startDrag</code> will fail if we are not currently in a
1550      *         touch event, <code>floatView</code> is null, or there is a drag
1551      *         in progress.
1552      */
1553     public boolean startDrag(final int position, final View floatView, final int dragFlags,
1554             final int deltaX, final int deltaY) {
1555         if (!mInTouchEvent || mFloatView != null || floatView == null) {
1556             return false;
1557         }
1558
1559         if (getParent() != null) {
1560             getParent().requestDisallowInterceptTouchEvent(true);
1561         }
1562
1563         final int pos = position + getHeaderViewsCount();
1564         mFirstExpPos = pos;
1565         mSecondExpPos = pos;
1566         mSrcPos = pos;
1567         mFloatPos = pos;
1568
1569         // mDragState = dragType;
1570         mDragState = DRAGGING;
1571         mDragFlags = 0;
1572         mDragFlags |= dragFlags;
1573
1574         mFloatView = floatView;
1575         measureFloatView(); // sets mFloatViewHeight
1576
1577         mDragDeltaX = deltaX;
1578         mDragDeltaY = deltaY;
1579         updateFloatView(mX - mDragDeltaX, mY - mDragDeltaY);
1580
1581         // set src item invisible
1582         final View srcItem = getChildAt(mSrcPos - getFirstVisiblePosition());
1583         if (srcItem != null) {
1584             srcItem.setVisibility(View.INVISIBLE);
1585         }
1586
1587         if (mTrackDragSort) {
1588             mDragSortTracker.startTracking();
1589         }
1590
1591         // once float view is created, events are no longer passed
1592         // to ListView
1593         switch (mCancelMethod) {
1594             case ON_TOUCH_EVENT:
1595                 super.onTouchEvent(mCancelEvent);
1596                 break;
1597             case ON_INTERCEPT_TOUCH_EVENT:
1598                 super.onInterceptTouchEvent(mCancelEvent);
1599                 break;
1600         }
1601
1602         requestLayout();
1603
1604         return true;
1605     }
1606
1607     /**
1608      * Sets float View location based on suggested values and constraints set in
1609      * mDragFlags.
1610      */
1611     private void updateFloatView(final int floatX, final int floatY) {
1612
1613         // restrict x motion
1614         final int padLeft = getPaddingLeft();
1615         if ((mDragFlags & DRAG_POS_X) == 0 && floatX > padLeft) {
1616             mFloatViewLeft = padLeft;
1617         } else if ((mDragFlags & DRAG_NEG_X) == 0 && floatX < padLeft) {
1618             mFloatViewLeft = padLeft;
1619         } else {
1620             mFloatViewLeft = floatX;
1621         }
1622
1623         // keep floating view from going past bottom of last header view
1624         final int numHeaders = getHeaderViewsCount();
1625         final int numFooters = getFooterViewsCount();
1626         final int firstPos = getFirstVisiblePosition();
1627         final int lastPos = getLastVisiblePosition();
1628
1629         // Log.d("mobeta",
1630         // "nHead="+numHeaders+" nFoot="+numFooters+" first="+firstPos+" last="+lastPos);
1631         int topLimit = getPaddingTop();
1632         if (firstPos < numHeaders) {
1633             topLimit = getChildAt(numHeaders - firstPos - 1).getBottom();
1634         }
1635         if ((mDragFlags & DRAG_NEG_Y) == 0) {
1636             if (firstPos <= mSrcPos) {
1637                 topLimit = Math.max(getChildAt(mSrcPos - firstPos).getTop(), topLimit);
1638             }
1639         }
1640         // bottom limit is top of first footer View or
1641         // bottom of last item in list
1642         int bottomLimit = getHeight() - getPaddingBottom();
1643         if (lastPos >= getCount() - numFooters - 1) {
1644             bottomLimit = getChildAt(getCount() - numFooters - 1 - firstPos).getBottom();
1645         }
1646         if ((mDragFlags & DRAG_POS_Y) == 0) {
1647             if (lastPos >= mSrcPos) {
1648                 bottomLimit = Math.min(getChildAt(mSrcPos - firstPos).getBottom(), bottomLimit);
1649             }
1650         }
1651
1652         // Log.d("mobeta", "dragView top=" + (y - mDragDeltaY));
1653         // Log.d("mobeta", "limit=" + limit);
1654         // Log.d("mobeta", "mDragDeltaY=" + mDragDeltaY);
1655
1656         if (floatY < topLimit) {
1657             mFloatViewTop = topLimit;
1658         } else if (floatY + mFloatViewHeight > bottomLimit) {
1659             mFloatViewTop = bottomLimit - mFloatViewHeight;
1660         } else {
1661             mFloatViewTop = floatY;
1662         }
1663
1664         // get y-midpoint of floating view (constrained to ListView bounds)
1665         mFloatViewMid = mFloatViewTop + mFloatViewHeightHalf;
1666     }
1667
1668     private void dragView(final int x, final int y) {
1669         // Log.d("mobeta", "float view pure x=" + x + " y=" + y);
1670
1671         // proposed position
1672         mFloatLoc.x = x - mDragDeltaX;
1673         mFloatLoc.y = y - mDragDeltaY;
1674
1675         final Point touch = new Point(x, y);
1676
1677         // let manager adjust proposed position first
1678         if (mFloatViewManager != null) {
1679             mFloatViewManager.onDragFloatView(mFloatView, mFloatLoc, touch);
1680         }
1681
1682         // then we override if manager gives an unsatisfactory
1683         // position (e.g. over a header/footer view). Also,
1684         // dragFlags override manager adjustments.
1685         updateFloatView(mFloatLoc.x, mFloatLoc.y);
1686     }
1687
1688     private void removeFloatView() {
1689         if (mFloatView != null) {
1690             mFloatView.setVisibility(GONE);
1691             if (mFloatViewManager != null) {
1692                 mFloatViewManager.onDestroyFloatView(mFloatView);
1693             }
1694             mFloatView = null;
1695         }
1696     }
1697
1698     /**
1699      * Interface for customization of the floating View appearance and dragging
1700      * behavior. Implement your own and pass it to {@link #setFloatViewManager}.
1701      * If your own is not passed, the default {@link SimpleFloatViewManager}
1702      * implementation is used.
1703      */
1704     public interface FloatViewManager {
1705         /**
1706          * Return the floating View for item at <code>position</code>.
1707          * DragSortListView will measure and layout this View for you, so feel
1708          * free to just inflate it. You can help DSLV by setting some
1709          * {@link ViewGroup.LayoutParams} on this View; otherwise it will set
1710          * some for you (with a width of FILL_PARENT and a height of
1711          * WRAP_CONTENT).
1712          * 
1713          * @param position Position of item to drag (NOTE: <code>position</code>
1714          *            excludes header Views; thus, if you want to call
1715          *            {@link ListView#getChildAt(int)}, you will need to add
1716          *            {@link ListView#getHeaderViewsCount()} to the index).
1717          * @return The View you wish to display as the floating View.
1718          */
1719         public View onCreateFloatView(int position);
1720
1721         /**
1722          * Called whenever the floating View is dragged. Float View properties
1723          * can be changed here. Also, the upcoming location of the float View
1724          * can be altered by setting <code>location.x</code> and
1725          * <code>location.y</code>.
1726          * 
1727          * @param floatView The floating View.
1728          * @param location The location (top-left; relative to DSLV top-left) at
1729          *            which the float View would like to appear, given the
1730          *            current touch location and the offset provided in
1731          *            {@link DragSortListView#startDrag}.
1732          * @param touch The current touch location (relative to DSLV top-left).
1733          */
1734         public void onDragFloatView(View floatView, Point location, Point touch);
1735
1736         /**
1737          * Called when the float View is dropped; lets you perform any necessary
1738          * cleanup. The internal DSLV floating View reference is set to null
1739          * immediately after this is called.
1740          * 
1741          * @param floatView The floating View passed to
1742          *            {@link #onCreateFloatView(int)}.
1743          */
1744         public void onDestroyFloatView(View floatView);
1745     }
1746
1747     public void setFloatViewManager(final FloatViewManager manager) {
1748         mFloatViewManager = manager;
1749     }
1750
1751     public void setDragListener(final DragListener l) {
1752         mDragListener = l;
1753     }
1754
1755     /**
1756      * Allows for easy toggling between a DragSortListView and a regular old
1757      * ListView. If enabled, items are draggable, where the drag init mode
1758      * determines how items are lifted (see {@link setDragInitMode(int)}). If
1759      * disabled, items cannot be dragged.
1760      * 
1761      * @param enabled Set <code>true</code> to enable list item dragging
1762      */
1763     public void setDragEnabled(final boolean enabled) {
1764         mDragEnabled = enabled;
1765     }
1766
1767     public boolean isDragEnabled() {
1768         return mDragEnabled;
1769     }
1770
1771     /**
1772      * This better reorder your ListAdapter! DragSortListView does not do this
1773      * for you; doesn't make sense to. Make sure
1774      * {@link BaseAdapter#notifyDataSetChanged()} or something like it is called
1775      * in your implementation.
1776      * 
1777      * @param l
1778      */
1779     public void setDropListener(final DropListener l) {
1780         mDropListener = l;
1781     }
1782
1783     /**
1784      * Probably a no-brainer, but make sure that your remove listener calls
1785      * {@link BaseAdapter#notifyDataSetChanged()} or something like it. When an
1786      * item removal occurs, DragSortListView relies on a redraw of all the items
1787      * to recover invisible views and such. Strictly speaking, if you remove
1788      * something, your dataset has changed...
1789      * 
1790      * @param l
1791      */
1792     public void setRemoveListener(final RemoveListener l) {
1793         if (mController != null && l == null) {
1794             mController.setRemoveEnabled(false);
1795         }
1796         mRemoveListener = l;
1797     }
1798
1799     public interface DragListener {
1800         public void drag(int from, int to);
1801     }
1802
1803     /**
1804      * Your implementation of this has to reorder your ListAdapter! Make sure to
1805      * call {@link BaseAdapter#notifyDataSetChanged()} or something like it in
1806      * your implementation.
1807      * 
1808      * @author heycosmo
1809      */
1810     public interface DropListener {
1811         public void drop(int from, int to);
1812     }
1813
1814     /**
1815      * Make sure to call {@link BaseAdapter#notifyDataSetChanged()} or something
1816      * like it in your implementation.
1817      * 
1818      * @author heycosmo
1819      */
1820     public interface RemoveListener {
1821         public void remove(int which);
1822     }
1823
1824     public interface DragSortListener extends DropListener, DragListener, RemoveListener {
1825     }
1826
1827     public void setDragSortListener(final DragSortListener l) {
1828         setDropListener(l);
1829         setDragListener(l);
1830         setRemoveListener(l);
1831     }
1832
1833     /**
1834      * Completely custom scroll speed profile. Default increases linearly with
1835      * position and is constant in time. Create your own by implementing
1836      * {@link DragSortListView.DragScrollProfile}.
1837      * 
1838      * @param ssp
1839      */
1840     public void setDragScrollProfile(final DragScrollProfile ssp) {
1841         if (ssp != null) {
1842             mScrollProfile = ssp;
1843         }
1844     }
1845
1846     /**
1847      * Interface for controlling scroll speed as a function of touch position
1848      * and time. Use
1849      * {@link DragSortListView#setDragScrollProfile(DragScrollProfile)} to set
1850      * custom profile.
1851      * 
1852      * @author heycosmo
1853      */
1854     public interface DragScrollProfile {
1855         /**
1856          * Return a scroll speed in pixels/millisecond. Always return a positive
1857          * number.
1858          * 
1859          * @param w Normalized position in scroll region (i.e. w \in [0,1]).
1860          *            Small w typically means slow scrolling.
1861          * @param t Time (in milliseconds) since start of scroll (handy if you
1862          *            want scroll acceleration).
1863          * @return Scroll speed at position w and time t in pixels/ms.
1864          */
1865         float getSpeed(float w, long t);
1866     }
1867
1868     private class DragScroller implements Runnable, AbsListView.OnScrollListener {
1869
1870         private boolean mAbort;
1871
1872         private long mPrevTime;
1873
1874         private int dy;
1875
1876         private float dt;
1877
1878         private long tStart;
1879
1880         private int scrollDir;
1881
1882         public final static int STOP = -1;
1883
1884         public final static int UP = 0;
1885
1886         public final static int DOWN = 1;
1887
1888         private float mScrollSpeed; // pixels per ms
1889
1890         private boolean mScrolling = false;
1891
1892         private int mMaxScrollSpeed;
1893
1894         public boolean isScrolling() {
1895             return mScrolling;
1896         }
1897
1898         public int getScrollDir() {
1899             return mScrolling ? scrollDir : STOP;
1900         }
1901
1902         public DragScroller() {
1903         }
1904
1905         public void startScrolling(final int dir) {
1906             if (!mScrolling) {
1907                 // Debug.startMethodTracing("dslv-scroll");
1908                 mAbort = false;
1909                 mScrolling = true;
1910                 tStart = SystemClock.uptimeMillis();
1911                 mPrevTime = tStart;
1912                 scrollDir = dir;
1913                 post(this);
1914             }
1915         }
1916
1917         public void stopScrolling(final boolean now) {
1918             if (now) {
1919                 removeCallbacks(this);
1920                 mScrolling = false;
1921             } else {
1922                 mAbort = true;
1923             }
1924
1925         }
1926
1927         public void setListHeight(final int height) {
1928             // cap the max scroll speed per frame to be 1/5 of the list height
1929             mMaxScrollSpeed = height / 5;
1930         }
1931
1932         /**
1933          * {@inheritDoc}
1934          */
1935         @Override
1936         public void run() {
1937             if (mAbort) {
1938                 mScrolling = false;
1939                 return;
1940             }
1941
1942             final int first = getFirstVisiblePosition();
1943             final int last = getLastVisiblePosition();
1944             final int count = getCount();
1945             final int padTop = getPaddingTop();
1946             final int listHeight = getHeight() - padTop - getPaddingBottom();
1947
1948             final int minY = Math.min(mY, mFloatViewMid + mFloatViewHeightHalf);
1949             final int maxY = Math.max(mY, mFloatViewMid - mFloatViewHeightHalf);
1950
1951             if (scrollDir == UP) {
1952                 final View v = getChildAt(0);
1953                 if (v == null) {
1954                     mScrolling = false;
1955                     return;
1956                 } else {
1957                     if (first == 0 && v.getTop() == padTop) {
1958                         mScrolling = false;
1959                         return;
1960                     }
1961                 }
1962                 mScrollSpeed = mScrollProfile.getSpeed((mUpScrollStartYF - maxY)
1963                         / mDragUpScrollHeight, mPrevTime);
1964             } else {
1965                 final View v = getChildAt(last - first);
1966                 if (v == null) {
1967                     mScrolling = false;
1968                     return;
1969                 } else {
1970                     if (last == count - 1 && v.getBottom() <= listHeight + padTop) {
1971                         mScrolling = false;
1972                         return;
1973                     }
1974                 }
1975                 mScrollSpeed = -mScrollProfile.getSpeed((minY - mDownScrollStartYF)
1976                         / mDragDownScrollHeight, mPrevTime);
1977             }
1978
1979             dt = SystemClock.uptimeMillis() - mPrevTime;
1980             // dy is change in View position of a list item; i.e. positive dy
1981             // means user is scrolling up (list item moves down the screen,
1982             // remember
1983             // y=0 is at top of View).
1984             dy = Math.round(mScrollSpeed * dt);
1985             mScrollY += dy;
1986
1987             // cap the scroll speed
1988             mScrollY = Math.max(Math.min(mScrollY, mMaxScrollSpeed), -mMaxScrollSpeed);
1989
1990             requestLayout();
1991
1992             mPrevTime += dt;
1993
1994             post(this);
1995         }
1996
1997         /**
1998          * {@inheritDoc}
1999          */
2000         @Override
2001         public void onScroll(final AbsListView view, final int firstVisibleItem,
2002                 final int visibleItemCount, final int totalItemCount) {
2003             if (mScrolling && visibleItemCount != 0) {
2004                 dragView(mX, mY);
2005             }
2006         }
2007
2008         /**
2009          * {@inheritDoc}
2010          */
2011         @Override
2012         public void onScrollStateChanged(final AbsListView view, final int scrollState) {
2013         }
2014
2015     }
2016
2017     private class DragSortTracker {
2018         StringBuilder mBuilder = new StringBuilder();
2019
2020         File mFile;
2021
2022         private int mNumInBuffer = 0;
2023
2024         private int mNumFlushes = 0;
2025
2026         private boolean mTracking = false;
2027
2028         public void startTracking() {
2029             mBuilder.append("<DSLVStates>\n");
2030             mNumFlushes = 0;
2031             mTracking = true;
2032         }
2033
2034         public void appendState() {
2035             if (!mTracking) {
2036                 return;
2037             }
2038
2039             mBuilder.append("<DSLVState>\n");
2040             final int children = getChildCount();
2041             final int first = getFirstVisiblePosition();
2042             final ItemHeights itemHeights = new ItemHeights();
2043             mBuilder.append("    <Positions>");
2044             for (int i = 0; i < children; ++i) {
2045                 mBuilder.append(first + i).append(",");
2046             }
2047             mBuilder.append("</Positions>\n");
2048
2049             mBuilder.append("    <Tops>");
2050             for (int i = 0; i < children; ++i) {
2051                 mBuilder.append(getChildAt(i).getTop()).append(",");
2052             }
2053             mBuilder.append("</Tops>\n");
2054             mBuilder.append("    <Bottoms>");
2055             for (int i = 0; i < children; ++i) {
2056                 mBuilder.append(getChildAt(i).getBottom()).append(",");
2057             }
2058             mBuilder.append("</Bottoms>\n");
2059
2060             mBuilder.append("    <FirstExpPos>").append(mFirstExpPos).append("</FirstExpPos>\n");
2061             getItemHeights(mFirstExpPos, itemHeights);
2062             mBuilder.append("    <FirstExpBlankHeight>")
2063                     .append(itemHeights.item - itemHeights.child)
2064                     .append("</FirstExpBlankHeight>\n");
2065             mBuilder.append("    <SecondExpPos>").append(mSecondExpPos).append("</SecondExpPos>\n");
2066             getItemHeights(mSecondExpPos, itemHeights);
2067             mBuilder.append("    <SecondExpBlankHeight>")
2068                     .append(itemHeights.item - itemHeights.child)
2069                     .append("</SecondExpBlankHeight>\n");
2070             mBuilder.append("    <SrcPos>").append(mSrcPos).append("</SrcPos>\n");
2071             mBuilder.append("    <SrcHeight>").append(mFloatViewHeight + getDividerHeight())
2072                     .append("</SrcHeight>\n");
2073             mBuilder.append("    <ViewHeight>").append(getHeight()).append("</ViewHeight>\n");
2074             mBuilder.append("    <LastY>").append(mLastY).append("</LastY>\n");
2075             mBuilder.append("    <FloatY>").append(mFloatViewMid).append("</FloatY>\n");
2076             mBuilder.append("    <ShuffleEdges>");
2077             for (int i = 0; i < children; ++i) {
2078                 mBuilder.append(getShuffleEdge(first + i, getChildAt(i).getTop())).append(",");
2079             }
2080             mBuilder.append("</ShuffleEdges>\n");
2081
2082             mBuilder.append("</DSLVState>\n");
2083             mNumInBuffer++;
2084
2085             if (mNumInBuffer > 1000) {
2086                 flush();
2087                 mNumInBuffer = 0;
2088             }
2089         }
2090
2091         public void flush() {
2092             if (!mTracking) {
2093                 return;
2094             }
2095
2096             // save to file on sdcard
2097             try {
2098                 boolean append = true;
2099                 if (mNumFlushes == 0) {
2100                     append = false;
2101                 }
2102                 final FileWriter writer = new FileWriter(mFile, append);
2103
2104                 writer.write(mBuilder.toString());
2105                 mBuilder.delete(0, mBuilder.length());
2106
2107                 writer.flush();
2108                 writer.close();
2109
2110                 mNumFlushes++;
2111             } catch (final IOException e) {
2112                 // do nothing
2113             }
2114         }
2115
2116         public void stopTracking() {
2117             if (mTracking) {
2118                 mBuilder.append("</DSLVStates>\n");
2119                 flush();
2120                 mTracking = false;
2121             }
2122         }
2123
2124     }
2125
2126 }