美文网首页
RecyclerView仿QQ消息列表左滑弹出菜单,标记已读和删

RecyclerView仿QQ消息列表左滑弹出菜单,标记已读和删

作者: 闪电代码手 | 来源:发表于2017-05-23 15:21 被阅读231次

    仿QQ消息列表左滑弹出菜单,标记已读和删除

    话不多说,看到这样的效果心动了么?

    这里写图片描述

    1.先上build .gradle,dependencies里面要这样写,其实就是导入v7里面的recyclerView。要用butterknife的话, 记得加到这里来:

    dependencies {
        compile fileTree(include: ['*.jar'], dir: 'libs')
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:support-v4:23.4.0'
        compile 'com.android.support:recyclerview-v7:23.2.0'
        //butterknife
        compile 'com.jakewharton:butterknife:6.1.0'
    }
    

    2.再来列表的item, item_msg_remind.xml:

    消息主体,width记得match_parent,直接把后面两个布局顶出去

        <LinearLayout
            //省略布局
            android:id="@+id/ll_msg_remind_main">
    
            <View
                //省略布局
                android:id="@+id/msg_remind_point" />
    
            <LinearLayout
                //省略布局
                android:orientation="vertical">
    
                <TextView
                    //省略布局
                    android:id="@+id/tv_remind_title"
                    android:layout_weight="1"
                    android:text="隔壁的二蛋" />
    
                <TextView
                   //省略布局
                    android:id="@+id/tv_remind_content"
                    android:layout_weight="2"
                    android:ellipsize="end"
                    android:maxLines="2"
                    android:text="对方撤回了一条消息并砍了你的狗" />
            </LinearLayout>
        </LinearLayout>
    
        <LinearLayout
            //省略布局>
    
            <TextView
                //省略布局
                android:id="@+id/tv_msg_remind_check"
                android:text="标记已读" />
        </LinearLayout>
    
        <LinearLayout
            //省略布局>
    
            <TextView
                //省略布局
                android:id="@+id/tv_msg_remind_delete"
                android:text="删除"/>
        </LinearLayout>
    

    3.那什么,Adapter来了,MsgRemindAdapter:

    消息adapter
    -->特别注意extends后面Adapter<>里面要写自己定义的ViewHolder

    public class MsgRemindAdapter extends RecyclerView.Adapter<MsgRemindAdapter.RemindViewHolder>
            implements ItemSlideHelper.Callback {
    
        private Context context;
        private List<MsgVo> mDatas = new ArrayList<MsgVo>();
    
        private RecyclerView mRecyclerView;
    
        public MsgRemindAdapter(Context context, List<MsgVo> mDatas) {
            this.context = context;
            this.mDatas = mDatas;
        }
    

    onCreateViewHolder

        @Override
        public RemindViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).
                    inflate(R.layout.item_msg_remind, parent, false);
            return new RemindViewHolder(view);
        }
    
        //将recyclerView绑定Slide事件
        @Override
        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
            super.onAttachedToRecyclerView(recyclerView);
            mRecyclerView = recyclerView;
            mRecyclerView.addOnItemTouchListener(new ItemSlideHelper(mRecyclerView.getContext(), this));
        }
    

    onBindViewHolder(),绑定

        @Override
        public void onBindViewHolder(final RemindViewHolder holder, final int position) {
            /**
             * 消息状态
             */
            if (mDatas.get(position).isChecked()) {
                holder.msgRemindPoint.setBackgroundResource(R.drawable.shape_remind_point_gray);
            } else {
                holder.msgRemindPoint.setBackgroundResource(R.drawable.shape_remind_point_theme);
            }
            //消息标题
            holder.tvRemindTitle.setText(mDatas.get(position).getTitle());
            //消息内容
            holder.tvRemindContent.setText(mDatas.get(position).getContent());
    

    -->特别注意,敲黑板了啊!!!在执行notify的时候,取position要取holder.getAdapterPosition(),消息被删除之后,他原来的position是final的,所以取到的值不准确,会报数组越界。

    消息主体监听,这里我是让他添加一条数据,替换成你需要的操作即可

            holder.llMsgRemindMain.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    addData(mDatas.size());
                }
            });
    

    标记已读监听

            holder.tvMsgRemindCheck.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    mDatas.get(holder.getAdapterPosition()).setChecked(true);
                    notifyItemChanged(holder.getAdapterPosition());
                }
            });
    

    删除消息监听

            holder.tvMsgRemindDelete.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    removeData(holder.getAdapterPosition());
                }
            });
    

    此方法用来计算水平方向移动的距离

        @Override
        public int getHorizontalRange(RecyclerView.ViewHolder holder) {
            if (holder.itemView instanceof LinearLayout) {
                ViewGroup viewGroup = (ViewGroup) holder.itemView;
                //viewGroup包含3个控件,即消息主item、标记已读、删除,返回为标记已读宽度+删除宽度
                return viewGroup.getChildAt(1).getLayoutParams().width
                        + viewGroup.getChildAt(2).getLayoutParams().width;
            }
            return 0;
        }
    
        @Override
        public RecyclerView.ViewHolder getChildViewHolder(View childView) {
            return mRecyclerView.getChildViewHolder(childView);
        }
    
        @Override
        public View findTargetView(float x, float y) {
            return mRecyclerView.findChildViewUnder(x, y);
        }
    

    自定义的ViewHolder

        public class RemindViewHolder extends RecyclerView.ViewHolder {
            @InjectView(R.id.msg_remind_point)
            View msgRemindPoint;
            @InjectView(R.id.tv_remind_title)
            TextView tvRemindTitle;
            @InjectView(R.id.tv_remind_content)
            TextView tvRemindContent;
            @InjectView(R.id.ll_msg_remind_main)
            LinearLayout llMsgRemindMain;
            @InjectView(R.id.tv_msg_remind_check)
            TextView tvMsgRemindCheck;
            @InjectView(R.id.tv_msg_remind_delete)
            TextView tvMsgRemindDelete;
    
            public RemindViewHolder(View itemView) {
                super(itemView);
                ButterKnife.inject(this, itemView);
            }
        }
    

    添加单条数据

        public void addData(int position) {
            MsgVo vo = new MsgVo();
            if (position % 2 == 1) {
                vo.setChecked(false);
                vo.setTitle("隔壁的二蛋");
                vo.setContent("对方撤回了一条消息并砍了你的狗,问你服不服。");
            } else {
                vo.setChecked(false);
                vo.setTitle("对面的三娃");
                vo.setContent("今天晚上开黑,4缺1,来不来?");
            }
            mDatas.add(position, vo);
            notifyItemInserted(position);
        }
    

    删除单条数据

        public void removeData(int position) {
            mDatas.remove(position);
            notifyItemRemoved(position);
        }
    }
    
    

    4.要做这么高难度的滑动,是要一个ItemSlideHelper的:

    /**
     * 消息列表左滑菜单帮助类
     */
    public class ItemSlideHelper implements RecyclerView.OnItemTouchListener, GestureDetector.OnGestureListener {
    
        private final int DEFAULT_DURATION = 200;
        private View mTargetView;
    
        private int mActivePointerId;
        private int mTouchSlop;
        private int mMaxVelocity;
        private int mMinVelocity;
        private int mLastX;
        private int mLastY;
    
        private boolean mIsDragging;
        private Animator mExpandAndCollapseAnim;
        private GestureDetectorCompat mGestureDetector;
    
        private Callback mCallback;
    
        public ItemSlideHelper(Context context, Callback callback) {
            this.mCallback = callback;
    
            //手势用于处理fling
            mGestureDetector = new GestureDetectorCompat(context, this);
    
            ViewConfiguration configuration = ViewConfiguration.get(context);
            mTouchSlop = configuration.getScaledTouchSlop();
            mMaxVelocity = configuration.getScaledMaximumFlingVelocity();
            mMinVelocity = configuration.getScaledMinimumFlingVelocity();
        }
    
        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
            int action = MotionEventCompat.getActionMasked(e);
            int x = (int) e.getX();
            int y = (int) e.getY();
    
    
            //如果RecyclerView滚动状态不是空闲targetView不是空
            if (rv.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
                if (mTargetView != null) {
                    //隐藏已经打开
                    smoothHorizontalExpandOrCollapse(DEFAULT_DURATION / 2);
                    mTargetView = null;
                }
    
                return false;
            }
    
            //如果正在运行动画 ,直接拦截什么都不做
            if (mExpandAndCollapseAnim != null && mExpandAndCollapseAnim.isRunning()) {
                return true;
            }
    
            boolean needIntercept = false;
            switch (action) {
                case MotionEvent.ACTION_DOWN:
    
                    mActivePointerId = MotionEventCompat.getPointerId(e, 0);
                    mLastX = (int) e.getX();
                    mLastY = (int) e.getY();
    
                    /*
                    * 如果之前有一个已经打开的项目,当此次点击事件没有发生在右侧的菜单中则返回TRUE,
                    * 如果点击的是右侧菜单那么返回FALSE这样做的原因是因为菜单需要响应Onclick
                    * */
                    if (mTargetView != null) {
                        return !inView(x, y);
                    }
    
                    //查找需要显示菜单的view;
                    mTargetView = mCallback.findTargetView(x, y);
    
                    break;
                case MotionEvent.ACTION_MOVE:
    
                    int deltaX = (x - mLastX);
                    int deltaY = (y - mLastY);
    
                    if (Math.abs(deltaY) > Math.abs(deltaX))
                        return false;
    
                    //如果移动距离达到要求,则拦截
                    needIntercept = mIsDragging = mTargetView != null && Math.abs(deltaX) >= mTouchSlop;
                    break;
    
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    /*
                    * 走这是因为没有发生过拦截事件
                    * */
                    if (isExpanded()) {
    
                        if (inView(x, y)) {
                            // 如果走这那行这个ACTION_UP的事件会发生在右侧的菜单中
                        } else {
                            //拦截事件,防止targetView执行onClick事件
                            needIntercept = true;
                        }
    
                        //折叠菜单
                        smoothHorizontalExpandOrCollapse(DEFAULT_DURATION / 2);
                    }
    
                    mTargetView = null;
                    break;
            }
    
            return needIntercept;
        }
    
        private boolean isExpanded() {
            return mTargetView != null && mTargetView.getScrollX() == getHorizontalRange();
        }
    
        private boolean isCollapsed() {
    
            return mTargetView != null && mTargetView.getScrollX() == 0;
        }
    
        /*
        * 根据targetView的scrollX计算出targetView的偏移,这样能够知道这个point
        * 是在右侧的菜单中
        * */
        private boolean inView(int x, int y) {
    
            if (mTargetView == null)
                return false;
    
            int scrollX = mTargetView.getScrollX();
            int left = mTargetView.getWidth() - scrollX;
            int top = mTargetView.getTop();
            int right = left + getHorizontalRange();
            int bottom = mTargetView.getBottom();
            Rect rect = new Rect(left, top, right, bottom);
            return rect.contains(x, y);
        }
    
    
        @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent e) {
            if (mExpandAndCollapseAnim != null && mExpandAndCollapseAnim.isRunning() || mTargetView == null)
                return;
    
            //如果要响应fling事件设置将mIsDragging设为false
            if (mGestureDetector.onTouchEvent(e)) {
                mIsDragging = false;
                return;
            }
    
            int x = (int) e.getX();
            int y = (int) e.getY();
            int action = MotionEventCompat.getActionMasked(e);
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    //RecyclerView 不会转发这个Down事件
    
                    break;
                case MotionEvent.ACTION_MOVE:
                    int deltaX = (int) (mLastX - e.getX());
                    if (mIsDragging) {
                        horizontalDrag(deltaX);
                    }
                    mLastX = x;
                    break;
    
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
    
                    if (mIsDragging) {
                        if (!smoothHorizontalExpandOrCollapse(0) && isCollapsed())
                            mTargetView = null;
    
                        mIsDragging = false;
                    }
    
                    break;
            }
        }
    
        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    
        }
    
        /**
         * 根据touch事件来滚动View的scrollX
         *
         * @param delta
         */
        private void horizontalDrag(int delta) {
            int scrollX = mTargetView.getScrollX();
            int scrollY = mTargetView.getScrollY();
            if ((scrollX + delta) <= 0) {
                mTargetView.scrollTo(0, scrollY);
                return;
            }
    
            int horRange = getHorizontalRange();
            scrollX += delta;
            if (Math.abs(scrollX) < horRange) {
                mTargetView.scrollTo(scrollX, scrollY);
            } else {
                mTargetView.scrollTo(horRange, scrollY);
            }
    
        }
    
        /**
         * 根据当前scrollX的位置判断是展开还是折叠
         *
         * @param velocityX 如果不等于0那么这是一次fling事件,否则是一次ACTION_UP或者ACTION_CANCEL
         */
        private boolean smoothHorizontalExpandOrCollapse(float velocityX) {
    
            int scrollX = mTargetView.getScrollX();
            int scrollRange = getHorizontalRange();
    
            if (mExpandAndCollapseAnim != null)
                return false;
    
            int to = 0;
            int duration = DEFAULT_DURATION;
    
            if (velocityX == 0) {
                //如果已经展一半,平滑展开
                if (scrollX > scrollRange / 2) {
                    to = scrollRange;
                }
            } else {
    
                if (velocityX > 0)
                    to = 0;
                else
                    to = scrollRange;
    
                duration = (int) ((1.f - Math.abs(velocityX) / mMaxVelocity) * DEFAULT_DURATION);
            }
    
            if (to == scrollX)
                return false;
    
            mExpandAndCollapseAnim = ObjectAnimator.ofInt(mTargetView, "scrollX", to);
            mExpandAndCollapseAnim.setDuration(duration);
            mExpandAndCollapseAnim.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
    
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    mExpandAndCollapseAnim = null;
                    if (isCollapsed())
                        mTargetView = null;
    
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
                    //onAnimationEnd(animation);
                    mExpandAndCollapseAnim = null;
    
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
    
                }
            });
            mExpandAndCollapseAnim.start();
    
            return true;
        }
    
        public int getHorizontalRange() {
            RecyclerView.ViewHolder viewHolder = mCallback.getChildViewHolder(mTargetView);
            return mCallback.getHorizontalRange(viewHolder);
        }
    
        @Override
        public boolean onDown(MotionEvent e) {
            return false;
        }
    
        @Override
        public void onShowPress(MotionEvent e) {
    
        }
    
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return false;
        }
    
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            return false;
        }
    
        @Override
        public void onLongPress(MotionEvent e) {
    
        }
    
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    
            if (Math.abs(velocityX) > mMinVelocity && Math.abs(velocityX) < mMaxVelocity) {
                if (!smoothHorizontalExpandOrCollapse(velocityX)) {
                    if (isCollapsed())
                        mTargetView = null;
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 左滑菜单Callback
         */
        public interface Callback {
    
            int getHorizontalRange(RecyclerView.ViewHolder holder);
    
            RecyclerView.ViewHolder getChildViewHolder(View childView);
    
            View findTargetView(float x, float y);
    
        }
    
    }
    
    

    5.最后来Activity,布局文件中添加:

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

    MsgRemindActivity:

    /**
     * 消息主界面
     */
    public class MsgRemindActivity extends Activity {
        @InjectView(R.id.rv_msg_remind)
        RecyclerView rvMsgRemind;
    
        private MsgRemindAdapter msgRemindAdapter;
        private List<MsgVo> mDatas;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            setContentView(R.layout.activity_msg_remind);
            ButterKnife.inject(this);
            initData();
            //设置布局类型为listview的LinearLayoutManager
            rvMsgRemind.setLayoutManager(new LinearLayoutManager(this));
            rvMsgRemind.addItemDecoration(new RecyclerViewListDecoration(this,
                    RecyclerViewListDecoration.VERTICAL_LIST));
            //固定recyclerview大小
            rvMsgRemind.setHasFixedSize(true);
            rvMsgRemind.setAdapter(msgRemindAdapter);
        }
    
        private void initData() {
            mDatas = new ArrayList<MsgVo>();
            for (int i = 'A'; i < 'G'; i++) {
                MsgVo vo = new MsgVo();
                if (i % 2 == 1) {
                    vo.setChecked(true);
                    vo.setTitle("原始消息,已读状态" + (char) i);
                    vo.setContent("乌啦啦啦啦啦啦啦啦啦");
                } else {
                    vo.setChecked(false);
                    vo.setTitle("原始消息,未读状态");
                    vo.setContent("唔噜噜噜噜噜噜噜噜噜噜");
                }
                mDatas.add(vo);
            }
            msgRemindAdapter = new MsgRemindAdapter(this, mDatas);
        }
    }
    
    

    6.以上代码中的

    rvMsgRemind.addItemDecoration(new RecyclerViewListDecoration(this,
                    RecyclerViewListDecoration.VERTICAL_LIST));
    

    是设置recyclerView的分割线,跟ListView有点点不一样哈。

    好了,就这样了吧。

    特别感谢鸿洋老司机提供了学习RecyclerView 的车牌:

    《Android RecyclerView 使用完全解析 体验艺术般的控件》
    http://blog.csdn.net/lmj623565791/article/details/45059587

    特别感谢Android小先森提供的思路:

    http://blog.csdn.net/as_jon/article/details/51830504


    demo地址放出:

    http://download.csdn.net/detail/u013806766/9641654

    相关文章

      网友评论

          本文标题:RecyclerView仿QQ消息列表左滑弹出菜单,标记已读和删

          本文链接:https://www.haomeiwen.com/subject/bqfgxxtx.html