diff --git a/demo/pom.xml b/demo/pom.xml index 173a549..fe72c1c 100644 --- a/demo/pom.xml +++ b/demo/pom.xml @@ -55,6 +55,9 @@ Copyright 2012 Andreas Schildbach com.jayway.maven.plugins.android.generation2 android-maven-plugin + + 10 + org.apache.maven.plugins diff --git a/demo/res/layout/checkable_main.xml b/demo/res/layout/checkable_main.xml index 6419b36..14e4b38 100644 --- a/demo/res/layout/checkable_main.xml +++ b/demo/res/layout/checkable_main.xml @@ -1,6 +1,6 @@ com.jayway.maven.plugins.android.generation2 android-maven-plugin + + 10 + org.apache.maven.plugins diff --git a/library/src/com/mobeta/android/dslv/DragSortListView.java b/library/src/com/mobeta/android/dslv/DragSortListView.java index 7254b9c..5d226a3 100644 --- a/library/src/com/mobeta/android/dslv/DragSortListView.java +++ b/library/src/com/mobeta/android/dslv/DragSortListView.java @@ -42,12 +42,16 @@ import android.widget.BaseAdapter; import android.widget.HeaderViewListAdapter; import android.widget.ListAdapter; +import android.widget.AdapterView; import android.widget.ListView; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Iterator; /** * ListView subclass that mediates drag and drop resorting of items. @@ -78,7 +82,9 @@ public class DragSortListView extends ListView { /** * The middle (in the y-direction) of the floating View. */ - private int mFloatViewMid; + private int mDragY; + + private int mLastDragY; /** * Flag to make sure float View isn't measured twice @@ -101,7 +107,7 @@ public class DragSortListView extends ListView { * While drag-sorting, the current position of the floating * View. If dropped, the dragged item will land in this position. */ - private int mFloatPos; + private int mDropSlot; /** * The first expanded ListView position that helps represent @@ -125,7 +131,7 @@ public class DragSortListView extends ListView { /** * The user dragged from this position. */ - private int mSrcPos; + private int mSrcSlot; /** * Offset (in x) within the dragged item at which the user @@ -386,12 +392,10 @@ public float getSpeed(float w, long t) { private float mSlideFrac = 0.0f; /** - * Wraps the user-provided ListAdapter. This is used to wrap each - * item View given by the user inside another View (currenly - * a RelativeLayout) which - * expands and collapses to simulate the item shuffling. + * Wraps the user-provided ListAdapter. During a drag, this + * wrapper hides the dragged item. */ - private AdapterWrapper mAdapterWrapper; + private HideAdapter mHideAdapter; /** * Turn on custom debugger. @@ -427,7 +431,7 @@ public float getSpeed(float w, long t) { * drag-sort. */ private static final int sCacheSize = 3; - private HeightCache mChildHeightCache = new HeightCache(sCacheSize); + private HeightCache mItemHeightCache = new HeightCache(sCacheSize); private RemoveAnimator mRemoveAnimator; @@ -603,7 +607,7 @@ public void setMaxScrollSpeed(float max) { */ @Override public void setAdapter(ListAdapter adapter) { - mAdapterWrapper = new AdapterWrapper(adapter); + mHideAdapter = new HideAdapter(adapter); if (adapter != null) { adapter.registerDataSetObserver(mObserver); @@ -619,7 +623,7 @@ public void setAdapter(ListAdapter adapter) { } } - super.setAdapter(mAdapterWrapper); + super.setAdapter(mHideAdapter); } /** @@ -630,57 +634,10 @@ public void setAdapter(ListAdapter adapter) { * @return The ListAdapter set as the argument of {@link setAdapter()} */ public ListAdapter getInputAdapter() { - if (mAdapterWrapper == null) { + if (mHideAdapter == null) { return null; } else { - return mAdapterWrapper.getAdapter(); - } - } - - private class AdapterWrapper extends HeaderViewListAdapter { - private ListAdapter mAdapter; - - public AdapterWrapper(ListAdapter adapter) { - super(null, null, adapter); - mAdapter = adapter; - } - - public ListAdapter getAdapter() { - return mAdapter; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - - DragSortItemView v; - View child; - // Log.d("mobeta", - // "getView: position="+position+" convertView="+convertView); - if (convertView != null) { - v = (DragSortItemView) convertView; - View oldChild = v.getChildAt(0); - - child = mAdapter.getView(position, oldChild, v); - if (child != oldChild) { - // shouldn't get here if user is reusing convertViews - // properly - v.removeViewAt(0); - v.addView(child); - } - } else { - v = new DragSortItemView(getContext()); - v.setLayoutParams(new AbsListView.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - child = mAdapter.getView(position, null, v); - v.addView(child); - } - - // Set the correct item height given drag state; passed - // View needs to be measured if measurement is required. - adjustItem(position + getHeaderViewsCount(), v, true); - - return v; + return mHideAdapter.getAdapter(); } } @@ -701,7 +658,7 @@ private void drawDivider(int expPosition, Canvas canvas) { final int childHeight = expItem.getChildAt(0).getHeight(); - if (expPosition > mSrcPos) { + if (expPosition > mSrcSlot) { t = expItem.getTop() + childHeight; b = t + dividerHeight; } else { @@ -724,16 +681,6 @@ private void drawDivider(int expPosition, Canvas canvas) { protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); - if (mDragState != IDLE) { - // draw the divider over the expanded item - if (mFirstExpPos != mSrcPos) { - drawDivider(mFirstExpPos, canvas); - } - if (mSecondExpPos != mFirstExpPos && mSecondExpPos != mSrcPos) { - drawDivider(mSecondExpPos, canvas); - } - } - if (mFloatView != null) { // draw the float view over everything final int w = mFloatView.getWidth(); @@ -767,24 +714,6 @@ protected void dispatchDraw(Canvas canvas) { } } - private int getItemHeight(int position) { - View v = getChildAt(position - getFirstVisiblePosition()); - - if (v != null) { - // item is onscreen, just get the height of the View - return v.getHeight(); - } else { - // item is offscreen. get child height and calculate - // item height based on current shuffle state - return calcItemHeight(position, getChildHeight(position)); - } - } - - private void printPosData() { - Log.d("mobeta", "mSrcPos=" + mSrcPos + " mFirstExpPos=" + mFirstExpPos + " mSecondExpPos=" - + mSecondExpPos); - } - private class HeightCache { private SparseIntArray mMap; @@ -825,241 +754,539 @@ public void clear() { mMap.clear(); mOrder.clear(); } - } - /** - * Get the shuffle edge for item at position when top of - * item is at y-coord top. Assumes that current item heights - * are consistent with current float view location and - * thus expanded positions and slide fraction. i.e. Should not be - * called between update of expanded positions/slide fraction - * and layoutChildren. - * - * @param position - * @param top - * @param height Height of item at position. If -1, this function - * calculates this height. + /** + * Return the height of any list item, even if + * offscreen. A call to this function caches + * the result internally (for a while) so that future calls + * to this may be faster (especially if later calls request + * the same position). This function could be + * slow for offscreen items because it has to call + * {@link ListAdapter#getView(int, View, ViewGroup)}. * - * @return Shuffle line between position-1 and position (for - * the given view of the list; that is, for when top of item at - * position has y-coord of given `top`). If - * floating View (treated as horizontal line) is dropped - * immediately above this line, it lands in position-1. If - * dropped immediately below this line, it lands in position. + * @param position The item position (including headers). */ - private int getShuffleEdge(int position, int top) { + public int getItemHeight(int position) { + View v = getChildAt(position - getFirstVisiblePosition()); - final int numHeaders = getHeaderViewsCount(); - final int numFooters = getFooterViewsCount(); + if (v != null) { + // item is onscreen, just get the height of the View + return v.getHeight(); + } else { + Log.w("mobeta", "Getting offscreen height!"); + // item is offscreen + // first check cache for child height at this position + int height = mItemHeightCache.get(position); + if (height != -1) { + // Log.d("mobeta", "found child height in cache!"); + return height; + } - // shuffle edges are defined between items that can be - // dragged; there are N-1 of them if there are N draggable - // items. + final ListAdapter adapter = getAdapter(); + int type = adapter.getItemViewType(position); - if (position <= numHeaders || (position >= getCount() - numFooters)) { - return top; - } + // There might be a better place for checking for the following + final int typeCount = adapter.getViewTypeCount(); + if (typeCount != mSampleViewTypes.length) { + mSampleViewTypes = new View[typeCount]; + } - int divHeight = getDividerHeight(); + if (type >= 0) { + if (mSampleViewTypes[type] == null) { + v = adapter.getView(position, null, this); + mSampleViewTypes[type] = v; + } else { + v = adapter.getView(position, mSampleViewTypes[type], this); + } + } else { + // type is HEADER_OR_FOOTER or IGNORE + v = adapter.getView(position, null, this); + } - int edge; + ViewGroup.LayoutParams lp = v.getLayoutParams(); + if (lp != null && lp.height > 0) { + height = lp.height; + } else { + measureItem(v); + height = v.getMeasuredHeight(); + } - int maxBlankHeight = mFloatViewHeight - mItemHeightCollapsed; - int childHeight = getChildHeight(position); - int itemHeight = getItemHeight(position); + // cache it because this could have been expensive + mItemHeightCache.add(position, height); - // first calculate top of item given that floating View is - // centered over src position - int otop = top; - if (mSecondExpPos <= mSrcPos) { - // items are expanded on and/or above the source position + return height; + } + } - if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) { - if (position == mSrcPos) { - otop = top + itemHeight - mFloatViewHeight; - } else { - int blankHeight = itemHeight - childHeight; - otop = top + blankHeight - maxBlankHeight; + private int getItemTop(int position) { + final int first = getFirstVisiblePosition(); + View child = getChildAt(position - first); + if (child != null) { + return child.getTop(); + } else if (position < first) { + final int divHeight = getDividerHeight(); + int top = getChildAt(0).getTop(); + Gap gap; + for (int pos = first - 1; pos >= position; pos--) { + gap = getGapBelow(pos); + if (gap != null) { + top -= gap.height; } - } else if (position > mSecondExpPos && position <= mSrcPos) { - otop = top - maxBlankHeight; + top -= divHeight + getItemHeight(pos); } - + return top; } else { - // items are expanded on and/or below the source position - - if (position > mSrcPos && position <= mFirstExpPos) { - otop = top + maxBlankHeight; - } else if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) { - int blankHeight = itemHeight - childHeight; - otop = top + blankHeight; + final int last = getLastVisiblePosition(); + final int divHeight = getDividerHeight(); + int top = getChildAt(getChildCount() - 1).getTop(); + Gap gap; + for (int pos = last; pos < position; pos++) { + top += divHeight + getItemHeight(pos); + gap = getGapBelow(pos); + if (gap != null) { + top += gap.height; + } } + return top; } + } - // otop is set - if (position <= mSrcPos) { - edge = otop + (mFloatViewHeight - divHeight - getChildHeight(position - 1)) / 2; + private int getItemBottom(int position) { + final int first = getFirstVisiblePosition(); + View child = getChildAt(position - first); + if (child != null) { + return child.getBottom(); + } else if (position < first) { + final int divHeight = getDividerHeight(); + int bottom = getChildAt(0).getBottom(); + Gap gap; + for (int pos = first; pos > position; pos--) { + bottom -= divHeight + getItemHeight(pos); + gap = getGapAbove(pos); + if (gap != null) { + bottom -= gap.height; + } + } + return bottom; } else { - edge = otop + (childHeight - divHeight - mFloatViewHeight) / 2; + final int last = getLastVisiblePosition(); + final int divHeight = getDividerHeight(); + int bottom = getChildAt(getChildCount() - 1).getBottom(); + Gap gap; + for (int pos = last + 1; pos <= position; pos++) { + gap = getGapAbove(pos); + if (gap != null) { + bottom += gap.height; + } + bottom += divHeight + getItemHeight(pos); + } + return bottom; + } + } + + /** + * Source gap. + */ + private final static int GAP_FLAG_SOURCE = 0x1; + + /** + * Drop gap. + */ + private final static int GAP_FLAG_DROP = 0x2; + + /** + * The gap above the sliding position. + */ + private final static int GAP_FLAG_ABOVE_SLIDE = 0x4; + + /** + * The gap below the sliding position. + */ + private final static int GAP_FLAG_BELOW_SLIDE = 0x8; + + private static class Gap { + int index; + int height; + int minHeight; + + boolean isAbove(int position) { + return position >= index; } - return edge; + boolean isBelow(int position) { + return position < index; + } + + int getPositionAbove() { + return index - 1; + } + + int getPositionBelow() { + return index; + } } - private boolean updatePositions() { + private HashMap mGaps = new HashMap(); - final int first = getFirstVisiblePosition(); - int startPos = mFirstExpPos; - View startView = getChildAt(startPos - first); + private static final int sGapPoolCapacity = 5; + private ArrayList mGapPool = new ArrayList(sGapPoolCapacity); - if (startView == null) { - startPos = first + getChildCount() / 2; - startView = getChildAt(startPos - first); + private void recycleGap(Gap gap) { + if (mGapPool.size() != sGapPoolCapacity) { + mGapPool.add(gap); } - int startTop = startView.getTop(); + } - int itemHeight = startView.getHeight(); + private Gap obtainGap() { + final int size = mGapPool.size(); + if (size == 0) { + return new Gap(); + } else { + return mGapPool.remove(size - 1); + } + } - int edge = getShuffleEdge(startPos, startTop); - int lastEdge = edge; + private Gap insertGap(int index, int height, int minHeight) { + Gap gap = mGaps.get(index); + if (gap == null) { + gap = obtainGap(); + mGaps.put(index, gap); + } + gap.index = index; + gap.height = height; + gap.minHeight = minHeight; + return gap; + } - int divHeight = getDividerHeight(); + private Gap insertGapBelow(int position, int height, int minHeight) { + final int index = position + 1; + return insertGap(index, height, minHeight); + } - // Log.d("mobeta", "float mid="+mFloatViewMid); + private Gap insertGapAbove(int position, int height, int minHeight) { + final int index = position; + return insertGap(index, height, minHeight); + } - int itemPos = startPos; - int itemTop = startTop; - if (mFloatViewMid < edge) { - // scanning up for float position - // Log.d("mobeta", " edge="+edge); - while (itemPos >= 0) { - itemPos--; - itemHeight = getItemHeight(itemPos); + private Gap getGapAbove(int position) { + return mGaps.get(position); + } - if (itemPos == 0) { - edge = itemTop - divHeight - itemHeight; - break; - } + private Gap getGapBelow(int position) { + return mGaps.get(position + 1); + } - itemTop -= itemHeight + divHeight; - edge = getShuffleEdge(itemPos, itemTop); - // Log.d("mobeta", " edge="+edge); + private int getGapHeightAbove(int position) { + final Gap gap = getGapAbove(position); + return gap == null ? 0 : gap.height; + } - if (mFloatViewMid >= edge) { - break; - } + private int getGapHeightBelow(int position) { + final Gap gap = getGapBelow(position); + return gap == null ? 0 : gap.height; + } - lastEdge = edge; - } - } else { - // scanning down for float position - // Log.d("mobeta", " edge="+edge); - final int count = getCount(); - while (itemPos < count) { - if (itemPos == count - 1) { - edge = itemTop + divHeight + itemHeight; - break; - } + private Gap findGapByIndex(int index) { + return mGaps.get(index); + } - itemTop += divHeight + itemHeight; - itemHeight = getItemHeight(itemPos + 1); - edge = getShuffleEdge(itemPos + 1, itemTop); - // Log.d("mobeta", " edge="+edge); + private void removeGaps() { + for (Gap gap : mGaps.values()) { + recycleGap(gap); + } + mGaps.clear(); + } - // test for hit - if (mFloatViewMid < edge) { - break; - } + private void removeGap(Gap gap) { + mGaps.remove(gap.index); + recycleGap(gap); + } - lastEdge = edge; - itemPos++; + private int minimizeGaps(int position, int where) { + Iterator> iter = mGaps.entrySet().iterator(); + Gap gap; + int heightRemoved = 0; + while (iter.hasNext()) { + gap = iter.next().getValue(); + if ((where == ABOVE && gap.isAbove(position)) || (where == BELOW && gap.isBelow(position))) { + if (gap.minHeight > 0) { + heightRemoved += gap.height - gap.minHeight; + gap.height = gap.minHeight; + } else { + heightRemoved += gap.height; + recycleGap(gap); + iter.remove(); + } } } + return heightRemoved; + } - final int numHeaders = getHeaderViewsCount(); - final int numFooters = getFooterViewsCount(); + private int minimizeGapsBelow(int position) { + return minimizeGaps(position, BELOW); + } + + private int minimizeGapsAbove(int position) { + return minimizeGaps(position, ABOVE); + } + + private void extendGapBelowBy(int position, int height) { + Gap gap = getGapBelow(position); + if (gap == null) { + insertGapBelow(position, height, 0); + } else { + gap.height += height; + } + } + + private void extendGapAboveBy(int position, int height) { + Gap gap = getGapAbove(position); + if (gap == null) { + insertGapAbove(position, height, 0); + } else { + gap.height += height; + } + } - boolean updated = false; + private ArrayList mGapRemovals = new ArrayList(3); - int oldFirstExpPos = mFirstExpPos; - int oldSecondExpPos = mSecondExpPos; - float oldSlideFrac = mSlideFrac; + private final static int NOWHERE = -1; + private final static int ABOVE = 0; + private final static int BELOW = 1; + private final static int ON = 2; - if (mAnimate) { - int edgeToEdge = Math.abs(edge - lastEdge); + private int mSlidePos = INVALID_POSITION; - int edgeTop, edgeBottom; - if (mFloatViewMid < edge) { - edgeBottom = edge; - edgeTop = lastEdge; - } else { - edgeTop = edge; - edgeBottom = lastEdge; - } - // Log.d("mobeta", "edgeTop="+edgeTop+" edgeBot="+edgeBottom); - - int slideRgnHeight = (int) (0.5f * mSlideRegionFrac * edgeToEdge); - float slideRgnHeightF = (float) slideRgnHeight; - int slideEdgeTop = edgeTop + slideRgnHeight; - int slideEdgeBottom = edgeBottom - slideRgnHeight; - - // Three regions - if (mFloatViewMid < slideEdgeTop) { - mFirstExpPos = itemPos - 1; - mSecondExpPos = itemPos; - mSlideFrac = 0.5f * ((float) (slideEdgeTop - mFloatViewMid)) / slideRgnHeightF; - // Log.d("mobeta", - // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac); - } else if (mFloatViewMid < slideEdgeBottom) { - mFirstExpPos = itemPos; - mSecondExpPos = itemPos; - } else { - mFirstExpPos = itemPos; - mSecondExpPos = itemPos + 1; - mSlideFrac = 0.5f * (1.0f + ((float) (edgeBottom - mFloatViewMid)) - / slideRgnHeightF); - // Log.d("mobeta", - // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac); + /** + * Call this when drag point has moved relative to the list. + * Starts at current drop slot, and shuffles items toward + * drag point. + */ + private void updateShuffle2() { + if (mDragY == mLastDragY) { + return; + } + + final int first = getFirstVisiblePosition(); + final int last = getLastVisiblePosition(); + +/* + if (false) { + Log.d("mobeta", "update shuffle. mDragY="+mDragY+" mY="+mY+" listH="+getHeight()+". gaps:"); + for (Gap gap : mGaps.values()) { + Log.d("mobeta", " index="+gap.index+" height="+gap.height); + if ((gap.flags & GAP_FLAG_ABOVE_SLIDE) == GAP_FLAG_ABOVE_SLIDE) { + Log.d("mobeta", " - above slide"); + } + if ((gap.flags & GAP_FLAG_BELOW_SLIDE) == GAP_FLAG_BELOW_SLIDE) { + Log.d("mobeta", " - below slide"); + } + if ((gap.flags & GAP_FLAG_SOURCE) == GAP_FLAG_SOURCE) { + Log.d("mobeta", " - source"); + } + if ((gap.flags & GAP_FLAG_DROP) == GAP_FLAG_DROP) { + Log.d("mobeta", " - drop"); + } } + } +*/ + // Where to start? + int position; + if (mSlidePos != INVALID_POSITION) { + position = mSlidePos; } else { - mFirstExpPos = itemPos; - mSecondExpPos = itemPos; + final int above = mDropSlot - 1; + final int below = mDropSlot; + if (below <= first) { + position = first; + } else if (above >= last) { + position = last; + } else { + // Get middle of drop gap. If + // drag point is below, start with position below + // drop gap, b/c it might move up. Otherwise + // choose position above. + final int top = getChildAt(above - first).getBottom(); + final int bottom = getChildAt(below - first).getTop(); + if (mDragY >= (top + bottom) / 2) { + position = below; + } else { + position = above; + } + } } - // correct for headers and footers - if (mFirstExpPos < numHeaders) { - itemPos = numHeaders; - mFirstExpPos = itemPos; - mSecondExpPos = itemPos; - } else if (mSecondExpPos >= getCount() - numFooters) { - itemPos = getCount() - numFooters - 1; - mFirstExpPos = itemPos; - mSecondExpPos = itemPos; + if (position < first) { + position = first; + } else if (position > last) { + position = last; } - if (mFirstExpPos != oldFirstExpPos || mSecondExpPos != oldSecondExpPos - || mSlideFrac != oldSlideFrac) { - updated = true; - } + View child; + final int divHeight = getDividerHeight(); + final int lastHeader = getHeaderViewsCount() - 1; + final int firstFooter = getCount() - getFooterViewsCount(); + int lastRegion = NOWHERE; + int top; + int height; + int topLimit; + int bottomLimit; + int mid; + int slideHeight; + int slideTop; + int slideBottom; + int offset; + //Log.d("mobeta", "shuffle"); + while (position <= last && position >= first) { + //Log.d("mobeta", " position="+position+" first="+first); + // + // |----------| + // | | + // |----------| topLimit + // |||||||||||| + // |----------| + // | position | + // |----------| + // |||||||||||| + // |----------| bottomLimit + // | | + // |----------| + // + // topLimit is farthest we can shift item at position + // up. bottomLimit is farthest we can shift item + // down. + // + + child = getChildAt(position - first); + top = child.getTop(); + height = child.getHeight(); + //Log.d("mobeta", " child top="+top+" height="+height); + + // Set top and bottom slide limits. + topLimit = top; + bottomLimit = top + height; + for (Gap gap : mGaps.values()) { + final int shift = gap.height - gap.minHeight; + if (gap.isAbove(position)) { + topLimit -= shift; + } else { + bottomLimit += shift; + } + } + //Log.d("mobeta", " topLimit="+topLimit+" bottomLimit="+bottomLimit); + + mid = (topLimit + bottomLimit) / 2; + + slideHeight = (int) (mSlideRegionFrac * (height + divHeight)); + slideTop = mid - slideHeight / 2; + slideBottom = slideTop + slideHeight; + //Log.d("mobeta", " slideTop="+slideTop+" slideBottom="+slideBottom); - if (itemPos != mFloatPos) { - if (mDragListener != null) { - mDragListener.drag(mFloatPos - numHeaders, itemPos - numHeaders); + if (mDragY >= slideBottom) { + //Log.d("mobeta", " below slide region"); + if (lastRegion == ABOVE) { + mDropSlot = position + 1; + break; + } + // Item at position needs to slide up as far as it can. + offset = topLimit - top; + + // Remove/Update gaps above + final int removedHeight = minimizeGapsAbove(position); + // Add/Update gap below + extendGapBelowBy(position, removedHeight); + + // Offset child + child.offsetTopAndBottom(offset); + mSlidePos = INVALID_POSITION; + lastRegion = BELOW; + position++; + } else if (mDragY < slideTop) { + //Log.d("mobeta", " above slide region"); + if (lastRegion == BELOW) { + mDropSlot = position; + break; + } + // Item at position needs to slide down as far as it can. + offset = bottomLimit - height - top; + + // Adjust gaps + final int removedHeight = minimizeGapsBelow(position); + extendGapAboveBy(position, removedHeight); + + // Offset the View. + child.offsetTopAndBottom(offset); + + mSlidePos = INVALID_POSITION; + lastRegion = ABOVE; + position--; + } else { + final float frac = ((float) (slideBottom - mDragY)) / ((float) slideHeight); + //Log.d("mobeta", " in slide region, frac="+frac); + offset = topLimit + (int) (frac * (bottomLimit - height - topLimit)) - top; + final int gapHeightAbove = top + offset - topLimit; + final int gapHeightBelow = bottomLimit - (top + offset + height); + + // Remove/Update gaps if they are not immediately + // above/below the current position. + minimizeGapsAbove(position - 1); + minimizeGapsBelow(position + 1); + + // Set the gaps above/below the sliding position. + Gap gap = getGapAbove(position); + if (gap == null) { + insertGapAbove(position, gapHeightAbove, 0); + } else { + gap.height = gap.minHeight + gapHeightAbove; + } + gap = getGapBelow(position); + if (gap == null) { + insertGapBelow(position, gapHeightBelow, 0); + } else { + gap.height = gap.minHeight + gapHeightBelow; + } + + child.offsetTopAndBottom(offset); + mDropSlot = mDragY >= mid ? position + 1 : position; + mSlidePos = position; + break; } - mFloatPos = itemPos; - updated = true; + if (position == firstFooter) { + mDropSlot = firstFooter; + break; + } else if (position == lastHeader) { + mDropSlot = lastHeader + 1; + break; + } } + //Log.d("mobeta", " "); - return updated; + invalidate(); } + private float frameCount = 0f; + private long frDt = 500; // ms + private long frStart = 0; + private boolean frShow = true; + @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); + if (frShow) { + final long now = SystemClock.uptimeMillis(); + if (now - frStart > frDt) { + Log.d("mobeta", "frame rate = " + (1000f * frameCount / ((float) (now - frStart)))); + frameCount = 0f; + frStart = now; + } else { + frameCount += 1f; + } + } + if (mTrackDragSort) { mDragSortTracker.appendState(); } @@ -1160,7 +1387,7 @@ public void onUpdate(float frac, float smoothFrac) { mDragDeltaY = (int) (smoothFrac * mFinalDragDeltaY + (1f - smoothFrac) * mInitDragDeltaY); mFloatLoc.y = mY - mDragDeltaY; - doDragFloatView(true); + updateShuffle2(); } } } @@ -1170,19 +1397,18 @@ public void onUpdate(float frac, float smoothFrac) { */ private class DropAnimator extends SmoothAnimator { - private int mDropPos; - private int srcPos; private float mInitDeltaY; private float mInitDeltaX; + private Gap mDropGap; + public DropAnimator(float smoothness, int duration) { super(smoothness, duration); } @Override public void onStart() { - mDropPos = mFloatPos; - srcPos = mSrcPos; + mDropGap = findGapByIndex(mDropSlot); mDragState = DROPPING; mInitDeltaY = mFloatLoc.y - getTargetY(); mInitDeltaX = mFloatLoc.x - getPaddingLeft(); @@ -1190,25 +1416,13 @@ public void onStart() { private int getTargetY() { final int first = getFirstVisiblePosition(); - final int otherAdjust = (mItemHeightCollapsed + getDividerHeight()) / 2; - View v = getChildAt(mDropPos - first); - int targetY = -1; - if (v != null) { - if (mDropPos == srcPos) { - targetY = v.getTop(); - } else if (mDropPos < srcPos) { - // expanded down - targetY = v.getTop() - otherAdjust; - } else { - // expanded up - targetY = v.getBottom() + otherAdjust - mFloatViewHeight; - } + final int above = mDropGap.getPositionAbove(); + if (above >= 0) { + final int y = getItemBottom(above) + getDividerHeight(); + return y > 0 ? y : 0; } else { - // drop position is not on screen?? no animation - cancel(); + return 0; } - - return targetY; } @Override @@ -1221,7 +1435,8 @@ public void onUpdate(float frac, float smoothFrac) { if (f < Math.abs(deltaY / mInitDeltaY) || f < Math.abs(deltaX / mInitDeltaX)) { mFloatLoc.y = targetY + (int) (mInitDeltaY * f); mFloatLoc.x = getPaddingLeft() + (int) (mInitDeltaX * f); - doDragFloatView(true); + updateFloatView(); + updateShuffle2(); } } @@ -1238,15 +1453,7 @@ public void onStop() { private class RemoveAnimator extends SmoothAnimator { private float mFloatLocX; - private float mFirstStartBlank; - private float mSecondStartBlank; - - private int mFirstChildHeight = -1; - private int mSecondChildHeight = -1; - - private int mFirstPos; - private int mSecondPos; - private int srcPos; + private float mLastFrac; public RemoveAnimator(float smoothness, int duration) { super(smoothness, duration); @@ -1254,11 +1461,7 @@ public RemoveAnimator(float smoothness, int duration) { @Override public void onStart() { - mFirstChildHeight = -1; - mSecondChildHeight = -1; - mFirstPos = mFirstExpPos; - mSecondPos = mSecondExpPos; - srcPos = mSrcPos; + mLastFrac = 1f; mDragState = REMOVING; mFloatLocX = mFloatLoc.x; @@ -1282,11 +1485,6 @@ else if (mRemoveVelocityX > 0 && mRemoveVelocityX < minVelocity) public void onUpdate(float frac, float smoothFrac) { float f = 1f - smoothFrac; - final int firstVis = getFirstVisiblePosition(); - View item = getChildAt(mFirstPos - firstVis); - ViewGroup.LayoutParams lp; - int blank; - if (mUseRemoveVelocity) { float dt = (float) (SystemClock.uptimeMillis() - mStartTime) / 1000; if (dt == 0) @@ -1298,48 +1496,49 @@ public void onUpdate(float frac, float smoothFrac) { mFloatLoc.x = (int) mFloatLocX; if (mFloatLocX < w && mFloatLocX > -w) { mStartTime = SystemClock.uptimeMillis(); - doDragFloatView(true); + updateShuffle2(); + invalidate(); return; } } - if (item != null) { - if (mFirstChildHeight == -1) { - mFirstChildHeight = getChildHeight(mFirstPos, item, false); - mFirstStartBlank = (float) (item.getHeight() - mFirstChildHeight); - } - blank = Math.max((int) (f * mFirstStartBlank), 1); - lp = item.getLayoutParams(); - lp.height = mFirstChildHeight + blank; - item.setLayoutParams(lp); - } - if (mSecondPos != mFirstPos) { - item = getChildAt(mSecondPos - firstVis); - if (item != null) { - if (mSecondChildHeight == -1) { - mSecondChildHeight = getChildHeight(mSecondPos, item, false); - mSecondStartBlank = (float) (item.getHeight() - mSecondChildHeight); - } - blank = Math.max((int) (f * mSecondStartBlank), 1); - lp = item.getLayoutParams(); - lp.height = mSecondChildHeight + blank; - item.setLayoutParams(lp); - } + for (Gap gap : mGaps.values()) { + gap.height = (int) (gap.height * (f / mLastFrac)); } + + mLastFrac = f; + requestLayout(); } @Override public void onStop() { + removeGaps(); doRemoveItem(); } } public void removeItem(int which) { - mUseRemoveVelocity = false; removeItem(which, 0); } + private void hideItem(int position, int minHeight) { + + final int height = getItemHeight(position) + getDividerHeight(); + if (position == 0) { + insertGapAbove(0, height, minHeight); + mAnchorPos = 0; + mAnchorTop = getItemTop(1); + } else { + insertGapBelow(position - 1, height, minHeight); + mAnchorPos = position - 1; + mAnchorTop = getItemTop(mAnchorPos); + } + + mHideAdapter.hide(position); + layoutChildren(); + } + /** * Removes an item from the list and animates the removal. * @@ -1352,14 +1551,9 @@ public void removeItem(int which, float velocityX) { if (mDragState == IDLE) { // called from outside drag-sort - mSrcPos = getHeaderViewsCount() + which; - mFirstExpPos = mSrcPos; - mSecondExpPos = mSrcPos; - mFloatPos = mSrcPos; - View v = getChildAt(mSrcPos - getFirstVisiblePosition()); - if (v != null) { - v.setVisibility(View.INVISIBLE); - } + mSrcSlot = getHeaderViewsCount() + which; + mDropSlot = mSrcSlot; + hideItem(mSrcSlot, 0); } mDragState = REMOVING; @@ -1410,8 +1604,9 @@ public void cancelDrag() { if (mDragState == DRAGGING) { mDragScroller.stopScrolling(true); destroyFloatView(); - clearPositions(); - adjustAllItems(); + clearSlots(); + removeGaps(); + requestLayout(); if (mInTouchEvent) { mDragState = STOPPED; @@ -1421,11 +1616,9 @@ public void cancelDrag() { } } - private void clearPositions() { - mSrcPos = -1; - mFirstExpPos = -1; - mSecondExpPos = -1; - mFloatPos = -1; + private void clearSlots() { + mSrcSlot = -1; + mDropSlot = -1; } private void dropFloatView() { @@ -1433,16 +1626,18 @@ private void dropFloatView() { // DataSetObserver mDragState = DROPPING; - if (mDropListener != null && mFloatPos >= 0 && mFloatPos < getCount()) { + if (mDropListener != null) { final int numHeaders = getHeaderViewsCount(); - mDropListener.drop(mSrcPos - numHeaders, mFloatPos - numHeaders); + mDropListener.drop(mSrcSlot - numHeaders, mDropSlot - numHeaders); } + mHideAdapter.reveal(); + removeGaps(); + destroyFloatView(); - adjustOnReorder(); - clearPositions(); - adjustAllItems(); + //adjustOnReorder(); + clearSlots(); // now the drag is done if (mInTouchEvent) { @@ -1453,7 +1648,7 @@ private void dropFloatView() { } private void doRemoveItem() { - doRemoveItem(mSrcPos - getHeaderViewsCount()); + doRemoveItem(mSrcSlot - getHeaderViewsCount()); } /** @@ -1469,10 +1664,11 @@ private void doRemoveItem(int which) { mRemoveListener.remove(which); } - destroyFloatView(); + mHideAdapter.reveal(); + removeGaps(); - adjustOnReorder(); - clearPositions(); + destroyFloatView(); + clearSlots(); // now the drag is done if (mInTouchEvent) { @@ -1484,8 +1680,8 @@ private void doRemoveItem(int which) { private void adjustOnReorder() { final int firstPos = getFirstVisiblePosition(); - // Log.d("mobeta", "first="+firstPos+" src="+mSrcPos); - if (mSrcPos < firstPos) { + // Log.d("mobeta", "first="+firstPos+" src="+mSrcSlot); + if (mSrcSlot < firstPos) { // collapsed src item is off screen; // adjust the scroll after item heights have been fixed View v = getChildAt(0); @@ -1525,7 +1721,7 @@ public boolean stopDrag(boolean remove, float velocityX) { mDragScroller.stopScrolling(true); if (remove) { - removeItem(mSrcPos - getHeaderViewsCount(), velocityX); + removeItem(mSrcSlot - getHeaderViewsCount(), velocityX); } else { if (mDropAnimator != null) { mDropAnimator.start(); @@ -1604,7 +1800,7 @@ private void doActionUpOrCancel() { mDragState = IDLE; } mCurrFloatAlpha = mFloatAlpha; - mChildHeightCache.clear(); + mItemHeightCache.clear(); } private void saveTouchCoords(MotionEvent ev) { @@ -1619,6 +1815,7 @@ private void saveTouchCoords(MotionEvent ev) { mLastX = mX; mLastY = mY; } + //Log.d("mobeta", "saving touch x="+mX+" y="+mY); mOffsetX = (int) ev.getRawX() - mX; mOffsetY = (int) ev.getRawY() - mY; } @@ -1721,10 +1918,15 @@ private void continueDrag(int x, int y) { mFloatLoc.x = x - mDragDeltaX; mFloatLoc.y = y - mDragDeltaY; - doDragFloatView(true); + // Set the new float view position + updateFloatView(); + + // Move the items around + updateShuffle2(); - int minY = Math.min(y, mFloatViewMid + mFloatViewHeightHalf); - int maxY = Math.max(y, mFloatViewMid - mFloatViewHeightHalf); + // Now check for drag-scrolls. + int minY = Math.min(y, mDragY + mFloatViewHeightHalf); + int maxY = Math.max(y, mDragY - mFloatViewHeightHalf); // get the current scroll direction int currentScrollDir = mDragScroller.getScrollDir(); @@ -1782,195 +1984,6 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { updateScrollStarts(); } - private void adjustAllItems() { - final int first = getFirstVisiblePosition(); - final int last = getLastVisiblePosition(); - - int begin = Math.max(0, getHeaderViewsCount() - first); - int end = Math.min(last - first, getCount() - 1 - getFooterViewsCount() - first); - - for (int i = begin; i <= end; ++i) { - View v = getChildAt(i); - if (v != null) { - adjustItem(first + i, v, false); - } - } - } - - private void adjustItem(int position) { - View v = getChildAt(position - getFirstVisiblePosition()); - - if (v != null) { - adjustItem(position, v, false); - } - } - - /** - * Sets layout param height, gravity, and visibility on - * wrapped item. - */ - private void adjustItem(int position, View v, boolean invalidChildHeight) { - - // Adjust item height - ViewGroup.LayoutParams lp = v.getLayoutParams(); - int height; - if (position != mSrcPos && position != mFirstExpPos && position != mSecondExpPos) { - height = ViewGroup.LayoutParams.WRAP_CONTENT; - } else { - height = calcItemHeight(position, v, invalidChildHeight); - } - - if (height != lp.height) { - lp.height = height; - v.setLayoutParams(lp); - } - - // Adjust item gravity - if (position == mFirstExpPos || position == mSecondExpPos) { - if (position < mSrcPos) { - ((DragSortItemView) v).setGravity(Gravity.BOTTOM); - } else if (position > mSrcPos) { - ((DragSortItemView) v).setGravity(Gravity.TOP); - } - } - - // Finally adjust item visibility - - int oldVis = v.getVisibility(); - int vis = View.VISIBLE; - - if (position == mSrcPos && mFloatView != null) { - vis = View.INVISIBLE; - } - - if (vis != oldVis) { - v.setVisibility(vis); - } - } - - private int getChildHeight(int position) { - if (position == mSrcPos) { - return 0; - } - - View v = getChildAt(position - getFirstVisiblePosition()); - - if (v != null) { - // item is onscreen, therefore child height is valid, - // hence the "true" - return getChildHeight(position, v, false); - } else { - // item is offscreen - // first check cache for child height at this position - int childHeight = mChildHeightCache.get(position); - if (childHeight != -1) { - // Log.d("mobeta", "found child height in cache!"); - return childHeight; - } - - final ListAdapter adapter = getAdapter(); - int type = adapter.getItemViewType(position); - - // There might be a better place for checking for the following - final int typeCount = adapter.getViewTypeCount(); - if (typeCount != mSampleViewTypes.length) { - mSampleViewTypes = new View[typeCount]; - } - - if (type >= 0) { - if (mSampleViewTypes[type] == null) { - v = adapter.getView(position, null, this); - mSampleViewTypes[type] = v; - } else { - v = adapter.getView(position, mSampleViewTypes[type], this); - } - } else { - // type is HEADER_OR_FOOTER or IGNORE - v = adapter.getView(position, null, this); - } - - // current child height is invalid, hence "true" below - childHeight = getChildHeight(position, v, true); - - // cache it because this could have been expensive - mChildHeightCache.add(position, childHeight); - - return childHeight; - } - } - - private int getChildHeight(int position, View item, boolean invalidChildHeight) { - if (position == mSrcPos) { - return 0; - } - - View child; - if (position < getHeaderViewsCount() || position >= getCount() - getFooterViewsCount()) { - child = item; - } else { - child = ((ViewGroup) item).getChildAt(0); - } - - ViewGroup.LayoutParams lp = child.getLayoutParams(); - - if (lp != null) { - if (lp.height > 0) { - return lp.height; - } - } - - int childHeight = child.getHeight(); - - if (childHeight == 0 || invalidChildHeight) { - measureItem(child); - childHeight = child.getMeasuredHeight(); - } - - return childHeight; - } - - private int calcItemHeight(int position, View item, boolean invalidChildHeight) { - return calcItemHeight(position, getChildHeight(position, item, invalidChildHeight)); - } - - private int calcItemHeight(int position, int childHeight) { - - int divHeight = getDividerHeight(); - - boolean isSliding = mAnimate && mFirstExpPos != mSecondExpPos; - int maxNonSrcBlankHeight = mFloatViewHeight - mItemHeightCollapsed; - int slideHeight = (int) (mSlideFrac * maxNonSrcBlankHeight); - - int height; - - if (position == mSrcPos) { - if (mSrcPos == mFirstExpPos) { - if (isSliding) { - height = slideHeight + mItemHeightCollapsed; - } else { - height = mFloatViewHeight; - } - } else if (mSrcPos == mSecondExpPos) { - // if gets here, we know an item is sliding - height = mFloatViewHeight - slideHeight; - } else { - height = mItemHeightCollapsed; - } - } else if (position == mFirstExpPos) { - if (isSliding) { - height = childHeight + slideHeight; - } else { - height = childHeight + maxNonSrcBlankHeight; - } - } else if (position == mSecondExpPos) { - // we know an item is sliding (b/c 2ndPos != 1stPos) - height = childHeight + maxNonSrcBlankHeight - slideHeight; - } else { - height = childHeight; - } - - return height; - } @Override public void requestLayout() { @@ -1979,49 +1992,6 @@ public void requestLayout() { } } - private int adjustScroll(int movePos, View moveItem, int oldFirstExpPos, int oldSecondExpPos) { - int adjust = 0; - - final int childHeight = getChildHeight(movePos); - - int moveHeightBefore = moveItem.getHeight(); - int moveHeightAfter = calcItemHeight(movePos, childHeight); - - int moveBlankBefore = moveHeightBefore; - int moveBlankAfter = moveHeightAfter; - if (movePos != mSrcPos) { - moveBlankBefore -= childHeight; - moveBlankAfter -= childHeight; - } - - int maxBlank = mFloatViewHeight; - if (mSrcPos != mFirstExpPos && mSrcPos != mSecondExpPos) { - maxBlank -= mItemHeightCollapsed; - } - - if (movePos <= oldFirstExpPos) { - if (movePos > mFirstExpPos) { - adjust += maxBlank - moveBlankAfter; - } - } else if (movePos == oldSecondExpPos) { - if (movePos <= mFirstExpPos) { - adjust += moveBlankBefore - maxBlank; - } else if (movePos == mSecondExpPos) { - adjust += moveHeightBefore - moveHeightAfter; - } else { - adjust += moveBlankBefore; - } - } else { - if (movePos <= mFirstExpPos) { - adjust -= maxBlank; - } else if (movePos == mSecondExpPos) { - adjust -= moveBlankAfter; - } - } - - return adjust; - } - private void measureItem(View item) { ViewGroup.LayoutParams lp = item.getLayoutParams(); if (lp == null) { @@ -2060,11 +2030,88 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mWidthMeasureSpec = widthMeasureSpec; } + private int mAnchorPos = INVALID_POSITION; + private int mAnchorTop = 0; + private static final int INVALID_POSITION = AdapterView.INVALID_POSITION; + @Override protected void layoutChildren() { + //Log.d("mobeta", "layout children, t="+SystemClock.uptimeMillis()); + if (mGaps.size() != 0) { + if (mAnchorPos == INVALID_POSITION) { + final int first = getFirstVisiblePosition(); + mAnchorPos = first + getChildCount() / 2; + mAnchorTop = getChildAt(mAnchorPos - first).getTop(); + } + //Log.d("mobeta", " want anchor pos="+mAnchorPos+" top="+mAnchorTop); + + mBlockLayoutRequests = true; + setSelectionFromTop(mAnchorPos, mAnchorTop - getPaddingTop()); + mBlockLayoutRequests = false; + } + super.layoutChildren(); + if (mGaps.size() != 0) { + final int first = getFirstVisiblePosition(); + final int last = getLastVisiblePosition(); + + // setSelectionFromTop() may not have taken (if we + // are scrolled to the bottom for example). + int shiftAll = 0; + final View anchor = getChildAt(mAnchorPos - first); + if (anchor != null) { + final int anchorTop = anchor.getTop(); + if (anchorTop != mAnchorTop) { + shiftAll = mAnchorTop - anchorTop; + anchor.offsetTopAndBottom(shiftAll); + } + //Log.d("mobeta", " got anchor pos="+mAnchorPos+" top="+anchorTop+". shift all by "+shiftAll); + } + + int shift = shiftAll; + // first go down from move position + for (int position = mAnchorPos + 1; position <= last; ++position) { + final Gap gap = getGapAbove(position); + if (gap != null) { + shift += gap.height; + } + + if (shift != 0) { + final View child = getChildAt(position - first); + if (child != null) { + child.offsetTopAndBottom(shift); + } + } + } + + shift = shiftAll; + // go up from move position + for (int position = mAnchorPos - 1; position >= first; --position) { + final Gap gap = getGapBelow(position); + if (gap != null) { + shift -= gap.height; + } + + if (shift != 0) { + final View child = getChildAt(position - first); + if (child != null) { + child.offsetTopAndBottom(shift); + } + } + } + + if (mFloatView != null) { + updateShuffle2(); + } + + mAnchorPos = INVALID_POSITION; + mBlockLayoutRequests = false; + } + if (mFloatView != null) { + updateFloatView(); + if (mFloatView.isLayoutRequested() && !mFloatViewOnMeasured) { // Have to measure here when usual android measure // pass is skipped. This happens during a drag-sort @@ -2179,10 +2226,8 @@ public boolean startDrag(int position, View floatView, int dragFlags, int deltaX } int pos = position + getHeaderViewsCount(); - mFirstExpPos = pos; - mSecondExpPos = pos; - mSrcPos = pos; - mFloatPos = pos; + mSrcSlot = pos; + mDropSlot = pos; // mDragState = dragType; mDragState = DRAGGING; @@ -2199,13 +2244,10 @@ public boolean startDrag(int position, View floatView, int dragFlags, int deltaX // updateFloatView(mX - mDragDeltaX, mY - mDragDeltaY); mFloatLoc.x = mX - mDragDeltaX; mFloatLoc.y = mY - mDragDeltaY; + updateFloatView(); - // set src item invisible - final View srcItem = getChildAt(mSrcPos - getFirstVisiblePosition()); - - if (srcItem != null) { - srcItem.setVisibility(View.INVISIBLE); - } + // set gap + hideItem(position, mItemHeightCollapsed); if (mTrackDragSort) { mDragSortTracker.startTracking(); @@ -2222,8 +2264,6 @@ public boolean startDrag(int position, View floatView, int dragFlags, int deltaX break; } - requestLayout(); - if (mLiftAnimator != null) { mLiftAnimator.start(); } @@ -2231,42 +2271,6 @@ public boolean startDrag(int position, View floatView, int dragFlags, int deltaX return true; } - private void doDragFloatView(boolean forceInvalidate) { - int movePos = getFirstVisiblePosition() + getChildCount() / 2; - View moveItem = getChildAt(getChildCount() / 2); - - if (moveItem == null) { - return; - } - - doDragFloatView(movePos, moveItem, forceInvalidate); - } - - private void doDragFloatView(int movePos, View moveItem, boolean forceInvalidate) { - mBlockLayoutRequests = true; - - updateFloatView(); - - int oldFirstExpPos = mFirstExpPos; - int oldSecondExpPos = mSecondExpPos; - - boolean updated = updatePositions(); - - if (updated) { - adjustAllItems(); - int scroll = adjustScroll(movePos, moveItem, oldFirstExpPos, oldSecondExpPos); - // Log.d("mobeta", " adjust scroll="+scroll); - - setSelectionFromTop(movePos, moveItem.getTop() + scroll - getPaddingTop()); - layoutChildren(); - } - - if (updated || forceInvalidate) { - invalidate(); - } - - mBlockLayoutRequests = false; - } /** * Sets float View location based on suggested values and @@ -2303,19 +2307,21 @@ private void updateFloatView() { topLimit = getChildAt(numHeaders - firstPos - 1).getBottom(); } if ((mDragFlags & DRAG_NEG_Y) == 0) { - if (firstPos <= mSrcPos) { - topLimit = Math.max(getChildAt(mSrcPos - firstPos).getTop(), topLimit); + if (firstPos <= mSrcSlot) { + topLimit = Math.max(getChildAt(mSrcSlot - firstPos).getTop(), topLimit); } } // bottom limit is top of first footer View or // bottom of last item in list + final int divHeight = getDividerHeight(); + final int lastNonFooter = getCount() - 1 - numFooters; int bottomLimit = getHeight() - getPaddingBottom(); - if (lastPos >= getCount() - numFooters - 1) { - bottomLimit = getChildAt(getCount() - numFooters - 1 - firstPos).getBottom(); + if (lastPos > lastNonFooter) { + bottomLimit = getChildAt(lastNonFooter + 1 - firstPos).getTop() - divHeight; } if ((mDragFlags & DRAG_POS_Y) == 0) { - if (lastPos >= mSrcPos) { - bottomLimit = Math.min(getChildAt(mSrcPos - firstPos).getBottom(), bottomLimit); + if (lastPos >= mSrcSlot) { + bottomLimit = Math.min(getChildAt(mSrcSlot - firstPos).getBottom(), bottomLimit); } } @@ -2330,7 +2336,7 @@ private void updateFloatView() { } // get y-midpoint of floating view (constrained to ListView bounds) - mFloatViewMid = mFloatLoc.y + mFloatViewHeightHalf; + mDragY = mFloatLoc.y + mFloatViewHeightHalf; } private void destroyFloatView() { @@ -2802,8 +2808,8 @@ public void run() { final int padTop = getPaddingTop(); final int listHeight = getHeight() - padTop - getPaddingBottom(); - int minY = Math.min(mY, mFloatViewMid + mFloatViewHeightHalf); - int maxY = Math.max(mY, mFloatViewMid - mFloatViewHeightHalf); + int minY = Math.min(mY, mDragY + mFloatViewHeightHalf); + int maxY = Math.max(mY, mDragY - mFloatViewHeightHalf); if (scrollDir == UP) { View v = getChildAt(0); @@ -2811,11 +2817,9 @@ public void run() { if (v == null) { mScrolling = false; return; - } else { - if (first == 0 && v.getTop() == padTop) { - mScrolling = false; - return; - } + } else if (first == 0 && v.getTop() - getGapHeightAbove(0) == padTop) { + mScrolling = false; + return; } mScrollSpeed = mScrollProfile.getSpeed((mUpScrollStartYF - maxY) / mDragUpScrollHeight, mPrevTime); @@ -2824,11 +2828,10 @@ public void run() { if (v == null) { mScrolling = false; return; - } else { - if (last == count - 1 && v.getBottom() <= listHeight + padTop) { - mScrolling = false; - return; - } + } else if (last == count - 1 && + (v.getBottom() + getGapHeightBelow(last) <= listHeight + padTop)) { + mScrolling = false; + return; } mScrollSpeed = -mScrollProfile.getSpeed((minY - mDownScrollStartYF) / mDragDownScrollHeight, mPrevTime); @@ -2842,38 +2845,36 @@ public void run() { // remember // y=0 is at top of View). dy = (int) Math.round(mScrollSpeed * dt); + //Log.d("mobeta", "scrolling speed="+mScrollSpeed+" dt="+dt+" dy="+dy); - int movePos; if (dy >= 0) { dy = Math.min(listHeight, dy); - movePos = first; + mAnchorPos = first; } else { dy = Math.max(-listHeight, dy); - movePos = last; + mAnchorPos = last; } + //Log.d("mobeta", "scrolling mod-dy="+dy+" padtop="+padTop); - final View moveItem = getChildAt(movePos - first); - int top = moveItem.getTop() + dy; + final View anchor = getChildAt(mAnchorPos - first); + mAnchorTop = anchor.getTop() + dy; + //Log.d("mobeta", "scroll: top="+anchor.getTop()+" newtop="+mAnchorTop); - if (movePos == 0 && top > padTop) { - top = padTop; + if (mAnchorPos == 0) { + final int gapHeight = getGapHeightAbove(0); + if (mAnchorTop - gapHeight > padTop) { + mAnchorTop = gapHeight + padTop; + } + } else if (mAnchorPos == count - 1) { + final int gapHeight = getGapHeightBelow(count - 1); + if (anchor.getBottom() + gapHeight < listHeight) { + mAnchorTop = padTop + listHeight - gapHeight - anchor.getHeight(); + } } - // always do scroll - mBlockLayoutRequests = true; - - setSelectionFromTop(movePos, top - padTop); - DragSortListView.this.layoutChildren(); - invalidate(); - - mBlockLayoutRequests = false; - - // scroll means relative float View movement - doDragFloatView(movePos, moveItem, false); - + layoutChildren(); mPrevTime = mCurrTime; // Log.d("mobeta", " updated prevTime="+mPrevTime); - post(this); } } @@ -2935,26 +2936,12 @@ public void appendState() { } mBuilder.append("\n"); - mBuilder.append(" ").append(mFirstExpPos).append("\n"); - mBuilder.append(" ") - .append(getItemHeight(mFirstExpPos) - getChildHeight(mFirstExpPos)) - .append("\n"); - mBuilder.append(" ").append(mSecondExpPos).append("\n"); - mBuilder.append(" ") - .append(getItemHeight(mSecondExpPos) - getChildHeight(mSecondExpPos)) - .append("\n"); - mBuilder.append(" ").append(mSrcPos).append("\n"); + mBuilder.append(" ").append(mSrcSlot).append("\n"); mBuilder.append(" ").append(mFloatViewHeight + getDividerHeight()) .append("\n"); mBuilder.append(" ").append(getHeight()).append("\n"); mBuilder.append(" ").append(mLastY).append("\n"); - mBuilder.append(" ").append(mFloatViewMid).append("\n"); - mBuilder.append(" "); - for (int i = 0; i < children; ++i) { - mBuilder.append(getShuffleEdge(first + i, getChildAt(i).getTop())).append(","); - } - mBuilder.append("\n"); - + mBuilder.append(" ").append(mDragY).append("\n"); mBuilder.append("\n"); mNumInBuffer++; diff --git a/library/src/com/mobeta/android/dslv/HideAdapter.java b/library/src/com/mobeta/android/dslv/HideAdapter.java new file mode 100644 index 0000000..77a26fb --- /dev/null +++ b/library/src/com/mobeta/android/dslv/HideAdapter.java @@ -0,0 +1,141 @@ +package com.mobeta.android.dslv; + +import android.database.DataSetObserver; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListAdapter; +import android.widget.AdapterView; + +public class HideAdapter extends BaseAdapter { + + private ListAdapter mAdapter; + + private int mHiddenPos = AdapterView.INVALID_POSITION; + + public HideAdapter(ListAdapter adapter) { + mAdapter = adapter; + } + + public void hide(int position) { + if (mHiddenPos == AdapterView.INVALID_POSITION) { + if (position >= 0 && position < getCount()) { + mHiddenPos = position; + notifyDataSetChanged(); + } + } + } + + public void reveal() { + if (mHiddenPos != AdapterView.INVALID_POSITION) { + mHiddenPos = AdapterView.INVALID_POSITION; + notifyDataSetChanged(); + } + } + + public boolean isHiding() { + return mHiddenPos != AdapterView.INVALID_POSITION; + } + + public boolean isEmpty() { + return mAdapter == null || mAdapter.isEmpty(); + } + + public int getCount() { + if (mAdapter != null) { + if (mHiddenPos != AdapterView.INVALID_POSITION) { + return mAdapter.getCount() - 1; + } else { + return mAdapter.getCount(); + } + } else { + return 0; + } + } + + public boolean areAllItemsEnabled() { + if (mAdapter != null) { + return mAdapter.areAllItemsEnabled(); + } else { + return true; + } + } + + private int wrappedPosition(int position) { + if (mHiddenPos == AdapterView.INVALID_POSITION || position < mHiddenPos) { + return position; + } else { + return position + 1; + } + } + + public boolean isEnabled(int position) { + if (mAdapter != null) { + return mAdapter.isEnabled(wrappedPosition(position)); + } else { + return false; + } + } + + public Object getItem(int position) { + if (mAdapter != null) { + return mAdapter.getItem(wrappedPosition(position)); + } else { + return null; + } + } + + public long getItemId(int position) { + if (mAdapter != null) { + return mAdapter.getItemId(wrappedPosition(position)); + } + return -1; + } + + public boolean hasStableIds() { + if (mAdapter != null) { + return mAdapter.hasStableIds(); + } + return false; + } + + public View getView(int position, View convertView, ViewGroup parent) { + if (mAdapter != null) { + return mAdapter.getView(wrappedPosition(position), convertView, parent); + } + return null; + } + + public int getItemViewType(int position) { + if (mAdapter != null) { + return mAdapter.getItemViewType(wrappedPosition(position)); + } + return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER; + } + + public int getViewTypeCount() { + if (mAdapter != null) { + return mAdapter.getViewTypeCount(); + } + return 1; + } + + public void registerDataSetObserver(DataSetObserver observer) { + super.registerDataSetObserver(observer); + if (mAdapter != null) { + mAdapter.registerDataSetObserver(observer); + } + } + + public void unregisterDataSetObserver(DataSetObserver observer) { + super.unregisterDataSetObserver(observer); + if (mAdapter != null) { + mAdapter.unregisterDataSetObserver(observer); + } + } + + public ListAdapter getAdapter() { + return mAdapter; + } +} + diff --git a/pom.xml b/pom.xml index f8b156b..01ab7cb 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ Copyright 2012 Andreas Schildbach com.jayway.maven.plugins.android.generation2 android-maven-plugin - 3.3.2 + 3.8.0 true