Android--ListView的item水平滑动效果

ListView的item水平滑动效果

---------------------------------------------------------------------

步骤:

1、自定义Item布局view

1、自定义布局中有至少两个view要显示:一个正常显示的布局contentView,一个隐藏起来的布局menuView
2、自定义布局中,设置两个布局的位置,通过onMeasure(),onLayout()两个方法设置
3、定义一个滑动处理方法,从父类获取到滑动事件,对滑动效果处理。
ACTION_DOWN:记录下滑动事件的初始位置。
ACTION_MOVE:根据滑动距离,显示view的位置
ACTION_UP:滑动结束后的操作

2、自定义ListView ,主要控制滑动事件的处理和分发

主要是onTouchEvent方法:
ACTION_DOWN:获取按下时,点击到的是哪个Item,记录下滑动事件的初始位置。
ACTION_MOVE:根据滑动距离,判断滑动:
上下滑动:不作处理,交给listview处理
左右滑动:将滑动事件分发给该Item,并return true,由Item处理滑动事件
ACTION_UP:当时左右滑动时,将滑动事件分发给该Item,并return true,由Item处理滑动事件

---------------------------------------------------------------------

QQ的一个聊天界面的listview每一行向左滑动的时候,会出现删除的按钮,特别炫酷,这个效果可以有,今天跟大家分享下。 
先上demo的效果图 
Android--ListView的item水平滑动效果_第1张图片 
Android--ListView的item水平滑动效果_第2张图片 
界面很丑,因为主要是介绍功能,界面什么的,搞那么复杂,下demo的时候还浪费资源,哈哈哈。

用到的几个类(4个) 
SwipeItemLayout,SwipeListView,SwipeAdapter,FragmentTestActivity. 
SwipeItemLayout就是listView的一个item,这个类集成了FrameLayout。SwipeListView是重写的一个ListView,其实主要在她的OnTouch事件的处理上。SwipeAdapter是一个adapter,这个不用解释了,FragmenTestActivity这个就是怎么用的了。 
好,一个一个来

首先我们看一个item怎么写,先上代码,代码里面基本上有逐行的解释。

public class SwipeItemLayout extends FrameLayout {
    //这个是内容的item,也就是不左滑的时候的布局
    private View contentView = null;
    //这个是左滑之后显示的那个部分,即多出的部分
    private View menuView = null;
    //这个是动画的速度控制器,其实没用到
    private Interpolator closeInterpolator = null;
    private Interpolator openInterpolator = null;
    //控制控件滑动的,会平滑滑动,一个开一个关
    private ScrollerCompat mOpenScroller;
    private ScrollerCompat mCloseScroller;
    //左滑之后,contentView左边距离屏幕左边的距离,基线,用于滑回
    private int mBaseX;
    //手指点击的初始位置
    private int mDownX;
    //当前item的状态,open和close两种
    private int state = STATE_CLOSE;

    private static final int STATE_CLOSE = 0;
    private static final int STATE_OPEN = 1;
    //构造函数
    public SwipeItemLayout(View contentView,View menuView,Interpolator closeInterpolator, Interpolator openInterpolator){
        super(contentView.getContext());
        this.contentView = contentView;
        this.menuView = menuView;
        this.closeInterpolator = closeInterpolator;
        this.openInterpolator = openInterpolator;

        init();
    }

