gabrielemariotti / cardslib

Android Library to build a UI Card
4.66k stars 1.19k forks source link

Invalid Index 0, size is 0 #253

Open huteri opened 10 years ago

huteri commented 10 years ago

Hi Gabriele, Take a look on this issue. Got 111 crashs from 103 users, it's not good. Any idea?

java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
       at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
       at java.util.ArrayList.get(ArrayList.java:308)
       at android.widget.ArrayAdapter.getItem(ArrayAdapter.java:337)
       at it.gmariotti.cardslib.library.view.listener.SwipeDismissListViewTouchListener.onTouch(SwipeDismissListViewTouchListener.java:236)
       at android.view.View.dispatchTouchEvent(View.java:7812)
       at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2435)
       at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2159)
       at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2441)
       at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2131)
       at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2441)
       at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2131)
       at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2441)
       at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2131)
       at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2441)
       at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2131)
       at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2441)
       at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2131)
       at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2441)
       at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2131)
       at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2441)
       at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2131)
       at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2441)
       at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2131)
       at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2441)
       at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2131)
       at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2326)
       at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1612)
       at android.app.Activity.dispatchTouchEvent(Activity.java:2494)
       at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2274)
       at android.view.View.dispatchPointerEvent(View.java:8025)
       at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4668)
       at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4556)
       at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4150)
       at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4207)
       at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4176)
       at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4261)
       at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4184)
       at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4318)
       at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4150)
       at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4207)
       at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4176)
       at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4184)
       at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4150)
       at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6302)
       at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6240)
       at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6211)
       at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6382)
       at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:188)
       at android.os.MessageQueue.nativePollOnce(MessageQueue.java)
       at android.os.MessageQueue.next(MessageQueue.java:132)
       at android.os.Looper.loop(Looper.java:138)
       at android.app.ActivityThread.main(ActivityThread.java:5419)
       at java.lang.reflect.Method.invokeNative(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:525)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1046)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:862)
       at dalvik.system.NativeStart.main(NativeStart.java)
gabrielemariotti commented 10 years ago

Can you post your code?

huteri commented 10 years ago

There is no reference to my code in crash report, so I'm a little bit confused which code. I use the listview with undobar in 2 different sections and the code is the same as in the doc. By the way, I also can't reproduce the bug. If you don't find any particular bug in the library then it's okay. I will try to find it in my code.

But how is it possible mDownView is not null while the size is 0?

gabrielemariotti commented 10 years ago

Sorry for delay.

This issue happens while the user is doing a swipe action. Which version are you using? I need to reply the use case.

huteri commented 10 years ago

It's fine, I know you're busy with L stuff :) it's v1.60, I will try to update it.

Edit.. I already updated the library with the newer one, and I remember I have modified the file, and now I have adapted it with the new version. Since I can't reproduce the bug, please take a look only if you have time on the new modified one whether it's already good.

The only I modified is adding SwipeDirection

/*
 * ******************************************************************************
 *   Copyright (c) 2013 Roman Nurik, 2013-2014 Gabriele Mariotti.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *  *****************************************************************************
 */

package it.gmariotti.cardslib.library.view.listener;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.widget.AbsListView;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import it.gmariotti.cardslib.library.R;
import it.gmariotti.cardslib.library.internal.Card;

