RecyclerView仿QQ消息列表左滑彈出菜單,標(biāo)記已讀和刪除

仿QQ消息列表左滑彈出菜單,標(biāo)記已讀和刪除

話不多說,看到這樣的效果心動了么?

這里寫圖片描述

1.先上build .gradle,dependencies里面要這樣寫,其實(shí)就是導(dǎo)入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="標(biāo)記已讀" />
    </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) {
        /**
         * 消息狀態(tài)
         */
        if (mDatas.get(position).isChecked()) {
            holder.msgRemindPoint.setBackgroundResource(R.drawable.shape_remind_point_gray);
        } else {
            holder.msgRemindPoint.setBackgroundResource(R.drawable.shape_remind_point_theme);
        }
        //消息標(biāo)題
        holder.tvRemindTitle.setText(mDatas.get(position).getTitle());
        //消息內(nèi)容
        holder.tvRemindContent.setText(mDatas.get(position).getContent());

-->特別注意,敲黑板了啊?。?!在執(zhí)行notify的時(shí)候,取position要取holder.getAdapterPosition(),消息被刪除之后,他原來的position是final的,所以取到的值不準(zhǔn)確,會報(bào)數(shù)組越界。

消息主體監(jiān)聽,這里我是讓他添加一條數(shù)據(jù),替換成你需要的操作即可

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

標(biāo)記已讀監(jiān)聽

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

刪除消息監(jiān)聽

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

此方法用來計(jì)算水平方向移動的距離

    @Override
    public int getHorizontalRange(RecyclerView.ViewHolder holder) {
        if (holder.itemView instanceof LinearLayout) {
            ViewGroup viewGroup = (ViewGroup) holder.itemView;
            //viewGroup包含3個控件,即消息主item、標(biāo)記已讀、刪除,返回為標(biāo)記已讀寬度+刪除寬度
            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);
        }
    }

添加單條數(shù)據(jù)

    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);
    }

刪除單條數(shù)據(jù)

    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滾動狀態(tài)不是空閑targetView不是空
        if (rv.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
            if (mTargetView != null) {
                //隱藏已經(jīng)打開
                smoothHorizontalExpandOrCollapse(DEFAULT_DURATION / 2);
                mTargetView = null;
            }

            return false;
        }

        //如果正在運(yùn)行動畫 ,直接攔截什么都不做
        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();

                /*
                * 如果之前有一個已經(jīng)打開的項(xiàng)目,當(dāng)此次點(diǎn)擊事件沒有發(fā)生在右側(cè)的菜單中則返回TRUE,
                * 如果點(diǎn)擊的是右側(cè)菜單那么返回FALSE這樣做的原因是因?yàn)椴藛涡枰憫?yīng)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;

                //如果移動距離達(dá)到要求,則攔截
                needIntercept = mIsDragging = mTargetView != null && Math.abs(deltaX) >= mTouchSlop;
                break;

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                /*
                * 走這是因?yàn)闆]有發(fā)生過攔截事件
                * */
                if (isExpanded()) {

                    if (inView(x, y)) {
                        // 如果走這那行這個ACTION_UP的事件會發(fā)生在右側(cè)的菜單中
                    } else {
                        //攔截事件,防止targetView執(zhí)行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;
    }

    /*
    * 根據(jù)targetView的scrollX計(jì)算出targetView的偏移,這樣能夠知道這個point
    * 是在右側(cè)的菜單中
    * */
    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;

        //如果要響應(yīng)fling事件設(shè)置將mIsDragging設(shè)為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 不會轉(zhuǎn)發(fā)這個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) {

    }

    /**
     * 根據(jù)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);
        }

    }

    /**
     * 根據(jù)當(dāng)前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) {
            //如果已經(jīng)展一半,平滑展開
            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();
        //設(shè)置布局類型為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("原始消息,已讀狀態(tài)" + (char) i);
                vo.setContent("烏啦啦啦啦啦啦啦啦啦");
            } else {
                vo.setChecked(false);
                vo.setTitle("原始消息,未讀狀態(tài)");
                vo.setContent("唔嚕嚕嚕嚕嚕嚕嚕嚕嚕嚕");
            }
            mDatas.add(vo);
        }
        msgRemindAdapter = new MsgRemindAdapter(this, mDatas);
    }
}

6.以上代碼中的

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

是設(shè)置recyclerView的分割線,跟ListView有點(diǎn)點(diǎn)不一樣哈。

好了,就這樣了吧。

特別感謝鴻洋老司機(jī)提供了學(xué)習(xí)RecyclerView 的車牌:

《Android RecyclerView 使用完全解析 體驗(yàn)藝術(shù)般的控件》
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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容