Android开发之自定义侧滑ListView

不知道是上哪个版本的知乎来着,可以实现用手指侧滑取消关注话题什么的,后来改版了,这个效果又没了。当时我是相当喜欢这个功能的,不想看什么鬼玩意,手指一滑就没了,挺方便的。后来想了很久,再看了下别人的代码,实现这个功能还是不难的。

首先分析一下这个功能该怎么实现,很容易想到是去写一个类去继承ListView,之所以不去继承BaseAdapter去实现这个功能,是因为有很多涉及到界面的操作,BaseAdapter里面没有相关的API。
确定了是去继承ListView,接下来怎么样呢?必不可少的去重写onTouchEvent监听手指触摸ListView的操作,获得手指在ListView上触摸的X,Y轴的坐标,然后通过int pointToPosition(int x,int y)去获取当前触碰到的是哪一个item,然后通过View getChildAt(int position)去获得当前操作的item,然后通过ViewHelper去改变item的状态(比如透明度和X坐标)。当手指抬起的时候,获取手指的位置,如果此时位置没有超过我们觉得用户想移除item的范围,可以理解为用户不想将这个item移除,用属性动画把item移回来;如果超过了我们觉得用户想移除item的范围,用属性动画把item移出去,把其余所有在原本item下面的item上移一位,各位注意了,此时这些操作全部都是视觉上的,也就是本身不会对ListView真正的操作,所以此时还需要定义一个接口,当视觉上的操作结束之后,回调接口,去执行真正的对于ListView内容上的变动。

 @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                handleActionDown(ev);
                break;
            case MotionEvent.ACTION_MOVE:
                return handleActionMove(ev);
            case MotionEvent.ACTION_UP:
                handleActionUp(ev);
                break;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 按下事件处理
     *
     * @param ev
     * @return
     */
    private void handleActionDown(MotionEvent ev) {
        mDownX = ev.getX();
        mDownY = ev.getY();
        mDownPosition = pointToPosition((int) mDownX, (int) mDownY);
        if (mDownPosition == AdapterView.INVALID_POSITION) {
            return;
        }
        mDownView = getChildAt(mDownPosition - getFirstVisiblePosition());
        if (mDownView != null) {
            mViewWidth = mDownView.getWidth();
        }
        //加入速度检测
        mVelocityTracker = VelocityTracker.obtain();
        mVelocityTracker.addMovement(ev);
    }

    /**
     * 处理手指滑动的方法
     *
     * @param ev
     * @return
     */
    private boolean handleActionMove(MotionEvent ev) {
        if (mVelocityTracker == null || mDownView == null) {
            return super.onTouchEvent(ev);
        }
        // 获取X方向滑动的距离
        float deltaX = ev.getX() - mDownX;
        float deltaY = ev.getY() - mDownY;
        // X方向滑动的距离大于mSlop并且Y方向滑动的距离小于mSlop,表示可以滑动
        if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < mSlop) {
            mSwiping = true;
            //当手指滑动item,取消item的点击事件,不然我们滑动Item也伴随着item点击事件的发生
            MotionEvent cancelEvent = MotionEvent.obtain(ev);
            cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
            onTouchEvent(cancelEvent);
        }
        if (mSwiping) {
            // 跟谁手指移动item
            ViewHelper.setTranslationX(mDownView, deltaX);
            // 透明度渐变
            ViewHelper.setAlpha(mDownView, Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX) / mViewWidth)));
            // 手指滑动的时候,返回true,表示SwipeDismissListView自己处理onTouchEvent,其他的就交给父类来处理
            return true;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 手指抬起的事件处理
     *
     * @param ev
     */
    private void handleActionUp(MotionEvent ev) {
        if (mVelocityTracker == null || mDownView == null || !mSwiping) {
            return;
        }
        float deltaX = ev.getX() - mDownX;
        //通过滑动的距离计算出X,Y方向的速度
        mVelocityTracker.computeCurrentVelocity(1000);
        float velocityX = Math.abs(mVelocityTracker.getXVelocity());
        float velocityY = Math.abs(mVelocityTracker.getYVelocity());
        boolean dismiss = false; //item是否要滑出屏幕
        boolean dismissRight = false;//是否往右边删除
        //当拖动item的距离大于item的一半,item滑出屏幕
        if (Math.abs(deltaX) > mViewWidth / 2) {
            dismiss = true;
            dismissRight = deltaX > 0;
            //手指在屏幕滑动的速度在某个范围内,也使得item滑出屏幕
        } else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity && velocityY < velocityX) {
            dismiss = true;
            dismissRight = mVelocityTracker.getXVelocity() > 0;
        }
        if (dismiss) {
            AnimatorSet set = new AnimatorSet();
            set.playTogether(ObjectAnimator.ofFloat(mDownView, "translationX", dismissRight ? mViewWidth : -mViewWidth),
                    ObjectAnimator.ofFloat(mDownView, "alpha", 0));
            set.setDuration(mAnimationTime).start();
            set.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    //Item滑出界面之后执行删除
                    performDismiss(mDownView, mDownPosition);
                }
            });
        } else {
            //将item滑动至开始位置
            com.nineoldandroids.view.ViewPropertyAnimator.animate(mDownView)
                    .translationX(0)
                    .alpha(1)
                    .setDuration(mAnimationTime).setListener(null);
        }
        //移除速度检测
        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
        mSwiping = false;
    }

 /**
     * 在此方法中执行item删除之后,其他的item向上或者向下滚动的动画,并且将position回调到方法onDismiss()中
     *
     * @param dismissView
     * @param dismissPosition
     */
    private void performDismiss(final View dismissView, final int dismissPosition) {
        final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();//获取item的布局参数
        final int originalHeight = dismissView.getHeight();//item的高度
        ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 0).setDuration(mAnimationTime);
        animator.start();
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (onDismissCallback != null) {
                    onDismissCallback.onDismiss(dismissPosition);
                }
                //这段代码很重要,因为我们并没有将item从ListView中移除,而是将item的高度设置为0
                //所以我们在动画执行完毕之后将item设置回来
                ViewHelper.setAlpha(dismissView, 1f);
                ViewHelper.setTranslationX(dismissView, 0);
                ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
                lp.height = originalHeight;
                dismissView.setLayoutParams(lp);
            }
        });
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //这段代码的效果是ListView删除某item之后,其他的item向上滑动的效果
                lp.height = (Integer) valueAnimator.getAnimatedValue();
                dismissView.setLayoutParams(lp);
            }
        });
    }