    private void init(){
        //设置一个item的宽和高,其实就是设置宽充满而已
        setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.WRAP_CONTENT));
        //初始化mColoseScroller和mOpenScroller
        if (closeInterpolator != null) {
            mCloseScroller = ScrollerCompat.create(getContext(),
                    closeInterpolator);
        } else {
            mCloseScroller = ScrollerCompat.create(getContext());
        }
        if (openInterpolator != null) {
            mOpenScroller = ScrollerCompat.create(getContext(),
                    openInterpolator);
        } else {
            mOpenScroller = ScrollerCompat.create(getContext());
        }
        //这也是设置宽和高
        LayoutParams contentParams = new LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        contentView.setLayoutParams(contentParams);

        menuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
                LayoutParams.WRAP_CONTENT));
        //将这两个布局都add到这个view中
        addView(contentView);
        addView(menuView);
    }
    //这个类就是当用户在界面上滑动的时候,通过ListView的onTouch方法,将MotionEvent的动作传到这里来,通过这个函数执行操作。
    public boolean onSwipe(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            //记录当前手指点击的x的坐标
            mDownX = (int) event.getX();
            break;
        case MotionEvent.ACTION_MOVE:
            //当手指移动的时候,获取这个差值
            int dis = (int) (mDownX - event.getX());
            //这个地方,当状态是open的时候,为啥要执行 += 这个操作,我们在下面就会找到答案的
            if (state == STATE_OPEN) {
                dis += menuView.getWidth();
            }
            //这个函数在下面说
            swipe(dis);
            break;
        case MotionEvent.ACTION_UP:
        //这里其实是一个判断,当用户滑了menuView的一半的时候,自动滑出来,否则滑进去。
            if ((mDownX - event.getX()) > (menuView.getWidth() / 2)) {
                // 平滑的滑出
                smoothOpenMenu();
            } else {
                // 平滑的滑进
                smoothCloseMenu();
                return false;
            }
            break;
        }
        //这个地方一定要return true,才能保证这个动作不会继续往下传递
        return true;
    }
    // 判断是否滑出的状态
    public boolean isOpen() {
        return state == STATE_OPEN;
    }
    //这个方法就是滑动dis的距离,还记得那个 += 吗,如果dis > menuView.getWidth()的 话,dis = menuView.getWidth().这样,当滑到最大限度的时候,就不会滑动了
    private void swipe(int dis) {
        if (dis > menuView.getWidth()) {
            dis = menuView.getWidth();
        }
        if (dis < 0) {
            dis = 0;
        }
        // layout的四个参数分别是(l,t,r,b),这样实现contentView的移动,这个应该没问题的吧?
        contentView.layout(-dis, contentView.getTop(),
                contentView.getWidth() - dis, getMeasuredHeight());
        // 这个跟上面方法一样
        menuView.layout(contentView.getWidth() - dis, menuView.getTop(),
                contentView.getWidth() + menuView.getWidth() - dis,
                menuView.getBottom());
    }
    //这个方法是系统的方法,就是执行一个刷新而已
    @Override
    public void computeScroll() {
        if (state == STATE_OPEN) {
            if (mOpenScroller.computeScrollOffset()) {
                swipe(mOpenScroller.getCurrX());
                postInvalidate();
            }
        } else {
            if (mCloseScroller.computeScrollOffset()) {
                swipe(mBaseX - mCloseScroller.getCurrX());
                postInvalidate();
            }
        }
    }
    // 额,这个不用解释了
    public void smoothCloseMenu() {
        state = STATE_CLOSE;
        mBaseX = -contentView.getLeft();
        mCloseScroller.startScroll(0, 0, mBaseX, 0, 350);
        postInvalidate();
    }
    // 额 这个也不用解释了
    public void smoothOpenMenu() {
        state = STATE_OPEN;
        mOpenScroller.startScroll(-contentView.getLeft(), 0,
                menuView.getWidth(), 0, 350);
        postInvalidate();
    }
    // 这个也懒得解释了
    public void closeMenu() {
        if (mCloseScroller.computeScrollOffset()) {
            mCloseScroller.abortAnimation();
        }
        if (state == STATE_OPEN) {
            state = STATE_CLOSE;
            swipe(0);
        }
    }
    // 各位码大大最棒了
    public void openMenu() {
        if (state == STATE_CLOSE) {
            state = STATE_OPEN;
            swipe(menuView.getWidth());
        }
    }

    public View getContentView() {
        return contentView;
    }

    public View getMenuView() {
        return menuView;
    }
    //这个方法 其实就是获取menuView的宽和高
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        menuView.measure(MeasureSpec.makeMeasureSpec(0,
                MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(
                getMeasuredHeight(), MeasureSpec.EXACTLY));
    }
    //这个方法就把两个控件的相对布局表现出来了
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        contentView.layout(0, 0, getMeasuredWidth(),
                contentView.getMeasuredHeight());
        menuView.layout(getMeasuredWidth(), 0,
                getMeasuredWidth() + menuView.getMeasuredWidth(),
                contentView.getMeasuredHeight());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183

接下来就是另一个重要的类了,那就是ListView到底怎样,之后我还是把源码放上去,绝对不要积分,有需要的可以自己下了看看。这里看下核心的代码(主要原因是加班时间快要到了,再不回去的话就出不去了,哈哈哈)

这是变量,额,写代码的时候没写注释,我怕你们看不懂额,就在这里写算了

//表示没有触摸的时候
private static final int TOUCH_STATE_NONE = 0;
    // 水平滑动的时候哦
    private static final int TOUCH_STATE_X = 1;
    // 垂直滑动的时候
    private static final int TOUCH_STATE_Y = 2;
    //这是设置的两个方向的阀值
    private int MAX_Y = 5;
    private int MAX_X = 3;
    // 记录初始时候的坐标
    private float mDownX;
    //状态标志符
    private int mTouchState;
    // 触摸的位置
    private int mTouchPosition;
    private SwipeItemLayout mTouchView;
    //private OnSwipeListener mOnSwipeListener;

    private Interpolator mCloseInterpolator;
    private Interpolator mOpenInterpolator;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