/**
 * It is based on Roman Nurik code. See this link for original code
 * https://github.com/romannurik/Android-SwipeToDismiss </p> It provides a
 * SwipeDismissViewTouchListener for a CardList. </p>
 * 
 * A {@link View.OnTouchListener} that makes the list items in a
 * {@link ListView} dismissable. {@link ListView} is given special treatment
 * because by default it handles touches for its list items... i.e. it's in
 * charge of drawing the pressed state (the list selector), handling list item
 * clicks, etc.
 * 
 * <p>
 * After creating the listener, the caller should also call
 * {@link ListView#setOnScrollListener(AbsListView.OnScrollListener)}, using a
 * {@link it.gmariotti.cardslib.library.view.listener.SwipeOnScrollListener}.
 * 
 * If a scroll listener is already assigned, the caller should still pass scroll
 * changes through to this listener. This will ensure that this
 * {@link SwipeDismissListViewTouchListener} is paused during list view
 * scrolling.
 * </p>
 * 
 * <p>
 * Example usage:
 * </p>
 * 
 * <pre>
 * SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener(
 *      listView, new SwipeDismissListViewTouchListener.OnDismissCallback() {
 *          public void onDismiss(ListView listView,
 *                  int[] reverseSortedPositions) {
 *              for (int position : reverseSortedPositions) {
 *                  adapter.remove(adapter.getItem(position));
 *              }
 *              adapter.notifyDataSetChanged();
 *          }
 *      });
 * listView.setOnTouchListener(touchListener);
 * listView.setOnScrollListener(touchListener.makeScrollListener());
 * </pre>
 * 
 * <p>
 * This class Requires API level 12 or later due to use of
 * {@link ViewPropertyAnimator}.
 * </p>
 * 
 */
public class SwipeDismissListViewTouchListener implements View.OnTouchListener {

    // Cached ViewConfiguration and system-wide constant values
    private int mSlop;
    private int mMinFlingVelocity;
    private int mMaxFlingVelocity;
    private long mAnimationTime;

    // Fixed properties
    private ListView mListView;
    private DismissCallbacks mCallbacks;
    private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero

    // Transient properties
    private List<PendingDismissData> mPendingDismisses = new ArrayList<PendingDismissData>();
    private int mDismissAnimationRefCount = 0;
    private float mDownX;
    private float mDownY;
    private boolean mSwiping;
    private int mSwipingSlop;
    private VelocityTracker mVelocityTracker;
    private int mDownPosition;
    private View mDownView;
    private boolean mPaused;

    private int mSwipeDirection = SwipeDirection.BOTH;
    private int swipeDistanceDivisor = 2;

    /**
     * The callback interface used by {@link SwipeDismissListViewTouchListener}
     * to inform its client about a successful dismissal of one or more list
     * item positions.
     */
    public interface DismissCallbacks {
        /**
         * Called to determine whether the given position can be dismissed.
         */
        boolean canDismiss(int position, Card card);

        /**
         * Called when the user has indicated they she would like to dismiss one
         * or more list item positions.
         * 
         * @param listView
         *            The originating {@link ListView}.
         * @param reverseSortedPositions
         *            An array of positions to dismiss, sorted in descending
         *            order for convenience.
         */
        void onDismiss(ListView listView, int[] reverseSortedPositions);
    }

    /**
     * Constructs a new swipe-to-dismiss touch listener for the given list view.
     * 
     * @param listView
     *            The list view whose items should be dismissable.
     * @param callbacks
     *            The callback to trigger when the user has indicated that she
     *            would like to dismiss one or more list items.
     */
    public SwipeDismissListViewTouchListener(ListView listView,
            DismissCallbacks callbacks) {
        ViewConfiguration vc = ViewConfiguration.get(listView.getContext());
        mSlop = vc.getScaledTouchSlop();
        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
        mAnimationTime = listView.getContext().getResources()
                .getInteger(android.R.integer.config_shortAnimTime);
        mListView = listView;
        mCallbacks = callbacks;
        swipeDistanceDivisor = listView.getContext().getResources()
                .getInteger(R.integer.list_card_swipe_distance_divisor);
    }

    /**
     * Enables or disables (pauses or resumes) watching for swipe-to-dismiss
     * gestures.
     * 
     * @param enabled
     *            Whether or not to watch for gestures.
     */
    public void setEnabled(boolean enabled) {
        mPaused = !enabled;
    }

