Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 72418af9 authored by Hongwei Wang's avatar Hongwei Wang
Browse files

Realtime drop area

Bug: 10686781

Improved also the scroll on drag-n-drop and NOTE ALSO

- The animations are not in, neither the lift up nor drop in
- Pos >= 20 is not draggable, nor droppable
- The implementation of D-N-D may need a refactor
- The refactor will use the ListView as the only D-N-D handler
- The refactor will remove the drag handleer from individual row
- The refactor will make it possible to do animation on the drag shadow

Change-Id: Ic7c81a9a139eb46ef1820192528da14e5d8e52cb
parent 42d49517
Loading
Loading
Loading
Loading
+45 −45
Original line number Diff line number Diff line
@@ -48,14 +48,12 @@ public class PhoneFavoriteDragAndDropListeners {
            mTileAdapter = tileAdapter;
        }

        @Override
        public boolean onDrag(View v, DragEvent event) {
            if (DEBUG) {
                Log.v(TAG, event.toString());
            }
            // Handles drag events.
            switch (event.getAction()) {
                case DragEvent.ACTION_DRAG_STARTED:
        /**
         * @return The item index in {@link #mTileAdapter} for the given {@link DragEvent}.
         *     Returns -1 if {@link #mTileAdapter} is not in dragging or index can not be found.
         */
        private int getDragItemIndex(DragEvent event) {
            int itemIndex = -1;
            if (mTileAdapter != null && mContactTileRow != null
                    && !mTileAdapter.getInDragging()) {
                mX = event.getX();
@@ -74,17 +72,31 @@ public class PhoneFavoriteDragAndDropListeners {
                if (locationRect.contains((int) mX, (int) mY)) {
                    // Finds out which item is being dragged.
                    // Computes relative coordinates as we get absolute coordinates.
                            final int dragIndex = mContactTileRow.getItemIndex(
                    itemIndex = mContactTileRow.getItemIndex(
                            mX - rowLocation[0], mY - rowLocation[1]);
                    if (DEBUG) {
                                Log.v(TAG, "Start dragging " + String.valueOf(dragIndex));
                        Log.v(TAG, "Start dragging " + String.valueOf(itemIndex));
                    }
                }
            }
            return itemIndex;
        }

        @Override
        public boolean onDrag(View v, DragEvent event) {
            if (DEBUG) {
                Log.v(TAG, event.toString());
            }
            // Handles drag events.
            switch (event.getAction()) {
                case DragEvent.ACTION_DRAG_STARTED:
                    final int itemIndex = getDragItemIndex(event);
                    if (itemIndex != -1) {
                        // Indicates a drag has started.
                        mTileAdapter.setInDragging(true);

                        // Temporarily pops out the Contact entry.
                            mTileAdapter.popContactEntry(dragIndex);
                        }
                        mTileAdapter.popContactEntry(itemIndex);
                    }
                    break;
                case DragEvent.ACTION_DRAG_ENTERED:
@@ -92,33 +104,21 @@ public class PhoneFavoriteDragAndDropListeners {
                case DragEvent.ACTION_DRAG_EXITED:
                    break;
                case DragEvent.ACTION_DROP:
                    mX = event.getX();
                    mY = event.getY();
                    if (DEBUG) {
                        Log.v(TAG, String.valueOf(mX) + "; " + String.valueOf(mY));
                    }

                    // Indicates a drag has finished.
                    if (mTileAdapter != null && mContactTileRow != null) {
                        mTileAdapter.setInDragging(false);

                        // Finds out at which position of the list the Contact is being dropped.
                        final int dropIndex = mContactTileRow.getItemIndex(mX, mY);
                        if (DEBUG) {
                            Log.v(TAG, "Stop dragging " + String.valueOf(dropIndex));
                        }

                        // Adds the dragged contact to the drop position.
                        mTileAdapter.dropContactEntry(dropIndex);
                        // The drop to position has been reported to the adapter
                        // via {@link DragEvent#ACTION_DRAG_LOCATION} events in ListView.
                        mTileAdapter.handleDrop();
                    }
                    break;
                case DragEvent.ACTION_DRAG_ENDED:
                    if (mTileAdapter.getInDragging()) {
                    if (mTileAdapter != null && mTileAdapter.getInDragging()) {
                        // If the drag and drop ends when the drop happens outside of any rows,
                        // we will end the drag here and put the item back to where it was dragged
                        // from before.
                        mTileAdapter.setInDragging(false);
                        mTileAdapter.dropToUnsupportedView();
                        mTileAdapter.handleDrop();
                    }
                    break;
                case DragEvent.ACTION_DRAG_LOCATION:
+50 −15
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import android.util.Log;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnDragListener;
import android.view.ViewConfiguration;
import android.widget.ListView;

@@ -40,8 +39,7 @@ import com.android.dialer.list.SwipeHelper.SwipeHelperCallback;
 * - Swiping, which is borrowed from packages/apps/UnifiedEmail (com.android.mail.ui.Swipeable)
 * - Drag and drop
 */
public class PhoneFavoriteListView extends ListView implements
        SwipeHelperCallback, OnDragListener {
public class PhoneFavoriteListView extends ListView implements SwipeHelperCallback {

    public static final String LOG_TAG = PhoneFavoriteListView.class.getSimpleName();

@@ -61,6 +59,9 @@ public class PhoneFavoriteListView extends ListView implements
    private final long SCROLL_HANDLER_DELAY_MILLIS = 5;
    private final int DRAG_SCROLL_PX_UNIT = 10;

    private boolean mIsDragScrollerRunning = false;
    private int mTouchDownForDragStartY;

    /**
     * {@link #mTopScrollBound} and {@link mBottomScrollBound} will be
     * offseted to the top / bottom by {@link #getHeight} * {@link #BOUND_GAP_RATIO} pixels.
@@ -94,7 +95,6 @@ public class PhoneFavoriteListView extends ListView implements
        mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this,
                mDensityScale, mTouchSlop);
        setItemsCanFocus(true);
        setOnDragListener(this);
    }

    @Override
@@ -123,6 +123,9 @@ public class PhoneFavoriteListView extends ListView implements

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            mTouchDownForDragStartY = (int) ev.getY();
        }
        if (isSwipeEnabled()) {
            return mSwipeHelper.onInterceptTouchEvent(ev) || super.onInterceptTouchEvent(ev);
        } else {
@@ -189,8 +192,7 @@ public class PhoneFavoriteListView extends ListView implements
    }

    @Override
    public void onDragCancelled(View v) {
    }
    public void onDragCancelled(View v) {}

    @Override
    public void onBeginDrag(View v) {
@@ -205,11 +207,16 @@ public class PhoneFavoriteListView extends ListView implements
    public boolean dispatchDragEvent(DragEvent event) {
        switch (event.getAction()) {
            case DragEvent.ACTION_DRAG_LOCATION:
                if (mScrollHandler == null) {
                    mScrollHandler = getHandler();
                }
                mLastDragY = (int) event.getY();
                handleDrag((int) event.getX(), mLastDragY);
                // Kick off {@link #mScrollHandler} if it's not started yet.
                if (!mIsDragScrollerRunning &&
                        // And if the distance traveled while dragging exceeds the touch slop
                        (Math.abs(mLastDragY - mTouchDownForDragStartY) >= 4 * mTouchSlop)) {
                    mIsDragScrollerRunning = true;
                    ensureScrollHandler();
                    mScrollHandler.postDelayed(mDragScroller, SCROLL_HANDLER_DELAY_MILLIS);
                }
                break;
            case DragEvent.ACTION_DRAG_ENTERED:
                final int boundGap = (int) (getHeight() * BOUND_GAP_RATIO);
@@ -218,20 +225,48 @@ public class PhoneFavoriteListView extends ListView implements
                break;
            case DragEvent.ACTION_DRAG_EXITED:
            case DragEvent.ACTION_DRAG_ENDED:
            case DragEvent.ACTION_DROP:
                ensureScrollHandler();
                mScrollHandler.removeCallbacks(mDragScroller);
                mIsDragScrollerRunning = false;
                break;
            case DragEvent.ACTION_DRAG_STARTED:
                // Not a receiver
            case DragEvent.ACTION_DROP:
                // Not a receiver
            default:
                break;
        }
        return super.dispatchDragEvent(event);
    }

    @Override
    public boolean onDrag(View v, DragEvent event) {
        return true;
    private void ensureScrollHandler() {
        if (mScrollHandler == null) {
            mScrollHandler = getHandler();
        }
    }

    private void handleDrag(int x, int y) {
        // find the view under the pointer, accounting for GONE views
        final int count = getChildCount();
        View slidingChild;
        for (int childIdx = 0; childIdx < count; childIdx++) {
            slidingChild = getChildAt(childIdx);
            if (slidingChild.getVisibility() == GONE) {
                continue;
            }
            if (y >= slidingChild.getTop() &&
                    y <= slidingChild.getBottom() &&
                    slidingChild instanceof ContactTileRow) {
                final ContactTileRow tile = (ContactTileRow) slidingChild;
                reportDragEnteredItemIndex(tile.getItemIndex(x, y));
            }
        }
    }

    private void reportDragEnteredItemIndex(int itemIndex) {
        final PhoneFavoriteMergedAdapter adapter =
                (PhoneFavoriteMergedAdapter) getAdapter();
        if (adapter != null) {
            adapter.reportDragEnteredItemIndex(itemIndex);
        }
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -346,4 +346,10 @@ public class PhoneFavoriteMergedAdapter extends BaseAdapter {
            mOnItemSwipeListener = listener;
        }
    }

    public void reportDragEnteredItemIndex(int itemIndex) {
        if (mContactTileAdapter != null) {
            mContactTileAdapter.reportDragEnteredItemIndex(itemIndex);
        }
    }
}
+5 −1
Original line number Diff line number Diff line
@@ -99,7 +99,11 @@ public abstract class PhoneFavoriteTileView extends ContactTileView {
                    // If the view is regular row, start drag the row view.
                    final View.DragShadowBuilder shadowBuilder =
                            new View.DragShadowBuilder(view.getParentRow());
                    final ContactTileRow parent = (ContactTileRow) view.getParentRow();
                    // Drag is not available for the item exceeds the PIN_LIMIT.
                    if (parent.getRegularRowItemIndex() < PhoneFavoritesTileAdapter.PIN_LIMIT) {
                        view.getParentRow().startDrag(data, shadowBuilder, null, 0);
                    }
                } else {
                    // If the view is a tile view, start drag the tile.
                    final View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
+71 −42
Original line number Diff line number Diff line
@@ -84,6 +84,8 @@ public class PhoneFavoritesTileAdapter extends BaseAdapter implements
    private int mDraggedEntryIndex = -1;
    /** New position of the temporarily removed contact in the cache. */
    private int mDropEntryIndex = -1;
    /** New position of the temporarily entered contact in the cache. */
    private int mDragEnteredEntryIndex = -1;
    /** Position of the contact pending removal. */
    private int mPotentialRemoveEntryIndex = -1;
    private long mIdToKeepInPlace = -1;
@@ -121,7 +123,7 @@ public class PhoneFavoritesTileAdapter extends BaseAdapter implements
    /** Indicates whether a drag is in process. */
    private boolean mInDragging = false;

    private static final int PIN_LIMIT = 20;
    public static final int PIN_LIMIT = 20;

    /**
     * The soft limit on how many contact tiles to show.
@@ -569,34 +571,58 @@ public class PhoneFavoritesTileAdapter extends BaseAdapter implements
     * @param index Position of the contact to be removed.
     */
    public void popContactEntry(int index) {
        if (index >= 0 && index < mContactEntries.size()) {
        if (isIndexInBound(index)) {
            mDraggedEntry = mContactEntries.get(index);
            mContactEntries.set(index, ContactEntry.BLANK_ENTRY);
            ContactEntry.BLANK_ENTRY.id = mDraggedEntry.id;
            mDraggedEntryIndex = index;
            mDragEnteredEntryIndex = index;
            markDropArea(mDragEnteredEntryIndex);
        }
    }

    /**
     * @param itemIndex Position of the contact in {@link #mContactEntries}.
     * @return True if the given index is valid for {@link #mContactEntries}.
     */
    private boolean isIndexInBound(int itemIndex) {
        return itemIndex >= 0 && itemIndex < mContactEntries.size();
    }

    /**
     * Mark the tile as drop area by given the item index in {@link #mContactEntries}.
     *
     * @param itemIndex Position of the contact in {@link #mContactEntries}.
     */
    private void markDropArea(int itemIndex) {
        if (isIndexInBound(mDragEnteredEntryIndex) && isIndexInBound(itemIndex)) {
            // Remove the old placeholder item and place the new placeholder item.
            mContactEntries.remove(mDragEnteredEntryIndex);
            mDragEnteredEntryIndex = itemIndex;
            mContactEntries.add(mDragEnteredEntryIndex, ContactEntry.BLANK_ENTRY);
            ContactEntry.BLANK_ENTRY.id = mDraggedEntry.id;
            notifyDataSetChanged();
        }
    }

    /**
     * Drops the temporarily removed contact to the desired location in the list.
     *
     * @param index Location where the contact will be dropped.
     */
    public void dropContactEntry(int index) {
    public void handleDrop() {
        boolean changed = false;
        if (mDraggedEntry != null) {
            if (index >= 0 && index < mContactEntries.size()) {
            if (isIndexInBound(mDragEnteredEntryIndex)) {
                // Don't add the ContactEntry here (to prevent a double animation from occuring).
                // When we receive a new cursor the list of contact entries will automatically be
                // populated with the dragged ContactEntry at the correct spot.
                mDropEntryIndex = index;
                mDropEntryIndex = mDragEnteredEntryIndex;
                mContactEntries.set(mDropEntryIndex, mDraggedEntry);
                mIdToKeepInPlace = getAdjustedItemId(mDraggedEntry.id);
                mDataSetChangedListener.cacheOffsetsForDatasetChange();
                changed = true;
            } else if (mDraggedEntryIndex >= 0 && mDraggedEntryIndex <= mContactEntries.size()) {
                /** If the index is invalid, falls back to the original position of the contact. */
                mContactEntries.set(mDraggedEntryIndex, mDraggedEntry);
            } else if (isIndexInBound(mDraggedEntryIndex)) {
                // If {@link #mDragEnteredEntryIndex} is invalid,
                // falls back to the original position of the contact.
                mContactEntries.remove(mDragEnteredEntryIndex);
                mContactEntries.add(mDraggedEntryIndex, mDraggedEntry);
                mDropEntryIndex = mDraggedEntryIndex;
                notifyDataSetChanged();
            }
@@ -618,7 +644,11 @@ public class PhoneFavoritesTileAdapter extends BaseAdapter implements
     * contact back to where it was dragged from.
     */
    public void dropToUnsupportedView() {
        dropContactEntry(-1);
        if (isIndexInBound(mDragEnteredEntryIndex)) {
            mContactEntries.remove(mDragEnteredEntryIndex);
            mContactEntries.add(mDraggedEntryIndex, mDraggedEntry);
            notifyDataSetChanged();
        }
    }

    /**
@@ -638,7 +668,7 @@ public class PhoneFavoritesTileAdapter extends BaseAdapter implements
     */
    public boolean removePendingContactEntry() {
        boolean removed = false;
        if (mPotentialRemoveEntryIndex >= 0 && mPotentialRemoveEntryIndex < mContactEntries.size()) {
        if (isIndexInBound(mPotentialRemoveEntryIndex)) {
            final ContactEntry entry = mContactEntries.get(mPotentialRemoveEntryIndex);
            unstarAndUnpinContact(entry.lookupKey);
            removed = true;
@@ -661,6 +691,7 @@ public class PhoneFavoritesTileAdapter extends BaseAdapter implements
    public void cleanTempVariables() {
        mDraggedEntryIndex = -1;
        mDropEntryIndex = -1;
        mDragEnteredEntryIndex = -1;
        mDraggedEntry = null;
        mPotentialRemoveEntryIndex = -1;
    }
@@ -917,11 +948,20 @@ public class PhoneFavoritesTileAdapter extends BaseAdapter implements
                }
            } else {
                /** If the selected item is one of the rows, compute the index. */
                return (mPosition - mMaxTiledRows) + mColumnCount * mMaxTiledRows;
                return getRegularRowItemIndex();
            }
            return -1;
        }

        /**
         * Gets the index of the regular row item.
         *
         * @return Index of the selected item in the cached array.
         */
        public int getRegularRowItemIndex() {
            return (mPosition - mMaxTiledRows) + mColumnCount * mMaxTiledRows;
        }

        public PhoneFavoritesTileAdapter getTileAdapter() {
            return PhoneFavoritesTileAdapter.this;
        }
@@ -1110,32 +1150,12 @@ public class PhoneFavoritesTileAdapter extends BaseAdapter implements
            ContactEntry entryToPin, int oldPos, int newPinPos) {

        final ContentValues cv = new ContentValues();
        // Add the dragged contact at the user-requested spot.
        cv.put(String.valueOf(entryToPin.id), newPinPos);

        final int listSize = list.size();
        if (oldPos < newPinPos && list.get(listSize - 1).pinned == (listSize - 1)) {
            // The only time we should get here is it we are completely full - i.e. starting
            // from the newly pinned contact to the end of the list, every single contact
            // thereafter is pinned, and a contact is being shifted to the right by the user.
            // Instead of trying to make room to the right, we should thus try to shift contacts
            // to the left instead, working backwards through the list, starting from the contact
            // which just got bumped.
            for (int i = newPinPos; i >= 0; i--) {
        final int lowerBound = Math.min(oldPos, newPinPos);
        final int upperBound = Math.max(oldPos, newPinPos);
        for (int i = lowerBound; i <= upperBound; i++) {
            final ContactEntry entry = list.get(i);
                // Once we find an unpinned spot(or a blank entry), we can stop pushing contacts
                // to the left.
                if (entry.pinned > PIN_LIMIT) break;
                cv.put(String.valueOf(entry.id), entry.pinned - 1);
            }
        } else {
            // Shift any pinned contacts to the right as necessary, until an unpinned
            // spot is found
            for (int i = newPinPos; i < PIN_LIMIT && i < list.size(); i++) {
                final ContactEntry entry = list.get(i);
                if (entry.pinned > PIN_LIMIT) break;
                cv.put(String.valueOf(entry.id), entry.pinned + 1);
            }
            if (entry.pinned == i) continue;
            cv.put(String.valueOf(entry.id), i);
        }
        return cv;
    }
@@ -1175,4 +1195,13 @@ public class PhoneFavoritesTileAdapter extends BaseAdapter implements
    public void setEmptyView(View emptyView) {
        mEmptyView = emptyView;
    }

    public void reportDragEnteredItemIndex(int itemIndex) {
        if (mInDragging &&
                mDragEnteredEntryIndex != itemIndex &&
                isIndexInBound(itemIndex) &&
                itemIndex < PIN_LIMIT) {
            markDropArea(itemIndex);
        }
    }
}