说完了变量之后,核心的就两个方法

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null)
            return super.onTouchEvent(ev);
        int action = MotionEventCompat.getActionMasked(ev);
        action = ev.getAction();
        switch (action) {
        case MotionEvent.ACTION_DOWN:
            int oldPos = mTouchPosition;
            mDownX = ev.getX();
            mDownY = ev.getY();
            mTouchState = TOUCH_STATE_NONE;
            //这个方法就是获取当前的x,y坐标对应的是listView中的哪个position,是系统方法。
            mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());

            if (mTouchPosition == oldPos && mTouchView != null
                    && mTouchView.isOpen()) {
                mTouchState = TOUCH_STATE_X;
                mTouchView.onSwipe(ev);
                return true;
            }
            //这个方法获取当前的item的View,也是系统的方法
            View view = getChildAt(mTouchPosition - getFirstVisiblePosition());

            if (mTouchView != null && mTouchView.isOpen()) {
                mTouchView.smoothCloseMenu();
                mTouchView = null;
                return super.onTouchEvent(ev);
            }
            if (view instanceof SwipeItemLayout) {
                mTouchView = (SwipeItemLayout) view;
            }
            if (mTouchView != null) {
                mTouchView.onSwipe(ev);
            }
            break;
        case MotionEvent.ACTION_MOVE:
            float dy = Math.abs((ev.getY() - mDownY));
            float dx = Math.abs((ev.getX() - mDownX));
            if (mTouchState == TOUCH_STATE_X) {
                if (mTouchView != null) {
                    mTouchView.onSwipe(ev);
                }
                getSelector().setState(new int[] { 0 });
                ev.setAction(MotionEvent.ACTION_CANCEL);
                super.onTouchEvent(ev);
                return true;
            } else if (mTouchState == TOUCH_STATE_NONE) {
                if (Math.abs(dy) > MAX_Y) {
                    mTouchState = TOUCH_STATE_Y;
                } else if (dx > MAX_X) {
                    mTouchState = TOUCH_STATE_X;
//                  if (mOnSwipeListener != null) {
//                      mOnSwipeListener.onSwipeStart(mTouchPosition);
//                  }
                }
            }
            break;
        case MotionEvent.ACTION_UP:
            if (mTouchState == TOUCH_STATE_X) {
                if (mTouchView != null) {
                    mTouchView.onSwipe(ev);
                    if (!mTouchView.isOpen()) {
                        mTouchPosition = -1;
                        mTouchView = null;
                    }
                }
//              if (mOnSwipeListener != null) {
//                  mOnSwipeListener.onSwipeEnd(mTouchPosition);
//              }
                ev.setAction(MotionEvent.ACTION_CANCEL);
                super.onTouchEvent(ev);
                return true;
            }
            break;
        }
        return super.onTouchEvent(ev);
    }

    public void smoothOpenMenu(int position) {
        if (position >= getFirstVisiblePosition()
                && position <= getLastVisiblePosition()) {
            View view = getChildAt(position - getFirstVisiblePosition());
            if (view instanceof SwipeItemLayout) {
                mTouchPosition = position;
                if (mTouchView != null && mTouchView.isOpen()) {
                    mTouchView.smoothCloseMenu();
                }
                mTouchView = (SwipeItemLayout) view;
                mTouchView.smoothOpenMenu();
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

唉,实在是很麻烦,其实代码不难,就是很繁琐,各种控制,各种判断,大家把这段代码中的核心的几个方法搞明白了,也就没问题了。我就不多说了哈。。。

好了,剩下的两个类,一个是adapter类,一个是用法,我就只说adapter中的一个getView方法了哈,用法的话跟一般的ListView的用法一样,对了,说adapter不就是再说用法么,哈哈哈。

@Override
    public View getView(int position, View contentView, ViewGroup arg2) {
        ViewHolder holder = null;
        if(contentView==null){
            holder = new ViewHolder();
            View view01 = LayoutInflater.from(mContext).inflate(R.layout.test01, null);
            View view02 = LayoutInflater.from(mContext).inflate(R.layout.test2, null);

            //这个地方就用到了我们自己写的那个类了,后面两个参数我上面已经说了,没用到,用到的话也可以,自己改下代码就好了。其他的没什么区别吧
            contentView = new SwipeItemLayout(view01, view02, null, null);
            contentView.setTag(holder);
        }else{
            holder = (ViewHolder) contentView.getTag();
        }
        //这个地方如果你的menu里面有button什么的,就可以在这个地方注册监听,或者,你直接将view02(上面声明的)自定义也行,在自定义的类中实现onClickListener 方法
//      holder.btn.setOnClickListener(new OnClickListener() {
//          
//          @Override
//          public void onClick(View arg0) {
//              // TODO Auto-generated method stub
//              Toast.makeText(mContext, "click", Toast.LENGTH_LONG).show();
//          }
//      });
        return contentView;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

是不是看完整个过程有点点晕,其实很简单的,你下了源码,自己去看就好了,真的简单,各位大神不要见怪.


demo下载链接(免费)

你可能感兴趣的:(android-View)