使用RecyclerView打造QQ条目侧滑效果

版权声明:本文为博主原创文章,转载请注明地址。 https://blog.csdn.net/huangxiaoguo1/article/details/54177004

说起这个功能,先吐槽一下,刚来不久的一个产品经理,自己虽然使用的是IOS手机,但也不能什么效果都是说人家IOS的效果交互设计的号,我们就按照它的效果做!

IOS自带的这个破侧滑功能,看着丑的要死,干嘛非要这个干,干,干……….啊…………啊………….

如果她不是个女的,我就……………………………….

好了!看看效果吧!
这里写图片描述

这里写图片描述

大致效果就是模仿QQ条目侧滑自己写了一个:

一、首先我们要自定义一个SwipeLayout继承自FrameLayout
来包装item的子view,子view分为两部分,一部分实在默认状态下(就是没侧滑)我们看的的内容,另一部分被隐藏在最右侧。

  • 那好我们来看看SwipeLayout具体内容:
package cn.hnshangyu.swipelayout.view;

import android.content.Context;
import android.graphics.Rect;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import cn.hnshangyu.swipelayout.R;


/**
 * =========================================
 * 版权所有 违法必究
 * 作者: huangxiaoguo.
 * =========================================
 */
public class SwipeLayout extends FrameLayout {


    /**
     * 滑动状态
     */
    public enum SwipeState {
        OPEN, CLOSE, SWIPING
    }

    private SwipeState swipeState = SwipeState.CLOSE;//默认关闭状态
    private OnSwipeChangeListener onSwipeChangeListener;

    public OnSwipeChangeListener getOnSwipeChangeListener() {
        return onSwipeChangeListener;
    }

    public void setOnSwipeChangeListener(OnSwipeChangeListener onSwipeChangeListener) {
        this.onSwipeChangeListener = onSwipeChangeListener;
    }

    public interface OnSwipeChangeListener {
        void onOpen(SwipeLayout layout);

        void onClose(SwipeLayout layout);

        void onSwiping(SwipeLayout layout);


        // 将要打开     当前是 关闭状态 ----> 拖动
        void onStartOpen(SwipeLayout layout);

        //将要关闭    当前是 打开i状态--->拖动
        void onStartClose(SwipeLayout layout);

    }


    private ViewDragHelper mViewDragHelper;
    private ViewGroup mBackLayout;
    private ViewGroup mFrontLayout;
    private int mWidth;
    private int mHeight;
    private int mRange;

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

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

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