    /**
     * Returns an {@link AbsListView.OnScrollListener} to be added to the
     * {@link ListView} using
     * {@link ListView#setOnScrollListener(AbsListView.OnScrollListener)}. If a
     * scroll listener is already assigned, the caller should still pass scroll
     * changes through to this listener. This will ensure that this
     * {@link SwipeDismissListViewTouchListener} is paused during list view
     * scrolling.</p>
     * 
     * @see SwipeDismissListViewTouchListener
     */
    /*
     * public AbsListView.OnScrollListener makeScrollListener() { return new
     * AbsListView.OnScrollListener() {
     * 
     * @Override public void onScrollStateChanged(AbsListView absListView, int
     * scrollState) { setEnabled(scrollState !=
     * AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); }
     * 
     * @Override public void onScroll(AbsListView absListView, int i, int i1,
     * int i2) { } }; }
     */

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        if (mViewWidth < 2) {
            mViewWidth = mListView.getWidth();
        }

        switch (motionEvent.getActionMasked()) {
        case MotionEvent.ACTION_DOWN: {
            if (mPaused) {
                return false;
            }

            if (mSwiping) {
                return true;
            }

            // TODO: ensure this is a finger, and set a flag

            // Find the child view that was touched (perform a hit test)
            Rect rect = new Rect();
            int childCount = mListView.getChildCount();
            int headerCount = mListView.getHeaderViewsCount();
            int footerCount = mListView.getFooterViewsCount();
            int[] listViewCoords = new int[2];
            mListView.getLocationOnScreen(listViewCoords);
            int x = (int) motionEvent.getRawX() - listViewCoords[0];
            int y = (int) motionEvent.getRawY() - listViewCoords[1];
            View child = null;
            for (int i = headerCount; i < (childCount - footerCount); i++) {
                child = mListView.getChildAt(i);
                child.getHitRect(rect);
                if (rect.contains(x, y)) {
                    mDownView = child;
                    break;
                }
            }

            if (mDownView != null) {

                mDownX = motionEvent.getRawX();
                mDownY = motionEvent.getRawY();
                mDownPosition = mListView.getPositionForView(mDownView);
                if (mListView.getAdapter().getItem(mDownPosition) instanceof Card) {
                    if (mCallbacks.canDismiss(mDownPosition, (Card) mListView
                            .getAdapter().getItem(mDownPosition))) {
                        mVelocityTracker = VelocityTracker.obtain();
                        mVelocityTracker.addMovement(motionEvent);
                    } else {
                        mDownView = null;
                    }
                } else {
                    mDownView = null;
                }
            }
            view.onTouchEvent(motionEvent);
            return true;
            // return false;
        }

        case MotionEvent.ACTION_UP: {
            if (mVelocityTracker == null) {
                break;
            }

            float deltaX = motionEvent.getRawX() - mDownX;
            mVelocityTracker.addMovement(motionEvent);
            mVelocityTracker.computeCurrentVelocity(1000);
            float velocityX = mVelocityTracker.getXVelocity();
            float absVelocityX = Math.abs(velocityX);
            float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
            boolean dismiss = false;
            boolean dismissRight = false;
            if (Math.abs(deltaX) > mViewWidth / swipeDistanceDivisor
                    && mSwiping) {
                dismiss = true;
                dismissRight = deltaX > 0;
            } else if (mMinFlingVelocity <= absVelocityX
                    && absVelocityX <= mMaxFlingVelocity
                    && absVelocityY < absVelocityX && mSwiping) {
                // dismiss only if flinging in the same direction as dragging
                dismiss = (velocityX < 0) == (deltaX < 0);
                dismissRight = mVelocityTracker.getXVelocity() > 0;
            }
            if (dismiss && mDownPosition != ListView.INVALID_POSITION) {
                // dismiss
                dismiss(mDownView,
                        mDownPosition - mListView.getHeaderViewsCount(),
                        dismissRight);

            } else {
                // cancel
                mDownView.animate().translationX(0).alpha(1)
                        .setDuration(mAnimationTime).setListener(null);
            }

            mVelocityTracker.recycle();
            mVelocityTracker = null;
            mDownX = 0;
            mDownY = 0;
            mDownView = null;
            mDownPosition = ListView.INVALID_POSITION;
            if (mSwiping) {
                // To prevent onClick event with a fast swipe
                mSwiping = false;
                return true;
            }
            mSwiping = false;
            break;
        }

        case MotionEvent.ACTION_CANCEL: {
            if (mVelocityTracker == null) {
                break;
            }

            if (mDownView != null) {
                // cancel
                mDownView.animate().translationX(0).alpha(1)
                        .setDuration(mAnimationTime).setListener(null);
            }
            mVelocityTracker.recycle();
            mVelocityTracker = null;
            mDownX = 0;
            mDownY = 0;
            mDownView = null;
            mDownPosition = ListView.INVALID_POSITION;
            mSwiping = false;
            break;
        }

        case MotionEvent.ACTION_MOVE: {
            if (mVelocityTracker == null || mPaused) {
                break;
            }

            mVelocityTracker.addMovement(motionEvent);
            float deltaX = motionEvent.getRawX() - mDownX;
            float deltaY = motionEvent.getRawY() - mDownY;

            float condDeltaX = deltaX;
            switch (getSwipeDirection()) {
            case SwipeDirection.LEFT:
                condDeltaX = deltaX * -1;
                break;
            case SwipeDirection.RIGHT:
                condDeltaX = deltaX * 1;
                break;
            case SwipeDirection.BOTH:
                condDeltaX = Math.abs(deltaX);
                break;
            }

            if (condDeltaX > mSlop
                    && Math.abs(deltaY) < Math.abs(deltaX) / 2) {
                mSwiping = true;
                mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
                mListView.requestDisallowInterceptTouchEvent(true);

                // Cancel ListView's touch (un-highlighting the item)
                MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
                cancelEvent
                        .setAction(MotionEvent.ACTION_CANCEL
                                | (motionEvent.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
                mListView.onTouchEvent(cancelEvent);
                view.onTouchEvent(cancelEvent);
                cancelEvent.recycle();
            }

            if (mSwiping) {
                mDownView.setTranslationX(deltaX - mSwipingSlop);
                mDownView.setAlpha(Math.max(0f,
                        Math.min(1f, 1f - 2f * Math.abs(deltaX) / mViewWidth)));
                return true;
            }
            break;
        }
        }
        return false;
    }

    private void dismiss(final View view, final int position,
            boolean dismissRight) {
        ++mDismissAnimationRefCount;
        if (view == null) {
            // No view, shortcut to calling onDismiss to let it deal with
            // adapter
            // updates and all that.
            mCallbacks.onDismiss(mListView, new int[] { position });
            return;
        }

        view.animate().translationX(dismissRight ? mViewWidth : -mViewWidth)
                .alpha(0).setDuration(mAnimationTime)
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        performDismiss(view, position);
                    }
                });
    }

    class PendingDismissData implements Comparable<PendingDismissData> {
        public int position;
        public View view;

        public PendingDismissData(int position, View view) {
            this.position = position;
            this.view = view;
        }

        @Override
        public int compareTo(PendingDismissData other) {
            // Sort by descending position
            return other.position - position;
        }
    }

    private void performDismiss(final View dismissView,
            final int dismissPosition) {
        // Animate the dismissed list item to zero-height and fire the dismiss
        // callback when
        // all dismissed list item animations have completed. This triggers
        // layout on each animation
        // frame; in the future we may want to do something smarter and more
        // performant.

        final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
        final int originalHeight = dismissView.getHeight();

        ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1)
                .setDuration(mAnimationTime);

        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                --mDismissAnimationRefCount;
                if (mDismissAnimationRefCount == 0) {
                    // No active animations, process all pending dismisses.
                    // Sort by descending position
                    Collections.sort(mPendingDismisses);

                    int[] dismissPositions = new int[mPendingDismisses.size()];
                    for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
                        dismissPositions[i] = mPendingDismisses.get(i).position;
                    }
                    mCallbacks.onDismiss(mListView, dismissPositions);

                    // Reset mDownPosition to avoid MotionEvent.ACTION_UP trying
                    // to start a dismiss
                    // animation with a stale position
                    mDownPosition = ListView.INVALID_POSITION;

                    ViewGroup.LayoutParams lp;
                    for (PendingDismissData pendingDismiss : mPendingDismisses) {
                        // Reset view presentation
                        pendingDismiss.view.setAlpha(1f);
                        pendingDismiss.view.setTranslationX(0);
                        lp = pendingDismiss.view.getLayoutParams();
                        lp.height = 0;
                        pendingDismiss.view.setLayoutParams(lp);
                    }

                    // Send a cancel event
                    long time = SystemClock.uptimeMillis();
                    MotionEvent cancelEvent = MotionEvent.obtain(time, time,
                            MotionEvent.ACTION_CANCEL, 0, 0, 0);
                    mListView.dispatchTouchEvent(cancelEvent);

                    mPendingDismisses.clear();
                }
            }
        });

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                lp.height = (Integer) valueAnimator.getAnimatedValue();
                dismissView.setLayoutParams(lp);
            }
        });

        mPendingDismisses.add(new PendingDismissData(dismissPosition,
                dismissView));
        animator.start();
    }

    public int getSwipeDirection() {
        return mSwipeDirection;
    }

    public void setSwipeDirection(int mSwipeDirection) {
        this.mSwipeDirection = mSwipeDirection;
    }

    public class SwipeDirection {
        public static final int LEFT = 1;
        public static final int RIGHT = 2;
        public static final int BOTH = 0;
    }
}
gabrielemariotti commented 10 years ago

