listview 侧滑删除 仿qq

侧滑原理:

侧滑是通过手势判断,将中间的view滑出去,将右边的view滑进来,当滑动的距离不够完全显示右边的view的时候执行平滑函数,自动将右边的view滑进去,并改变滑动状态为open。所以需要通过xml定义两个view,一个作为默认显示的view,一个作为隐藏(需要滑入)的view。

先从简单练起:

开始我做了一个简单的demo,没有涉及listview,只是写了简单的一个滑入滑出的效果。

public class ScrollerLinearLayout extends LinearLayout {

    private View centerView = null; //不滑动显示的view
    private View rightView = null; //左滑显示的view

    //用这两个可以实现滑动效果
    private ScrollerCompat mOpenScroller;
    private ScrollerCompat mCloseScroller;

    private int downX; //开始按下的位置

    //记录状态
    private int state = STATE_CLOSE;
    private static final int STATE_CLOSE = 0;
    private static final int STATE_OPEN = 1;

    private int mBaseX;

    public ScrollerLinearLayout(Context context) {
        super(context);
    }

    public ScrollerLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ScrollerLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setContentView(View centerView, View rightView){
        this.centerView = centerView;
        this.rightView = rightView;

        //初始化mColoseScroller和mOpenScroller
        mCloseScroller = ScrollerCompat.create(getContext());
        mOpenScroller = ScrollerCompat.create(getContext());

        initView();
    }

    //child view的布局参数设定好后 添加到parent view里面
    private void initView() {

//        setLayoutParams(new AbsListView.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
        setLayoutParams(new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        //这也是设置宽和高
        LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
        centerView.setLayoutParams(contentParams);
        rightView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));

        //将这两个布局都add到这个view中
        addView(centerView);
        addView(rightView);
    }

    // 判断是否滑出的状态
    public boolean isOpen() {
        return state == STATE_OPEN;
    }

    /**
     * 这里的函数是由 listview来控制的。
     * listview来监听左滑,并判断是否需要将rightview 显示出来。
     * (因为不是所有左滑都要画出rightview,还需要判断listview的其他item的状态不是STATE_OPEN 状态
     * ,所以由listview来控制自己item的rightview是否左滑最合理,这个函数目的就是让listview来调用)
     * 反之右滑亦然
     *
     * @param event
     * @return
     */
    public boolean onSwipeLeft(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                int dis = (int) (downX - event.getX());
                if (state == STATE_OPEN) {
                    dis += rightView.getWidth();
                }
                //手指移动多少距离,rightview也移动多少距离
                swipe(dis);
                break;
            case MotionEvent.ACTION_UP:
                if ((downX - event.getX()) > (rightView.getWidth() / 2)) {
                    smoothOpenMenu(); //自动滑出来
                } else {
                    smoothCloseMenu(); //自动滑进去
                    return false;
                }
                break;
        }
        //消费掉事件
        return true;
    }

    @Override
    public void computeScroll() {
        if (state == STATE_OPEN) {
            if (mOpenScroller.computeScrollOffset()) {
                swipe(mOpenScroller.getCurrX());
                postInvalidate();
            }
        } else {
            if (mCloseScroller.computeScrollOffset()) {
                swipe(mBaseX - mCloseScroller.getCurrX());
                postInvalidate();
            }
        }
    }

    private void swipe(int dis) {
        if (dis > rightView.getWidth()) {
            dis = rightView.getWidth();
        }
        if (dis < 0) {
            dis = 0;
        }
        centerView.layout(-dis, centerView.getTop(), centerView.getWidth() - dis, getMeasuredHeight());
        rightView.layout(centerView.getWidth() - dis, rightView.getTop(), centerView.getWidth() + rightView.getWidth() - dis, rightView.getBottom());
    }

    public void smoothCloseMenu() {
        state = STATE_CLOSE;
        mBaseX = -centerView.getLeft();
        mCloseScroller.startScroll(0, 0, mBaseX, 0, 350);
        postInvalidate();
    }
    public void smoothOpenMenu() {
        state = STATE_OPEN;
        mOpenScroller.startScroll(-centerView.getLeft(), 0, rightView.getWidth(), 0, 350);
        postInvalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(rightView != null)
            rightView.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) {
        if(centerView != null)
            centerView.layout(0, 0, getMeasuredWidth(), centerView.getMeasuredHeight());
        if(rightView != null)
            rightView.layout(getMeasuredWidth(), 0, getMeasuredWidth() + rightView.getMeasuredWidth(), centerView.getMeasuredHeight());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        onSwipeLeft(event);
        return true;
    }
}

MainActivity部分:

private void initScrollerLayout() {

        View centerView1 = LayoutInflater.from(this).inflate(R.layout.view_framelayout_01, null);
        View rightView1 = LayoutInflater.from(this).inflate(R.layout.view_framelayout_02, null);
        ScrollerLinearLayout scrollerLinearLayout1 = (ScrollerLinearLayout)super.findViewById(R.id.activity_listview_scroller_layout01);
        scrollerLinearLayout1.setContentView(centerView1, rightView1);

        View centerView2 = LayoutInflater.from(this).inflate(R.layout.view_framelayout_01, null);
        View rightView2 = LayoutInflater.from(this).inflate(R.layout.view_framelayout_02, null);
        ScrollerLinearLayout scrollerLinearLayout2 = (ScrollerLinearLayout)super.findViewById(R.id.activity_listview_scroller_layout02);
        scrollerLinearLayout2.setContentView(centerView2, rightView2);
    }

XML部分:

