OSDN Git Service

Eleven: Remove unnecessary local variables
[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 int slideEdgeTop = edgeTop + slideRgnHeight;
893             final int slideEdgeBottom = edgeBottom - slideRgnHeight;
894
895             // Three regions
896             if (mFloatViewMid < slideEdgeTop) {
897                 mFirstExpPos = itemPos - 1;
898                 mSecondExpPos = itemPos;
899                 mSlideFrac = 0.5f * (slideEdgeTop - mFloatViewMid) / (float) slideRgnHeight;
900                 // Log.d("mobeta",
901                 // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
902             } else if (mFloatViewMid < slideEdgeBottom) {
903                 mFirstExpPos = itemPos;
904                 mSecondExpPos = itemPos;
905             } else {
906                 mFirstExpPos = itemPos;
907                 mSecondExpPos = itemPos + 1;
908                 mSlideFrac = 0.5f * (1.0f + (edgeBottom - mFloatViewMid) / (float) slideRgnHeight);
909                 // Log.d("mobeta",
910                 // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
911             }
912
913         } else {
914             mFirstExpPos = itemPos;
915             mSecondExpPos = itemPos;
916         }
917
918         // correct for headers and footers
919         if (mFirstExpPos < numHeaders) {
920             itemPos = numHeaders;
921             mFirstExpPos = itemPos;
922             mSecondExpPos = itemPos;
923         } else if (mSecondExpPos >= getCount() - numFooters) {
924             itemPos = getCount() - numFooters - 1;
925             mFirstExpPos = itemPos;
926             mSecondExpPos = itemPos;
927         }
928
929         if (mFirstExpPos != oldFirstExpPos || mSecondExpPos != oldSecondExpPos
930                 || mSlideFrac != oldSlideFrac) {
931             updated = true;
932         }
933
934         if (itemPos != mFloatPos) {
935             if (mDragListener != null) {
936                 mDragListener.drag(mFloatPos - numHeaders, itemPos - numHeaders);
937             }
938
939             mFloatPos = itemPos;
940             updated = true;
941         }
942
943         return updated;
944     }
945
946     /**
947      * {@inheritDoc}
948      */
949     @Override
950     protected void onDraw(final Canvas canvas) {
951         super.onDraw(canvas);
952
953         if (mTrackDragSort) {
954             mDragSortTracker.appendState();
955         }
956     }
957
958     /**
959      * Stop a drag in progress. Pass <code>true</code> if you would like to
960      * remove the dragged item from the list.
961      * 
962      * @param remove Remove the dragged item from the list. Calls a registered
963      *            DropListener, if one exists.
964      * @return True if the stop was successful.
965      */
966     public boolean stopDrag(final boolean remove) {
967         if (mFloatView != null) {
968             mDragState = STOPPED;
969
970             // stop the drag
971             dropFloatView(remove);
972
973             return true;
974         } else {
975             // stop failed
976             return false;
977         }
978     }
979
980     /**
981      * {@inheritDoc}
982      */
983     @Override
984     public boolean onTouchEvent(final MotionEvent ev) {
985
986         if (!mDragEnabled) {
987             return super.onTouchEvent(ev);
988         }
989
990         boolean more = false;
991
992         final boolean lastCallWasIntercept = mLastCallWasIntercept;
993         mLastCallWasIntercept = false;
994
995         if (!lastCallWasIntercept) {
996             saveTouchCoords(ev);
997         }
998
999         if (mFloatView != null) {
1000             onDragTouchEvent(ev);
1001             more = true; // give us more!
1002         } else {
1003             // what if float view is null b/c we dropped in middle
1004             // of drag touch event?
1005
1006             if (mDragState != STOPPED) {
1007                 if (super.onTouchEvent(ev)) {
1008                     more = true;
1009                 }
1010             }
1011
1012             final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1013             switch (action) {
1014                 case MotionEvent.ACTION_CANCEL:
1015                 case MotionEvent.ACTION_UP:
1016                     doActionUpOrCancel();
1017                     break;
1018                 default:
1019                     if (more) {
1020                         mCancelMethod = ON_TOUCH_EVENT;
1021                     }
1022             }
1023         }
1024
1025         return more;
1026
1027     }
1028
1029     private void doActionUpOrCancel() {
1030         mCancelMethod = NO_CANCEL;
1031         mInTouchEvent = false;
1032         mDragState = IDLE;
1033         mCurrFloatAlpha = mFloatAlpha;
1034     }
1035
1036     private void saveTouchCoords(final MotionEvent ev) {
1037         final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1038         if (action != MotionEvent.ACTION_DOWN) {
1039             mLastY = mY;
1040         }
1041         mX = (int)ev.getX();
1042         mY = (int)ev.getY();
1043         if (action == MotionEvent.ACTION_DOWN) {
1044             mLastY = mY;
1045         }
1046     }
1047
1048     /**
1049      * {@inheritDoc}
1050      */
1051     @Override
1052     public boolean onInterceptTouchEvent(final MotionEvent ev) {
1053         if (!mDragEnabled) {
1054             return super.onInterceptTouchEvent(ev);
1055         }
1056
1057         saveTouchCoords(ev);
1058         mLastCallWasIntercept = true;
1059
1060         boolean intercept = false;
1061
1062         final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1063
1064         if (action == MotionEvent.ACTION_DOWN) {
1065             mInTouchEvent = true;
1066         }
1067
1068         // the following deals with calls to super.onInterceptTouchEvent
1069         if (mFloatView != null) {
1070             // super's touch event canceled in startDrag
1071             intercept = true;
1072         } else {
1073             if (super.onInterceptTouchEvent(ev)) {
1074                 intercept = true;
1075             }
1076
1077             switch (action) {
1078                 case MotionEvent.ACTION_CANCEL:
1079                 case MotionEvent.ACTION_UP:
1080                     doActionUpOrCancel();
1081                     break;
1082                 default:
1083                     if (intercept) {
1084                         mCancelMethod = ON_TOUCH_EVENT;
1085                     } else {
1086                         mCancelMethod = ON_INTERCEPT_TOUCH_EVENT;
1087                     }
1088             }
1089         }
1090
1091         // check for startDragging
1092
1093         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
1094             mInTouchEvent = false;
1095         }
1096
1097         return intercept;
1098     }
1099
1100     /**
1101      * Set the width of each drag scroll region by specifying a fraction of the
1102      * ListView height.
1103      * 
1104      * @param heightFraction Fraction of ListView height. Capped at 0.5f.
1105      */
1106     public void setDragScrollStart(final float heightFraction) {
1107         setDragScrollStarts(heightFraction, heightFraction);
1108     }
1109
1110     /**
1111      * Set the width of each drag scroll region by specifying a fraction of the
1112      * ListView height.
1113      * 
1114      * @param upperFrac Fraction of ListView height for up-scroll bound. Capped
1115      *            at 0.5f.
1116      * @param lowerFrac Fraction of ListView height for down-scroll bound.
1117      *            Capped at 0.5f.
1118      */
1119     public void setDragScrollStarts(final float upperFrac, final float lowerFrac) {
1120         if (lowerFrac > 0.5f) {
1121             mDragDownScrollStartFrac = 0.5f;
1122         } else {
1123             mDragDownScrollStartFrac = lowerFrac;
1124         }
1125
1126         if (upperFrac > 0.5f) {
1127             mDragUpScrollStartFrac = 0.5f;
1128         } else {
1129             mDragUpScrollStartFrac = upperFrac;
1130         }
1131
1132         if (getHeight() != 0) {
1133             updateScrollStarts();
1134         }
1135     }
1136
1137     private void continueDrag(final int x, final int y) {
1138
1139         // Log.d("mobeta", "move");
1140         dragView(x, y);
1141
1142         // if (mTrackDragSort) {
1143         // mDragSortTracker.appendState();
1144         // }
1145
1146         requestLayout();
1147
1148         final int minY = Math.min(y, mFloatViewMid + mFloatViewHeightHalf);
1149         final int maxY = Math.max(y, mFloatViewMid - mFloatViewHeightHalf);
1150
1151         // get the current scroll direction
1152         final int currentScrollDir = mDragScroller.getScrollDir();
1153
1154         if (minY > mLastY && minY > mDownScrollStartY && currentScrollDir != DragScroller.DOWN) {
1155             // dragged down, it is below the down scroll start and it is not
1156             // scrolling up
1157
1158             if (currentScrollDir != DragScroller.STOP) {
1159                 // moved directly from up scroll to down scroll
1160                 mDragScroller.stopScrolling(true);
1161             }
1162
1163             // start scrolling down
1164             mDragScroller.startScrolling(DragScroller.DOWN);
1165         } else if (maxY < mLastY && maxY < mUpScrollStartY && currentScrollDir != DragScroller.UP) {
1166             // dragged up, it is above the up scroll start and it is not
1167             // scrolling up
1168
1169             if (currentScrollDir != DragScroller.STOP) {
1170                 // moved directly from down scroll to up scroll
1171                 mDragScroller.stopScrolling(true);
1172             }
1173
1174             // start scrolling up
1175             mDragScroller.startScrolling(DragScroller.UP);
1176         } else if (maxY >= mUpScrollStartY && minY <= mDownScrollStartY
1177                 && mDragScroller.isScrolling()) {
1178             // not in the upper nor in the lower drag-scroll regions but it is
1179             // still scrolling
1180
1181             mDragScroller.stopScrolling(true);
1182         }
1183     }
1184
1185     private void updateScrollStarts() {
1186         final int padTop = getPaddingTop();
1187         final int listHeight = getHeight() - padTop - getPaddingBottom();
1188
1189         mUpScrollStartYF = padTop + mDragUpScrollStartFrac * (float) listHeight;
1190         mDownScrollStartYF = padTop + (1.0f - mDragDownScrollStartFrac) * (float) listHeight;
1191
1192         mUpScrollStartY = (int)mUpScrollStartYF;
1193         mDownScrollStartY = (int)mDownScrollStartYF;
1194
1195         mDragUpScrollHeight = mUpScrollStartYF - padTop;
1196         mDragDownScrollHeight = padTop + listHeight - mDownScrollStartYF;
1197     }
1198
1199     /**
1200      * {@inheritDoc}
1201      */
1202     @Override
1203     protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
1204         super.onSizeChanged(w, h, oldw, oldh);
1205         updateScrollStarts();
1206     }
1207
1208     private void dropFloatView(final boolean removeSrcItem) {
1209
1210         mDragScroller.stopScrolling(true);
1211
1212         if (removeSrcItem) {
1213             if (mRemoveListener != null) {
1214                 mRemoveListener.remove(mSrcPos - getHeaderViewsCount());
1215             }
1216         } else {
1217             if (mDropListener != null && mFloatPos >= 0 && mFloatPos < getCount()) {
1218                 final int numHeaders = getHeaderViewsCount();
1219                 mDropListener.drop(mSrcPos - numHeaders, mFloatPos - numHeaders);
1220             }
1221
1222             // adjustAllItems();
1223
1224             final int firstPos = getFirstVisiblePosition();
1225             if (mSrcPos < firstPos) {
1226                 // collapsed src item is off screen;
1227                 // adjust the scroll after item heights have been fixed
1228                 final View v = getChildAt(0);
1229                 int top = 0;
1230                 if (v != null) {
1231                     top = v.getTop();
1232                 }
1233                 // Log.d("mobeta", "top="+top+" fvh="+mFloatViewHeight);
1234                 setSelectionFromTop(firstPos - 1, top - getPaddingTop());
1235             }
1236         }
1237
1238         mSrcPos = -1;
1239         mFirstExpPos = -1;
1240         mSecondExpPos = -1;
1241         mFloatPos = -1;
1242
1243         removeFloatView();
1244
1245         if (mTrackDragSort) {
1246             mDragSortTracker.stopTracking();
1247         }
1248     }
1249
1250     private void adjustAllItems() {
1251         final int first = getFirstVisiblePosition();
1252         final int last = getLastVisiblePosition();
1253
1254         final int begin = Math.max(0, getHeaderViewsCount() - first);
1255         final int end = Math.min(last - first, getCount() - 1 - getFooterViewsCount() - first);
1256
1257         for (int i = begin; i <= end; ++i) {
1258             final View v = getChildAt(i);
1259             if (v != null) {
1260                 adjustItem(first + i, v, false);
1261             }
1262         }
1263     }
1264
1265     private void adjustItem(final int position, final View v, final boolean needsMeasure) {
1266
1267         final ViewGroup.LayoutParams lp = v.getLayoutParams();
1268         final int oldHeight = lp.height;
1269         int height = oldHeight;
1270
1271         getDividerHeight();
1272
1273         final boolean isSliding = mAnimate && mFirstExpPos != mSecondExpPos;
1274         final int maxNonSrcBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
1275         final int slideHeight = (int)(mSlideFrac * maxNonSrcBlankHeight);
1276
1277         if (position == mSrcPos) {
1278             if (mSrcPos == mFirstExpPos) {
1279                 if (isSliding) {
1280                     height = slideHeight + mItemHeightCollapsed;
1281                 } else {
1282                     height = mFloatViewHeight;
1283                 }
1284             } else if (mSrcPos == mSecondExpPos) {
1285                 // if gets here, we know an item is sliding
1286                 height = mFloatViewHeight - slideHeight;
1287             } else {
1288                 height = mItemHeightCollapsed;
1289             }
1290         } else if (position == mFirstExpPos || position == mSecondExpPos) {
1291             // position is not src
1292
1293             final ItemHeights itemHeights = new ItemHeights();
1294             if (needsMeasure) {
1295                 measureItemAndGetHeights(position, v, itemHeights);
1296             } else {
1297                 getItemHeights(position, v, itemHeights);
1298             }
1299
1300             if (position == mFirstExpPos) {
1301                 if (isSliding) {
1302                     height = itemHeights.child + slideHeight;
1303                 } else {
1304                     height = itemHeights.child + maxNonSrcBlankHeight;
1305                 }
1306             } else { // position=mSecondExpPos
1307                 // we know an item is sliding (b/c 2ndPos != 1stPos)
1308                 height = itemHeights.child + maxNonSrcBlankHeight - slideHeight;
1309             }
1310         } else {
1311             height = ViewGroup.LayoutParams.WRAP_CONTENT;
1312         }
1313
1314         if (height != oldHeight) {
1315             lp.height = height;
1316
1317             v.setLayoutParams(lp);
1318         }
1319
1320         // Adjust item gravity
1321
1322         if (position == mFirstExpPos || position == mSecondExpPos) {
1323             if (position < mSrcPos) {
1324                 ((RelativeLayout)v).setGravity(Gravity.BOTTOM);
1325             } else if (position > mSrcPos) {
1326                 ((RelativeLayout)v).setGravity(Gravity.TOP);
1327             }
1328         }
1329
1330         // Finally adjust item visibility
1331
1332         final int oldVis = v.getVisibility();
1333         int vis = View.VISIBLE;
1334
1335         if (position == mSrcPos && mFloatView != null) {
1336             vis = View.INVISIBLE;
1337         }
1338
1339         if (vis != oldVis) {
1340             v.setVisibility(vis);
1341         }
1342     }
1343
1344     /**
1345      * {@inheritDoc}
1346      */
1347     @Override
1348     public void requestLayout() {
1349         if (!mBlockLayoutRequests) {
1350             super.requestLayout();
1351         }
1352     }
1353
1354     private void doDragScroll(final int oldFirstExpPos, final int oldSecondExpPos) {
1355         if (mScrollY == 0) {
1356             return;
1357         }
1358
1359         final int padTop = getPaddingTop();
1360         final int listHeight = getHeight() - padTop - getPaddingBottom();
1361         final int first = getFirstVisiblePosition();
1362         final int last = getLastVisiblePosition();
1363
1364         int movePos;
1365
1366         if (mScrollY >= 0) {
1367             mScrollY = Math.min(listHeight, mScrollY);
1368             movePos = first;
1369         } else {
1370             mScrollY = Math.max(-listHeight, mScrollY);
1371             movePos = last;
1372         }
1373
1374         final View moveItem = getChildAt(movePos - first);
1375         int top = moveItem.getTop() + mScrollY;
1376
1377         if (movePos == 0 && top > padTop) {
1378             top = padTop;
1379         }
1380
1381         final ItemHeights itemHeightsBefore = new ItemHeights();
1382         getItemHeights(movePos, moveItem, itemHeightsBefore);
1383         final int moveHeightBefore = itemHeightsBefore.item;
1384         final int moveBlankBefore = moveHeightBefore - itemHeightsBefore.child;
1385
1386         final ItemHeights itemHeightsAfter = new ItemHeights();
1387         measureItemAndGetHeights(movePos, moveItem, itemHeightsAfter);
1388         final int moveHeightAfter = itemHeightsAfter.item;
1389         final int moveBlankAfter = moveHeightAfter - itemHeightsAfter.child;
1390
1391         if (movePos <= oldFirstExpPos) {
1392             if (movePos > mFirstExpPos) {
1393                 top += mFloatViewHeight - moveBlankAfter;
1394             }
1395         } else if (movePos == oldSecondExpPos) {
1396             if (movePos <= mFirstExpPos) {
1397                 top += moveBlankBefore - mFloatViewHeight;
1398             } else if (movePos == mSecondExpPos) {
1399                 top += moveHeightBefore - moveHeightAfter;
1400             } else {
1401                 top += moveBlankBefore;
1402             }
1403         } else {
1404             if (movePos <= mFirstExpPos) {
1405                 top -= mFloatViewHeight;
1406             } else if (movePos == mSecondExpPos) {
1407                 top -= moveBlankAfter;
1408             }
1409         }
1410
1411         setSelectionFromTop(movePos, top - padTop);
1412
1413         mScrollY = 0;
1414     }
1415
1416     private void measureFloatView() {
1417         if (mFloatView != null) {
1418             ViewGroup.LayoutParams lp = mFloatView.getLayoutParams();
1419             if (lp == null) {
1420                 lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1421                         ViewGroup.LayoutParams.WRAP_CONTENT);
1422             }
1423             final int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft()
1424                     + getListPaddingRight(), lp.width);
1425             int hspec;
1426             if (lp.height > 0) {
1427                 hspec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
1428             } else {
1429                 hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1430             }
1431             mFloatView.measure(wspec, hspec);
1432             mFloatViewHeight = mFloatView.getMeasuredHeight();
1433             mFloatViewHeightHalf = mFloatViewHeight / 2;
1434         }
1435     }
1436
1437     /**
1438      * {@inheritDoc}
1439      */
1440     @Override
1441     protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
1442         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1443         if (mFloatView != null) {
1444             if (mFloatView.isLayoutRequested()) {
1445                 measureFloatView();
1446             }
1447         }
1448         mWidthMeasureSpec = widthMeasureSpec;
1449         mDragScroller.setListHeight(getHeight());
1450     }
1451
1452     /**
1453      * {@inheritDoc}
1454      */
1455     @Override
1456     protected void layoutChildren() {
1457
1458         if (mFloatView != null) {
1459             mFloatView.layout(0, 0, mFloatView.getMeasuredWidth(), mFloatView.getMeasuredHeight());
1460
1461             // Log.d("mobeta", "layout children");
1462             final int oldFirstExpPos = mFirstExpPos;
1463             final int oldSecondExpPos = mSecondExpPos;
1464
1465             mBlockLayoutRequests = true;
1466
1467             if (getChildCount() > 0 && updatePositions()) {
1468                 adjustAllItems();
1469             }
1470
1471             if (mScrollY != 0) {
1472                 doDragScroll(oldFirstExpPos, oldSecondExpPos);
1473             }
1474
1475             mBlockLayoutRequests = false;
1476         }
1477
1478         super.layoutChildren();
1479     }
1480
1481     protected boolean onDragTouchEvent(final MotionEvent ev) {
1482         switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1483             case MotionEvent.ACTION_CANCEL:
1484             case MotionEvent.ACTION_UP:
1485                 stopDrag(false);
1486                 doActionUpOrCancel();
1487                 break;
1488             case MotionEvent.ACTION_MOVE:
1489                 continueDrag((int)ev.getX(), (int)ev.getY());
1490                 break;
1491         }
1492
1493         return true;
1494     }
1495
1496     /**
1497      * Start a drag of item at <code>position</code> using the registered
1498      * FloatViewManager. Calls through to
1499      * {@link #startDrag(int,View,int,int,int)} after obtaining the floating
1500      * View from the FloatViewManager.
1501      * 
1502      * @param position Item to drag.
1503      * @param dragFlags Flags that restrict some movements of the floating View.
1504      *            For example, set <code>dragFlags |= 
1505      * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating View in all
1506      *            directions except off the screen to the left.
1507      * @param deltaX Offset in x of the touch coordinate from the left edge of
1508      *            the floating View (i.e. touch-x minus float View left).
1509      * @param deltaY Offset in y of the touch coordinate from the top edge of
1510      *            the floating View (i.e. touch-y minus float View top).
1511      * @return True if the drag was started, false otherwise. This
1512      *         <code>startDrag</code> will fail if we are not currently in a
1513      *         touch event, there is no registered FloatViewManager, or the
1514      *         FloatViewManager returns a null View.
1515      */
1516     public boolean startDrag(final int position, final int dragFlags, final int deltaX,
1517             final int deltaY) {
1518         if (!mInTouchEvent || mFloatViewManager == null) {
1519             return false;
1520         }
1521
1522         final View v = mFloatViewManager.onCreateFloatView(position);
1523
1524         if (v == null) {
1525             return false;
1526         } else {
1527             return startDrag(position, v, dragFlags, deltaX, deltaY);
1528         }
1529
1530     }
1531
1532     /**
1533      * Start a drag of item at <code>position</code> without using a
1534      * FloatViewManager.
1535      * 
1536      * @param position Item to drag.
1537      * @param floatView Floating View.
1538      * @param dragFlags Flags that restrict some movements of the floating View.
1539      *            For example, set <code>dragFlags |= 
1540      * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating View in all
1541      *            directions except off the screen to the left.
1542      * @param deltaX Offset in x of the touch coordinate from the left edge of
1543      *            the floating View (i.e. touch-x minus float View left).
1544      * @param deltaY Offset in y of the touch coordinate from the top edge of
1545      *            the floating View (i.e. touch-y minus float View top).
1546      * @return True if the drag was started, false otherwise. This
1547      *         <code>startDrag</code> will fail if we are not currently in a
1548      *         touch event, <code>floatView</code> is null, or there is a drag
1549      *         in progress.
1550      */
1551     public boolean startDrag(final int position, final View floatView, final int dragFlags,
1552             final int deltaX, final int deltaY) {
1553         if (!mInTouchEvent || mFloatView != null || floatView == null) {
1554             return false;
1555         }
1556
1557         if (getParent() != null) {
1558             getParent().requestDisallowInterceptTouchEvent(true);
1559         }
1560
1561         final int pos = position + getHeaderViewsCount();
1562         mFirstExpPos = pos;
1563         mSecondExpPos = pos;
1564         mSrcPos = pos;
1565         mFloatPos = pos;
1566
1567         // mDragState = dragType;
1568         mDragState = DRAGGING;
1569         mDragFlags = 0;
1570         mDragFlags |= dragFlags;
1571
1572         mFloatView = floatView;
1573         measureFloatView(); // sets mFloatViewHeight
1574
1575         mDragDeltaX = deltaX;
1576         mDragDeltaY = deltaY;
1577         updateFloatView(mX - mDragDeltaX, mY - mDragDeltaY);
1578
1579         // set src item invisible
1580         final View srcItem = getChildAt(mSrcPos - getFirstVisiblePosition());
1581         if (srcItem != null) {
1582             srcItem.setVisibility(View.INVISIBLE);
1583         }
1584
1585         if (mTrackDragSort) {
1586             mDragSortTracker.startTracking();
1587         }
1588
1589         // once float view is created, events are no longer passed
1590         // to ListView
1591         switch (mCancelMethod) {
1592             case ON_TOUCH_EVENT:
1593                 super.onTouchEvent(mCancelEvent);
1594                 break;
1595             case ON_INTERCEPT_TOUCH_EVENT:
1596                 super.onInterceptTouchEvent(mCancelEvent);
1597                 break;
1598         }
1599
1600         requestLayout();
1601
1602         return true;
1603     }
1604
1605     /**
1606      * Sets float View location based on suggested values and constraints set in
1607      * mDragFlags.
1608      */
1609     private void updateFloatView(final int floatX, final int floatY) {
1610
1611         // restrict x motion
1612         final int padLeft = getPaddingLeft();
1613         if ((mDragFlags & DRAG_POS_X) == 0 && floatX > padLeft) {
1614             mFloatViewLeft = padLeft;
1615         } else if ((mDragFlags & DRAG_NEG_X) == 0 && floatX < padLeft) {
1616             mFloatViewLeft = padLeft;
1617         } else {
1618             mFloatViewLeft = floatX;
1619         }
1620
1621         // keep floating view from going past bottom of last header view
1622         final int numHeaders = getHeaderViewsCount();
1623         final int numFooters = getFooterViewsCount();
1624         final int firstPos = getFirstVisiblePosition();
1625         final int lastPos = getLastVisiblePosition();
1626
1627         // Log.d("mobeta",
1628         // "nHead="+numHeaders+" nFoot="+numFooters+" first="+firstPos+" last="+lastPos);
1629         int topLimit = getPaddingTop();
1630         if (firstPos < numHeaders) {
1631             topLimit = getChildAt(numHeaders - firstPos - 1).getBottom();
1632         }
1633         if ((mDragFlags & DRAG_NEG_Y) == 0) {
1634             if (firstPos <= mSrcPos) {
1635                 topLimit = Math.max(getChildAt(mSrcPos - firstPos).getTop(), topLimit);
1636             }
1637         }
1638         // bottom limit is top of first footer View or
1639         // bottom of last item in list
1640         int bottomLimit = getHeight() - getPaddingBottom();
1641         if (lastPos >= getCount() - numFooters - 1) {
1642             bottomLimit = getChildAt(getCount() - numFooters - 1 - firstPos).getBottom();
1643         }
1644         if ((mDragFlags & DRAG_POS_Y) == 0) {
1645             if (lastPos >= mSrcPos) {
1646                 bottomLimit = Math.min(getChildAt(mSrcPos - firstPos).getBottom(), bottomLimit);
1647             }
1648         }
1649
1650         // Log.d("mobeta", "dragView top=" + (y - mDragDeltaY));
1651         // Log.d("mobeta", "limit=" + limit);
1652         // Log.d("mobeta", "mDragDeltaY=" + mDragDeltaY);
1653
1654         if (floatY < topLimit) {
1655             mFloatViewTop = topLimit;
1656         } else if (floatY + mFloatViewHeight > bottomLimit) {
1657             mFloatViewTop = bottomLimit - mFloatViewHeight;
1658         } else {
1659             mFloatViewTop = floatY;
1660         }
1661
1662         // get y-midpoint of floating view (constrained to ListView bounds)
1663         mFloatViewMid = mFloatViewTop + mFloatViewHeightHalf;
1664     }
1665
1666     private void dragView(final int x, final int y) {
1667         // Log.d("mobeta", "float view pure x=" + x + " y=" + y);
1668
1669         // proposed position
1670         mFloatLoc.x = x - mDragDeltaX;
1671         mFloatLoc.y = y - mDragDeltaY;
1672
1673         final Point touch = new Point(x, y);
1674
1675         // let manager adjust proposed position first
1676         if (mFloatViewManager != null) {
1677             mFloatViewManager.onDragFloatView(mFloatView, mFloatLoc, touch);
1678         }
1679
1680         // then we override if manager gives an unsatisfactory
1681         // position (e.g. over a header/footer view). Also,
1682         // dragFlags override manager adjustments.
1683         updateFloatView(mFloatLoc.x, mFloatLoc.y);
1684     }
1685
1686     private void removeFloatView() {
1687         if (mFloatView != null) {
1688             mFloatView.setVisibility(GONE);
1689             if (mFloatViewManager != null) {
1690                 mFloatViewManager.onDestroyFloatView(mFloatView);
1691             }
1692             mFloatView = null;
1693         }
1694     }
1695
1696     /**
1697      * Interface for customization of the floating View appearance and dragging
1698      * behavior. Implement your own and pass it to {@link #setFloatViewManager}.
1699      * If your own is not passed, the default {@link SimpleFloatViewManager}
1700      * implementation is used.
1701      */
1702     public interface FloatViewManager {
1703         /**
1704          * Return the floating View for item at <code>position</code>.
1705          * DragSortListView will measure and layout this View for you, so feel
1706          * free to just inflate it. You can help DSLV by setting some
1707          * {@link ViewGroup.LayoutParams} on this View; otherwise it will set
1708          * some for you (with a width of FILL_PARENT and a height of
1709          * WRAP_CONTENT).
1710          * 
1711          * @param position Position of item to drag (NOTE: <code>position</code>
1712          *            excludes header Views; thus, if you want to call
1713          *            {@link ListView#getChildAt(int)}, you will need to add
1714          *            {@link ListView#getHeaderViewsCount()} to the index).
1715          * @return The View you wish to display as the floating View.
1716          */
1717         public View onCreateFloatView(int position);
1718
1719         /**
1720          * Called whenever the floating View is dragged. Float View properties
1721          * can be changed here. Also, the upcoming location of the float View
1722          * can be altered by setting <code>location.x</code> and
1723          * <code>location.y</code>.
1724          * 
1725          * @param floatView The floating View.
1726          * @param location The location (top-left; relative to DSLV top-left) at
1727          *            which the float View would like to appear, given the
1728          *            current touch location and the offset provided in
1729          *            {@link DragSortListView#startDrag}.
1730          * @param touch The current touch location (relative to DSLV top-left).
1731          */
1732         public void onDragFloatView(View floatView, Point location, Point touch);
1733
1734         /**
1735          * Called when the float View is dropped; lets you perform any necessary
1736          * cleanup. The internal DSLV floating View reference is set to null
1737          * immediately after this is called.
1738          * 
1739          * @param floatView The floating View passed to
1740          *            {@link #onCreateFloatView(int)}.
1741          */
1742         public void onDestroyFloatView(View floatView);
1743     }
1744
1745     public void setFloatViewManager(final FloatViewManager manager) {
1746         mFloatViewManager = manager;
1747     }
1748
1749     public void setDragListener(final DragListener l) {
1750         mDragListener = l;
1751     }
1752
1753     /**
1754      * Allows for easy toggling between a DragSortListView and a regular old
1755      * ListView. If enabled, items are draggable, where the drag init mode
1756      * determines how items are lifted (see {@link setDragInitMode(int)}). If
1757      * disabled, items cannot be dragged.
1758      * 
1759      * @param enabled Set <code>true</code> to enable list item dragging
1760      */
1761     public void setDragEnabled(final boolean enabled) {
1762         mDragEnabled = enabled;
1763     }
1764
1765     public boolean isDragEnabled() {
1766         return mDragEnabled;
1767     }
1768
1769     /**
1770      * This better reorder your ListAdapter! DragSortListView does not do this
1771      * for you; doesn't make sense to. Make sure
1772      * {@link BaseAdapter#notifyDataSetChanged()} or something like it is called
1773      * in your implementation.
1774      * 
1775      * @param l
1776      */
1777     public void setDropListener(final DropListener l) {
1778         mDropListener = l;
1779     }
1780
1781     /**
1782      * Probably a no-brainer, but make sure that your remove listener calls
1783      * {@link BaseAdapter#notifyDataSetChanged()} or something like it. When an
1784      * item removal occurs, DragSortListView relies on a redraw of all the items
1785      * to recover invisible views and such. Strictly speaking, if you remove
1786      * something, your dataset has changed...
1787      * 
1788      * @param l
1789      */
1790     public void setRemoveListener(final RemoveListener l) {
1791         if (mController != null && l == null) {
1792             mController.setRemoveEnabled(false);
1793         }
1794         mRemoveListener = l;
1795     }
1796
1797     public interface DragListener {
1798         public void drag(int from, int to);
1799     }
1800
1801     /**
1802      * Your implementation of this has to reorder your ListAdapter! Make sure to
1803      * call {@link BaseAdapter#notifyDataSetChanged()} or something like it in
1804      * your implementation.
1805      * 
1806      * @author heycosmo
1807      */
1808     public interface DropListener {
1809         public void drop(int from, int to);
1810     }
1811
1812     /**
1813      * Make sure to call {@link BaseAdapter#notifyDataSetChanged()} or something
1814      * like it in your implementation.
1815      * 
1816      * @author heycosmo
1817      */
1818     public interface RemoveListener {
1819         public void remove(int which);
1820     }
1821
1822     public interface DragSortListener extends DropListener, DragListener, RemoveListener {
1823     }
1824
1825     public void setDragSortListener(final DragSortListener l) {
1826         setDropListener(l);
1827         setDragListener(l);
1828         setRemoveListener(l);
1829     }
1830
1831     /**
1832      * Completely custom scroll speed profile. Default increases linearly with
1833      * position and is constant in time. Create your own by implementing
1834      * {@link DragSortListView.DragScrollProfile}.
1835      * 
1836      * @param ssp
1837      */
1838     public void setDragScrollProfile(final DragScrollProfile ssp) {
1839         if (ssp != null) {
1840             mScrollProfile = ssp;
1841         }
1842     }
1843
1844     /**
1845      * Interface for controlling scroll speed as a function of touch position
1846      * and time. Use
1847      * {@link DragSortListView#setDragScrollProfile(DragScrollProfile)} to set
1848      * custom profile.
1849      * 
1850      * @author heycosmo
1851      */
1852     public interface DragScrollProfile {
1853         /**
1854          * Return a scroll speed in pixels/millisecond. Always return a positive
1855          * number.
1856          * 
1857          * @param w Normalized position in scroll region (i.e. w \in [0,1]).
1858          *            Small w typically means slow scrolling.
1859          * @param t Time (in milliseconds) since start of scroll (handy if you
1860          *            want scroll acceleration).
1861          * @return Scroll speed at position w and time t in pixels/ms.
1862          */
1863         float getSpeed(float w, long t);
1864     }
1865
1866     private class DragScroller implements Runnable, AbsListView.OnScrollListener {
1867
1868         private boolean mAbort;
1869
1870         private long mPrevTime;
1871
1872         private int dy;
1873
1874         private float dt;
1875
1876         private long tStart;
1877
1878         private int scrollDir;
1879
1880         public final static int STOP = -1;
1881
1882         public final static int UP = 0;
1883
1884         public final static int DOWN = 1;
1885
1886         private float mScrollSpeed; // pixels per ms
1887
1888         private boolean mScrolling = false;
1889
1890         private int mMaxScrollSpeed;
1891
1892         public boolean isScrolling() {
1893             return mScrolling;
1894         }
1895
1896         public int getScrollDir() {
1897             return mScrolling ? scrollDir : STOP;
1898         }
1899
1900         public DragScroller() {
1901         }
1902
1903         public void startScrolling(final int dir) {
1904             if (!mScrolling) {
1905                 // Debug.startMethodTracing("dslv-scroll");
1906                 mAbort = false;
1907                 mScrolling = true;
1908                 tStart = SystemClock.uptimeMillis();
1909                 mPrevTime = tStart;
1910                 scrollDir = dir;
1911                 post(this);
1912             }
1913         }
1914
1915         public void stopScrolling(final boolean now) {
1916             if (now) {
1917                 removeCallbacks(this);
1918                 mScrolling = false;
1919             } else {
1920                 mAbort = true;
1921             }
1922
1923         }
1924
1925         public void setListHeight(final int height) {
1926             // cap the max scroll speed per frame to be 1/5 of the list height
1927             mMaxScrollSpeed = height / 5;
1928         }
1929
1930         /**
1931          * {@inheritDoc}
1932          */
1933         @Override
1934         public void run() {
1935             if (mAbort) {
1936                 mScrolling = false;
1937                 return;
1938             }
1939
1940             final int first = getFirstVisiblePosition();
1941             final int last = getLastVisiblePosition();
1942             final int count = getCount();
1943             final int padTop = getPaddingTop();
1944             final int listHeight = getHeight() - padTop - getPaddingBottom();
1945
1946             final int minY = Math.min(mY, mFloatViewMid + mFloatViewHeightHalf);
1947             final int maxY = Math.max(mY, mFloatViewMid - mFloatViewHeightHalf);
1948
1949             if (scrollDir == UP) {
1950                 final View v = getChildAt(0);
1951                 if (v == null) {
1952                     mScrolling = false;
1953                     return;
1954                 } else {
1955                     if (first == 0 && v.getTop() == padTop) {
1956                         mScrolling = false;
1957                         return;
1958                     }
1959                 }
1960                 mScrollSpeed = mScrollProfile.getSpeed((mUpScrollStartYF - maxY)
1961                         / mDragUpScrollHeight, mPrevTime);
1962             } else {
1963                 final View v = getChildAt(last - first);
1964                 if (v == null) {
1965                     mScrolling = false;
1966                     return;
1967                 } else {
1968                     if (last == count - 1 && v.getBottom() <= listHeight + padTop) {
1969                         mScrolling = false;
1970                         return;
1971                     }
1972                 }
1973                 mScrollSpeed = -mScrollProfile.getSpeed((minY - mDownScrollStartYF)
1974                         / mDragDownScrollHeight, mPrevTime);
1975             }
1976
1977             dt = SystemClock.uptimeMillis() - mPrevTime;
1978             // dy is change in View position of a list item; i.e. positive dy
1979             // means user is scrolling up (list item moves down the screen,
1980             // remember
1981             // y=0 is at top of View).
1982             dy = Math.round(mScrollSpeed * dt);
1983             mScrollY += dy;
1984
1985             // cap the scroll speed
1986             mScrollY = Math.max(Math.min(mScrollY, mMaxScrollSpeed), -mMaxScrollSpeed);
1987
1988             requestLayout();
1989
1990             mPrevTime += dt;
1991
1992             post(this);
1993         }
1994
1995         /**
1996          * {@inheritDoc}
1997          */
1998         @Override
1999         public void onScroll(final AbsListView view, final int firstVisibleItem,
2000                 final int visibleItemCount, final int totalItemCount) {
2001             if (mScrolling && visibleItemCount != 0) {
2002                 dragView(mX, mY);
2003             }
2004         }
2005
2006         /**
2007          * {@inheritDoc}
2008          */
2009         @Override
2010         public void onScrollStateChanged(final AbsListView view, final int scrollState) {
2011         }
2012
2013     }
2014
2015     private class DragSortTracker {
2016         StringBuilder mBuilder = new StringBuilder();
2017
2018         File mFile;
2019
2020         private int mNumInBuffer = 0;
2021
2022         private int mNumFlushes = 0;
2023
2024         private boolean mTracking = false;
2025
2026         public void startTracking() {
2027             mBuilder.append("<DSLVStates>\n");
2028             mNumFlushes = 0;
2029             mTracking = true;
2030         }
2031
2032         public void appendState() {
2033             if (!mTracking) {
2034                 return;
2035             }
2036
2037             mBuilder.append("<DSLVState>\n");
2038             final int children = getChildCount();
2039             final int first = getFirstVisiblePosition();
2040             final ItemHeights itemHeights = new ItemHeights();
2041             mBuilder.append("    <Positions>");
2042             for (int i = 0; i < children; ++i) {
2043                 mBuilder.append(first + i).append(",");
2044             }
2045             mBuilder.append("</Positions>\n");
2046
2047             mBuilder.append("    <Tops>");
2048             for (int i = 0; i < children; ++i) {
2049                 mBuilder.append(getChildAt(i).getTop()).append(",");
2050             }
2051             mBuilder.append("</Tops>\n");
2052             mBuilder.append("    <Bottoms>");
2053             for (int i = 0; i < children; ++i) {
2054                 mBuilder.append(getChildAt(i).getBottom()).append(",");
2055             }
2056             mBuilder.append("</Bottoms>\n");
2057
2058             mBuilder.append("    <FirstExpPos>").append(mFirstExpPos).append("</FirstExpPos>\n");
2059             getItemHeights(mFirstExpPos, itemHeights);
2060             mBuilder.append("    <FirstExpBlankHeight>")
2061                     .append(itemHeights.item - itemHeights.child)
2062                     .append("</FirstExpBlankHeight>\n");
2063             mBuilder.append("    <SecondExpPos>").append(mSecondExpPos).append("</SecondExpPos>\n");
2064             getItemHeights(mSecondExpPos, itemHeights);
2065             mBuilder.append("    <SecondExpBlankHeight>")
2066                     .append(itemHeights.item - itemHeights.child)
2067                     .append("</SecondExpBlankHeight>\n");
2068             mBuilder.append("    <SrcPos>").append(mSrcPos).append("</SrcPos>\n");
2069             mBuilder.append("    <SrcHeight>").append(mFloatViewHeight + getDividerHeight())
2070                     .append("</SrcHeight>\n");
2071             mBuilder.append("    <ViewHeight>").append(getHeight()).append("</ViewHeight>\n");
2072             mBuilder.append("    <LastY>").append(mLastY).append("</LastY>\n");
2073             mBuilder.append("    <FloatY>").append(mFloatViewMid).append("</FloatY>\n");
2074             mBuilder.append("    <ShuffleEdges>");
2075             for (int i = 0; i < children; ++i) {
2076                 mBuilder.append(getShuffleEdge(first + i, getChildAt(i).getTop())).append(",");
2077             }
2078             mBuilder.append("</ShuffleEdges>\n");
2079
2080             mBuilder.append("</DSLVState>\n");
2081             mNumInBuffer++;
2082
2083             if (mNumInBuffer > 1000) {
2084                 flush();
2085                 mNumInBuffer = 0;
2086             }
2087         }
2088
2089         public void flush() {
2090             if (!mTracking) {
2091                 return;
2092             }
2093
2094             // save to file on sdcard
2095             try {
2096                 boolean append = true;
2097                 if (mNumFlushes == 0) {
2098                     append = false;
2099                 }
2100                 final FileWriter writer = new FileWriter(mFile, append);
2101
2102                 writer.write(mBuilder.toString());
2103                 mBuilder.delete(0, mBuilder.length());
2104
2105                 writer.flush();
2106                 writer.close();
2107
2108                 mNumFlushes++;
2109             } catch (final IOException e) {
2110                 // do nothing
2111             }
2112         }
2113
2114         public void stopTracking() {
2115             if (mTracking) {
2116                 mBuilder.append("</DSLVStates>\n");
2117                 flush();
2118                 mTracking = false;
2119             }
2120         }
2121
2122     }
2123
2124 }