    //1 .初始化 ViewDragHelper对象
        mViewDragHelper = ViewDragHelper.create(this, 1.0f, callBack);
    }

    //2. 将touch 事件 转交给 mViewDragHelper
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 交给 mViewDragHelper 决定是否拦截
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                Log.e("Log","onTouchEvent---ACTION_UP");
                if (swipeState == SwipeState.CLOSE) {
                    Log.e("Log","onTouchEvent---ACTION_UP--swipeState");

                    return false;
                }
                break;
        }
        // 让 mViewDragHelper 接收到 触摸事件
        try {
            mViewDragHelper.processTouchEvent(event);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    //测量 会调用很多次
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    // 测量完成后   值改变后才会调用
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        //拖动范围
        mRange = mBackLayout.getMeasuredWidth();
    }

    /**
     * 放置 子view
     *
     * @param changed
     * @param left
     * @param top
     * @param right
     * @param bottom
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        layoutInit(false);
    }

    private void layoutInit(boolean isOpen) {
        Rect frontRect = computeFrontRect(isOpen);
        //f放置  mFrontLayout
        mFrontLayout.layout(frontRect.left, frontRect.top, frontRect.right, frontRect.bottom);
        Rect backRect = computeBackRect(frontRect);

        mBackLayout.layout(backRect.left, backRect.top, backRect.right, backRect.bottom);

        //将 控件前置
        bringChildToFront(mFrontLayout);
    }

    /**
     * 计算 mBackLayout 矩形位置
     *
     * @param frontRect
     * @return
     */
    private Rect computeBackRect(Rect frontRect) {
        int left = frontRect.right;
        return new Rect(left, frontRect.top, left + mRange, frontRect.bottom);
    }

    /**
     * 计算 mFrontLayout矩形位置
     *
     * @param isOpen
     * @return
     */
    private Rect computeFrontRect(boolean isOpen) {
        int left = 0;
        if (isOpen) {
            left = -mRange;
        } else {
            left = 0;
        }

        return new Rect(left, 0, left + mWidth, 0 + mHeight);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        //添加健壮性 判断
        //1 比如有 两个 或者 两个以上子view
        int childCount = getChildCount();
        if (childCount < 2) {
            throw new IllegalStateException("You must have 2 children at least!! 你得有 至少两个子view!!");
        }
        //2 校验都是viewGroup
        if (getChildAt(0) == null || !(getChildAt(0) instanceof ViewGroup) || getChildAt(1) == null && !(getChildAt(1) instanceof ViewGroup)) {
            throw new IllegalArgumentException("your child must be instance of ViewGroup! 你的view 必须是 viewgroup 的子类 ");
        }

        //  后边菜单
        mBackLayout = (ViewGroup) findViewById(R.id.layout_back);
        //前置条目
        mFrontLayout = (ViewGroup) findViewById(R.id.layout_front);
    }

    //    3  mViewDragHelper 解析完 touch事件  ----》CallBack
    ViewDragHelper.Callback callBack = new ViewDragHelper.Callback() {

        /**
         *返回值决定是否 可以拖动
         * @param child   拖拽的view对象  子view
         * @param pointerId  多指   手指的id
         * @return
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {

//            return child == mFrontLayout;
            return true;
        }

        /**
         * 当 view 被捕获的时候调用
         * @param capturedChild
         * @param activePointerId
         */
        @Override
        public void onViewCaptured(View capturedChild, int activePointerId) {
            super.onViewCaptured(capturedChild, activePointerId);
        }

        /**
         * 获取横向  拖拽范围   不决定 能否拖动
         * 做伴随动画 计算执行时长   ,计算敏感度  >0
         * @param child
         * @return
         */
        @Override
        public int getViewHorizontalDragRange(View child) {
            //返回实际的拖动范围
            return mRange;
        }

        /**
         *  1.   修正  位置   left  2. 没有 开始真正的移动
         * @param child
         * @param left
         * @param dx
         * @return
         */

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            //child  正在拖动的子view
            //left  建议达到的位置
            //dx  deltaX 水平方向的瞬间变化量
//            int currentLeft = mFrontLayout.getLeft();
//            System.out.println( "currentLeft = "+currentLeft+"dx"+dx+" =? "+left);
            if (child == mFrontLayout) {
                left = fixedFrontLeft(left);
            } else if (child == mBackLayout) {
                left = fixedBackLeft(left);
            }

            return left;
        }


        private int fixedFrontLeft(int left) {
            if (left < -mRange) {
                left = -mRange;
            } else if (left > 0) {
                left = 0;
            }
            return left;
        }

        private int fixedBackLeft(int left) {
            if (left < (mWidth - mRange)) {
                left = mWidth - mRange;
            } else if (left > mWidth) {
                left = mWidth;
            }
            return left;
        }

        /**
         * 位置改变的时候调用  1. 伴随动画 2. 状态变化 3.  添加回调
         * @param changedView
         * @param left
         * @param top
         * @param dx
         * @param dy
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
//            System.out.println("onViewPositionChanged>>>mBackLayout " + mBackLayout.getLeft());
            //changedView 当前正在拖动的子view
            //left  clampViewPositionHorizontal  的返回值
//            top
//            dx   横向的瞬间变化量
            if (changedView == mFrontLayout) {  //拖动mFrontLayout  让 mBackLayout跟着出来
                mBackLayout.offsetLeftAndRight(dx);
            } else if (changedView == mBackLayout) {//mBackLayout 转交 给mFrontLayout
                mFrontLayout.offsetLeftAndRight(dx);
            }


            dispatchEvent();


            //手动 刷新  重新绘制
            invalidate();

        }

//        @Override
//        public int clampViewPositionVertical(View child, int top, int dy) {
//            return top;
//        }

        // 当 拖动的view 释放的时候调用
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            //releasedChild  释放的view
            //xvel   释放时横向的速度 向左  -  +    停止后释放  0
            //yvel 释放时纵向的速度
//            System.out.println(" releasedChild = " + releasedChild + "::xvel = " + xvel);

            // 释放时 位置小于 -mRange 丙炔速度为0
            if (mFrontLayout.getLeft() < -mRange * 0.5f && xvel == 0) {
                open();
            } else if (xvel < 0) { // 向左快速滑动
                open();
            } else {
                close();
            }
        }


    };

    /**
     * 1. 更新状态 2.添加回调
     */
    private void dispatchEvent() {
        SwipeState preState = swipeState;
        swipeState = updateState();
        if (onSwipeChangeListener != null) {
            onSwipeChangeListener.onSwiping(this);
            if (swipeState != preState) { //当前状态和上一个状态不一样
                if (swipeState == SwipeState.OPEN) {
                    onSwipeChangeListener.onOpen(this);
                } else if (swipeState == SwipeState.CLOSE) {
                    onSwipeChangeListener.onClose(this);
                } else if (preState == SwipeState.OPEN) {
                    onSwipeChangeListener.onStartClose(this);
                } else if (preState == SwipeState.CLOSE) {
                    onSwipeChangeListener.onStartOpen(this);
                }
            }
        }
    }

    /**
     * 获取当前 最新状态
     *
     * @return
     */
    private SwipeState updateState() {
        if (mFrontLayout.getLeft() == -mRange) { // 打开
            return SwipeState.OPEN;
        } else if (mFrontLayout.getLeft() == 0) {
            return SwipeState.CLOSE;
        }
        return SwipeState.SWIPING;
    }

    //  scroller 执行会调用此方法     computeScroll 会调用很多次
    @Override
    public void computeScroll() {
        super.computeScroll();
        //  是否继续触发动画
        if (mViewDragHelper.continueSettling(true)) {
            //执行动画
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    public void open(boolean isSmooth) {
        if (isSmooth) {
            int finalLeft = -mRange;// 最终的位置
            //  返回值 决定是否触发动画
            boolean b = mViewDragHelper.smoothSlideViewTo(mFrontLayout, finalLeft, 0);
            if (b) {
                // 执行动画
                ViewCompat.postInvalidateOnAnimation(this);
            }

        } else {
            layoutInit(true);
        }
    }

    /**
     * 打开
     */
    public void open() {
        open(true);//默认平滑状态
    }

    public void close(boolean isSmooth) {
        if (isSmooth) {
            int finalLeft = 0;// 最终的位置
            //  返回值 决定是否触发动画
            boolean b = mViewDragHelper.smoothSlideViewTo(mFrontLayout, finalLeft, 0);
            if (b) {
                // 执行动画
                ViewCompat.postInvalidateOnAnimation(this);
            }

        } else {
            layoutInit(false);
        }
    }

    /**
     * 关闭
     */
    public void close() {
        close(true);// 默认平滑关闭
    }
}

代码里面解释的很详细,再让我解释,都不知道该说啥了!

  • 我们再来看看item的布局
<cn.hnshangyu.swipelayout.view.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipelayout"
    android:layout_width="match_parent"
    android:layout_height="60dp">

    <LinearLayout
        android:id="@+id/layout_back"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:descendantFocusability="blocksDescendants"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/placed_top"
            android:layout_width="60dp"
            android:layout_height="match_parent"
            android:background="#bdbdbd"
            android:gravity="center"
            android:text="置顶"
            android:textColor="@android:color/white"
            android:textSize="16dp" />

        <TextView
            android:id="@+id/no_read"
            android:layout_width="100dp"
            android:layout_height="match_parent"
            android:background="#e6be62"
            android:gravity="center"
            android:text="标为未读"
            android:textColor="@android:color/white"
            android:textSize="16dp" />

        <TextView
            android:id="@+id/delete"
            android:layout_width="60dp"
            android:layout_height="match_parent"
            android:background="#e92f2f"
            android:gravity="center"
            android:text="删除"
            android:textColor="@android:color/white"
            android:textSize="16dp" />

    </LinearLayout>

    <RelativeLayout
        android:id="@+id/layout_front"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:descendantFocusability="blocksDescendants"
        android:padding="8dp">

        <ImageView
            android:id="@+id/icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:gravity="center_vertical"
            android:src="@mipmap/icon_head" />

        <TextView
            android:id="@+id/textview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="8dp"
            android:layout_toRightOf="@+id/icon"
            android:gravity="center_vertical"
            android:text="姓名"
            android:textSize="16dp" />
    </RelativeLayout>
</cn.hnshangyu.swipelayout.view.SwipeLayout>

可以看到item分为前后两部分,

  • mainActivity的布局很简单只有一个RecyclerView
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="cn.hnshangyu.swipelayout.MainActivity">


    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

好了布局我们完成了,现在要看看我们的adapter了

package cn.hnshangyu.swipelayout.adapter;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import java.util.concurrent.CopyOnWriteArrayList;

import butterknife.ButterKnife;
import butterknife.InjectView;
import cn.hnshangyu.swipelayout.R;
import cn.hnshangyu.swipelayout.view.SwipeLayout;


public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    private Context mContext;
    private CopyOnWriteArrayList<String> mNameList;
    private SwipeLayout preLayout;//记录上一个打开

    public SwipeLayout getPreLayout() {
        return preLayout;
    }

    public MyAdapter(Context context, CopyOnWriteArrayList<String> nameList) {
        this.mContext = context;
        this.mNameList = nameList;
    }

    private OnItemClickListener onItemClickListener;

    public interface OnItemClickListener {
        void onOpen(SwipeLayout layout);

        void onClose(SwipeLayout layout);

        void onSwiping(SwipeLayout layout);

        void onStartOpen(SwipeLayout layout);

        void onStartClose(SwipeLayout layout);

        void onpLacedTop(int position);

        void onNoRead(int position);

        void onDelete(int position);

        void onItemClick(int position);
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.item_swipe, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        holder.textview.setText(mNameList.get(position));
        holder.swipelayout.setOnSwipeChangeListener(new SwipeLayout.OnSwipeChangeListener() {
            @Override
            public void onOpen(SwipeLayout layout) {
                preLayout = layout;

                if (onItemClickListener != null) {
                    onItemClickListener.onOpen(layout);
                }
            }

            @Override
            public void onClose(SwipeLayout layout) {
                if (onItemClickListener != null) {
                    onItemClickListener.onClose(layout);
                }
            }

            @Override
            public void onSwiping(SwipeLayout layout) {
                if (onItemClickListener != null) {
                    onItemClickListener.onSwiping(layout);
                }
            }

            @Override
            public void onStartOpen(SwipeLayout layout) {
                if (preLayout != null) {
                    preLayout.close();
                }
                if (onItemClickListener != null) {
                    onItemClickListener.onStartOpen(layout);
                }
            }

            @Override
            public void onStartClose(SwipeLayout layout) {
                if (onItemClickListener != null) {
                    onItemClickListener.onStartClose(layout);
                }
            }
        });
        holder.layoutFront.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return false;
            }
        });
        holder.placedTop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (onItemClickListener != null) {
                    onItemClickListener.onpLacedTop(position);
                }
            }
        });
        holder.noRead.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (onItemClickListener != null) {
                    onItemClickListener.onNoRead(position);
                }
            }
        });
        holder.delete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (onItemClickListener != null) {
                    onItemClickListener.onDelete(position);
                }
            }
        });
        holder.layoutFront.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (onItemClickListener != null) {
                    onItemClickListener.onItemClick(position);
                }
            }
        });
    }


    @Override
    public int getItemCount() {
        return mNameList.size();
    }


    class ViewHolder extends RecyclerView.ViewHolder {

        @InjectView(R.id.placed_top)
        TextView placedTop;
        @InjectView(R.id.no_read)
        TextView noRead;
        @InjectView(R.id.delete)
        TextView delete;
        @InjectView(R.id.layout_back)
        LinearLayout layoutBack;
        @InjectView(R.id.icon)
        ImageView icon;
        @InjectView(R.id.textview)
        TextView textview;
        @InjectView(R.id.layout_front)
        RelativeLayout layoutFront;
        @InjectView(R.id.swipelayout)
        SwipeLayout swipelayout;

        public ViewHolder(View itemView) {
            super(itemView);
            ButterKnife.inject(this, itemView);
        }
    }

}