view_framelayout_01

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:gravity="center_vertical"
    android:layout_height="match_parent">

    <com.best.android.listviewactivity.CircleImageView
        android:layout_width="50dp"
        android:layout_height="50dp" />

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">

        <RelativeLayout
            android:layout_margin="4dp"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content">

            <TextView
                android:text="android交流群"
                android:textSize="14dp"
                android:layout_alignParentLeft="true"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <TextView
                android:text="15:34"
                android:layout_alignParentRight="true"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

        </RelativeLayout>

        <RelativeLayout
            android:layout_margin="4dp"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content">

            <TextView
                android:text="Emma加入群"
                android:textColor="#666666"
                android:textSize="14dp"
                android:layout_alignParentLeft="true"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <TextView
                android:text="99+"
                android:gravity="center"
                android:textColor="#fff"
                android:background="@mipmap/green_bg"
                android:layout_alignParentRight="true"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

        </RelativeLayout>

    </LinearLayout>

</LinearLayout>
view_framelayout_02:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:background="#444444"
        android:gravity="center"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <TextView
            android:text="置顶"
            android:textColor="#fff"
            android:textSize="16sp"
            android:layout_margin="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/view_framelayout_02_ll_delete"
        android:background="#ee2222"
        android:gravity="center"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <TextView
            android:text="删除"
            android:textColor="#fff"
            android:textSize="16sp"
            android:layout_margin="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

</LinearLayout>


基本侧滑已经实现,但是不是我们最终想要的效果,所以接下来我们要加入listview

与listview关联:

首先更改上面代码的这个部分(将第一句取消注释,第二句注释掉)

//        setLayoutParams(new AbsListView.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
        setLayoutParams(new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
并将ScrollerLinearLayout里面的ontouchevent函数去掉,因为现在事件监听将在listview里面做判断。


主要难点在自定义listview里面的ontouchEvent方法 这里详细讲里面的逻辑。。。。。

首先梳理下思路: 在action.down的时候我们都需要做哪些呢?

1.记录下按下的位置。

2.记录下是listview里面的哪个item

3.状态要改变

4.获取当前的item的view对象。

5.判断item是否和上次点击的item相同,如果不相同那么就直接让view关闭,如果相同那么记录下x座标

action.move的时候我们做的事情比较简单,记录下移动的距离,判断是否是x方向的移动,是的话就改变滑动状态,并直接调用onswipeleft()执行滑动。否则就是让listview上下滚动了。

action.up,我们就需要判断滑动是否执行到位,不到位就调用smoothOpen方法,将没有滑出的部分滑出,没有滑入的部分滑入。

自定义Listview ontouch部分代码

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null)
            return super.onTouchEvent(ev);
        int action = MotionEventCompat.getActionMasked(ev);
//        int disY;
        action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                //按住的item的position
                int oldPos = mTouchPosition;
                //记录位置
                mDownX = ev.getX();
                mDownY = ev.getY();
                mTouchState = TOUCH_STATE_NONE;
                //获取点击的item的position
                mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());

                //判断当前点击的是否和上次点击的item是同一个,如果是同一个,并且状态是打开了的就记录状态和座标
                //mTouchView.onSwipeLeft(ev);在down方法里就是记录X座标的
                if (mTouchPosition == oldPos && mTouchView != null && mTouchView.isOpen()) {
                    mTouchState = TOUCH_STATE_X;
                    mTouchView.onSwipeLeft(ev);
                    return true;
                }
                //获取当前的item的View
                View view = getChildAt(mTouchPosition - getFirstVisiblePosition());

                //如果不是同一个item 那么点击的话就关闭掉之前打开的item
                if (mTouchView != null && mTouchView.isOpen()) {
                    mTouchView.smoothCloseMenu();
                    mTouchView = null;
                    return super.onTouchEvent(ev);
                }
                //判断该view的类型
                if (view instanceof ScrollerLinearLayout) {
                    mTouchView = (ScrollerLinearLayout) view;
                }
                if (mTouchView != null) {
                    mTouchView.onSwipeLeft(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.onSwipeLeft(ev);
                    }
                    return true;
                } else if (mTouchState == TOUCH_STATE_NONE) {
                    //判断滑动方向,x方向执行滑动,Y方向执行滚动
                    if (Math.abs(dy) > MAX_Y) {
                        mTouchState = TOUCH_STATE_Y;
                    } else if (dx > MAX_X) {
                        mTouchState = TOUCH_STATE_X;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mTouchState == TOUCH_STATE_X) {
                    if (mTouchView != null) {
                        mTouchView.onSwipeLeft(ev);
                        //如过最后状态是打开 那么就重新初始化
                        if (!mTouchView.isOpen()) {
                            mTouchPosition = -1;
                            mTouchView = null;
                        }
                    }
                    ev.setAction(MotionEvent.ACTION_CANCEL);
                    super.onTouchEvent(ev);
                    return true;
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

listview     adapter部分代码

@Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if(convertView==null){
            View view01 = LayoutInflater.from(context).inflate(R.layout.view_framelayout_01, null);
            View view02 = LayoutInflater.from(context).inflate(R.layout.view_framelayout_02, null);

            LinearLayout layoutDelete = (LinearLayout)view02.findViewById(R.id.view_framelayout_02_ll_delete);
            layoutDelete.setOnClickListener(new OnDeleteClickListener(position));

//            convertView = new MyFrameLayout(view01, view02);
            ScrollerLinearLayout scrollerLinearLayout = new ScrollerLinearLayout(context);
            scrollerLinearLayout.setContentView(view01, view02);
            convertView = scrollerLinearLayout;
        }

        return convertView;
    

最后效果图:



等下拉刷新功能完成后,会将源码一起上传上去,不过上面都已经将的很详细了,应该都能实现开发功能了。。。。


最后总结下:其实这个思路不仅仅是实现listview的侧滑,按照这个思路来gridview,recycleview都是能实现侧滑效果的,大家多多举一反三。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章