以下是简单Demo的源码

public class SwipeDismissListView extends ListView {

    /**
     * 认为是用户滑动的最小距离
     */
    private int mSlop;
    /**
     * 滑动的最小速度
     */
    private int mMinFlingVelocity;
    /**
     * 滑动的最大速度
     */
    private int mMaxFlingVelocity;
    /**
     * 执行动画的时间
     */
    protected long mAnimationTime = 150;
    /**
     * 用来标记用户是否正在滑动中
     */
    private boolean mSwiping;
    /**
     * 滑动速度检测类
     */
    private VelocityTracker mVelocityTracker;
    /**
     * 手指按下的position
     */
    private int mDownPosition;
    /**
     * 按下的item对应的View
     */
    private View mDownView;
    private float mDownX;
    private float mDownY;
    /**
     * item的宽度
     */
    private int mViewWidth;
    /**
     * 当ListView的Item滑出界面回调的接口
     */
    private OnDismissCallback onDismissCallback;

    /**
     * 设置动画时间
     *
     * @param mAnimationTime
     */
    public void setmAnimationTime(long mAnimationTime) {
        this.mAnimationTime = mAnimationTime;
    }

    /**
     * 设置删除回调接口
     *
     * @param onDismissCallback
     */

    /**
     * 删除的回调接口
     *
     * @author xiaanming
     */
    public interface OnDismissCallback {
        public void onDismiss(int dismissPosition);
    }

    public void setOnDismissCallback(OnDismissCallback onDismissCallback) {
        this.onDismissCallback = onDismissCallback;
    }

    public SwipeDismissListView(Context context) {
        this(context, null);
    }

    public SwipeDismissListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SwipeDismissListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        ViewConfiguration vc = ViewConfiguration.get(context);
        mSlop = vc.getScaledTouchSlop();
        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 8; //获取滑动的最小速度
        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();  //获取滑动的最大速度
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                handleActionDown(ev);
                break;
            case MotionEvent.ACTION_MOVE:
                return handleActionMove(ev);
            case MotionEvent.ACTION_UP:
                handleActionUp(ev);
                break;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 按下事件处理
     *
     * @param ev
     * @return
     */
    private void handleActionDown(MotionEvent ev) {
        mDownX = ev.getX();
        mDownY = ev.getY();
        mDownPosition = pointToPosition((int) mDownX, (int) mDownY);
        if (mDownPosition == AdapterView.INVALID_POSITION) {
            return;
        }
        mDownView = getChildAt(mDownPosition - getFirstVisiblePosition());
        if (mDownView != null) {
            mViewWidth = mDownView.getWidth();
        }
        //加入速度检测
        mVelocityTracker = VelocityTracker.obtain();
        mVelocityTracker.addMovement(ev);
    }