在adapter中自定义监听,便于与Activity的交互

  • Activity的实现
package cn.hnshangyu.swipelayout;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.AbsListView;

import java.util.concurrent.CopyOnWriteArrayList;

import butterknife.ButterKnife;
import butterknife.InjectView;
import cn.hnshangyu.swipelayout.adapter.MyAdapter;
import cn.hnshangyu.swipelayout.utils.ToastUtil;
import cn.hnshangyu.swipelayout.view.RecycleViewDivider;
import cn.hnshangyu.swipelayout.view.SwipeLayout;


public class MainActivity extends AppCompatActivity {

    @InjectView(R.id.recyclerView)
    RecyclerView mRecyclerView;
    private LinearLayoutManager manager;
    private Context mContext;
    private MyAdapter mAdapter;
    private CopyOnWriteArrayList<String> NameList = new CopyOnWriteArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
        ButterKnife.inject(this);
        initData();
        initView();
        initListener();
    }

    private void initData() {
        for (int i = 0; i < 108; i++) {
            NameList.add("huangxiaoguo" + i);
        }
    }


    private void initView() {
        manager = new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(manager);
        int mColor = ContextCompat.getColor(mContext, R.color.light_gray);
        mRecyclerView.addItemDecoration(new RecycleViewDivider(mContext, LinearLayoutManager.HORIZONTAL, 2, mColor));
        mAdapter = new MyAdapter(mContext, NameList);
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                    SwipeLayout preLayout = mAdapter.getPreLayout();
                    if (preLayout != null) {
                        preLayout.close();
                    }
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
            }
        });
    }

    private void initListener() {
        mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
            @Override
            public void onOpen(SwipeLayout layout) {
                ToastUtil.showToast(mContext, "打开");
            }

            @Override
            public void onClose(SwipeLayout layout) {
                ToastUtil.showToast(mContext, "关闭");

            }

            @Override
            public void onSwiping(SwipeLayout layout) {
                ToastUtil.showToast(mContext, "正在移动");

            }

            @Override
            public void onStartOpen(SwipeLayout layout) {
                ToastUtil.showToast(mContext, "开始打开");

            }

            @Override
            public void onStartClose(SwipeLayout layout) {
                ToastUtil.showToast(mContext, "开始关闭");

            }

            @Override
            public void onpLacedTop(int position) {
                ToastUtil.showToast(mContext, "置顶"+NameList.get(position));
            }

            @Override
            public void onNoRead(int position) {
                ToastUtil.showToast(mContext, "标记未读"+NameList.get(position));
            }

            @Override
            public void onDelete(int position) {
                ToastUtil.showToast(mContext, "删除"+NameList.get(position));
            }

            @Override
            public void onItemClick(int position) {
                ToastUtil.showToast(mContext, NameList.get(position));
            }
        });
    }
}

Activity中有相对应的监听回调,这样就可以进行我们下一步操作了!

Demo下载地址:http://download.csdn.net/download/huangxiaoguo1/9731545

阅读更多
换一批

没有更多推荐了,返回首页