Are you using header views? Can you post something about your adapter?

huteri commented 10 years ago

Yep, I am. The adapter is just usual, nothing odd to me.

this is part of fragment class

@Override
    public void onActivityCreated(Bundle savedInstanceState) {

        super.onActivityCreated(savedInstanceState);

        if (mCardList.size() > 0) {
            mCardListView = (CardListView) getActivity().findViewById(R.id.card_list_view_income);
            CardArrayAdapter cardAdapter = new CardArrayAdapter(getActivity(), mCardList);

            if (!Util.isGingerBread()) {
                cardAdapter.setUndoBarUIElements(new UndoBarController.DefaultUndoBarUIElements() {
                    @Override
                    public int getUndoBarId() {
                        return R.id.recurring_undobar;
                    }

                    @Override
                    public int getUndoBarButtonId() {
                        return R.id.my_undobar_button;
                    }

                    @Override
                    public int getUndoBarMessageId() {
                        return R.id.my_undobar_message;
                    }
                });

                cardAdapter.setEnableUndo(true);
                cardAdapter.setSwipeDirection(SwipeDirection.LEFT);
            } else {
                LinearLayout undoBarLayout = (LinearLayout) getActivity().findViewById(R.id.recurring_undobar);
                undoBarLayout.setVisibility(View.GONE);
            }
            mCardListView.setAdapter(cardAdapter);
        }
    }
gabrielemariotti commented 10 years ago

It seems legit. Did you continue to have issue in your app with the last release?

huteri commented 10 years ago

I have not published it yet. I will publish it first and let you know if the issue persists.

gabrielemariotti commented 10 years ago

I spent a few hours in your issue. I am not able to replicate it, and I don't know how is possible that mDownView is not null while the size is 0.

It is not the real solution, however you can change your code to avoid the Exception:

    mDownPosition = mListView.getPositionForView(mDownView);
    if (mDownPosition != ListView.INVALID_POSITION && 
                     mDownPosition < mListView.getAdapter().getCount()) {
         if (mListView.getAdapter().getItem(mDownPosition) instanceof Card) {
                 //......
        } else {
             mDownView = null;
        }
    } else {
          mDownView = null;
    }
huteri commented 10 years ago

Sorry for a late reply. I was not working on this version for the past few days, and I'm working on it now. Thank you for your time and I will try your revision.