    /**
     * 处理手指滑动的方法
     *
     * @param ev
     * @return
     */
    private boolean handleActionMove(MotionEvent ev) {
        if (mVelocityTracker == null || mDownView == null) {
            return super.onTouchEvent(ev);
        }
        // 获取X方向滑动的距离
        float deltaX = ev.getX() - mDownX;
        float deltaY = ev.getY() - mDownY;
        // X方向滑动的距离大于mSlop并且Y方向滑动的距离小于mSlop,表示可以滑动
        if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < mSlop) {
            mSwiping = true;
            //当手指滑动item,取消item的点击事件,不然我们滑动Item也伴随着item点击事件的发生
            MotionEvent cancelEvent = MotionEvent.obtain(ev);
            cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
            onTouchEvent(cancelEvent);
        }
        if (mSwiping) {
            // 跟谁手指移动item
            ViewHelper.setTranslationX(mDownView, deltaX);
            // 透明度渐变
            ViewHelper.setAlpha(mDownView, Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX) / mViewWidth)));
            // 手指滑动的时候,返回true,表示SwipeDismissListView自己处理onTouchEvent,其他的就交给父类来处理
            return true;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 手指抬起的事件处理
     *
     * @param ev
     */
    private void handleActionUp(MotionEvent ev) {
        if (mVelocityTracker == null || mDownView == null || !mSwiping) {
            return;
        }
        float deltaX = ev.getX() - mDownX;
        //通过滑动的距离计算出X,Y方向的速度
        mVelocityTracker.computeCurrentVelocity(1000);
        float velocityX = Math.abs(mVelocityTracker.getXVelocity());
        float velocityY = Math.abs(mVelocityTracker.getYVelocity());
        boolean dismiss = false; //item是否要滑出屏幕
        boolean dismissRight = false;//是否往右边删除
        //当拖动item的距离大于item的一半,item滑出屏幕
        if (Math.abs(deltaX) > mViewWidth / 2) {
            dismiss = true;
            dismissRight = deltaX > 0;
            //手指在屏幕滑动的速度在某个范围内,也使得item滑出屏幕
        } else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity && velocityY < velocityX) {
            dismiss = true;
            dismissRight = mVelocityTracker.getXVelocity() > 0;
        }
        if (dismiss) {
            AnimatorSet set = new AnimatorSet();
            set.playTogether(ObjectAnimator.ofFloat(mDownView, "translationX", dismissRight ? mViewWidth : -mViewWidth),
                    ObjectAnimator.ofFloat(mDownView, "alpha", 0));
            set.setDuration(mAnimationTime).start();
            set.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    //Item滑出界面之后执行删除
                    performDismiss(mDownView, mDownPosition);
                }
            });
        } else {
            //将item滑动至开始位置
            com.nineoldandroids.view.ViewPropertyAnimator.animate(mDownView)
                    .translationX(0)
                    .alpha(1)
                    .setDuration(mAnimationTime).setListener(null);
        }
        //移除速度检测
        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
        mSwiping = false;
    }

    /**
     * 在此方法中执行item删除之后,其他的item向上或者向下滚动的动画,并且将position回调到方法onDismiss()中
     *
     * @param dismissView
     * @param dismissPosition
     */
    private void performDismiss(final View dismissView, final int dismissPosition) {
        final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();//获取item的布局参数
        final int originalHeight = dismissView.getHeight();//item的高度
        ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 0).setDuration(mAnimationTime);
        animator.start();
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (onDismissCallback != null) {
                    onDismissCallback.onDismiss(dismissPosition);
                }
                //这段代码很重要,因为我们并没有将item从ListView中移除,而是将item的高度设置为0
                //所以我们在动画执行完毕之后将item设置回来
                ViewHelper.setAlpha(dismissView, 1f);
                ViewHelper.setTranslationX(dismissView, 0);
                ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
                lp.height = originalHeight;
                dismissView.setLayoutParams(lp);
            }
        });
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //这段代码的效果是ListView删除某item之后,其他的item向上滑动的效果
                lp.height = (Integer) valueAnimator.getAnimatedValue();
                dismissView.setLayoutParams(lp);
            }
        });
    }

}

布局文件list_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.cjm.bignews.UI.SwipeDismissListView
        android:id="@+id/swipdislist"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </com.example.cjm.bignews.UI.SwipeDismissListView>
</LinearLayout>

Activity 这里就用简单的默认Adapter,用自定义的BaseAdapter都是相似的操作

public class ListActivity extends Activity{

    private SwipeDismissListView listView;
    private List<String> data;
    private ArrayAdapter<String> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.list_layout);
        InitData();
        InitView();
        InitListener();
    }

    private void InitData(){
        data=new ArrayList<String>();
        data.add("卡牌大师");
        data.add("小鱼人");
        data.add("EZ");
        data.add("卡特琳娜");
    }

    private void InitView(){
        listView=(SwipeDismissListView)findViewById(R.id.swipdislist);
        adapter=new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data);
        listView.setAdapter(adapter);
    }

    private void InitListener(){
        listView.setOnDismissCallback(new SwipeDismissListView.OnDismissCallback() {
            @Override
            public void onDismiss(int dismissPosition) {
                data.remove(dismissPosition);
                adapter.notifyDataSetChanged();
            }
        });
    }

}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值