基于RecyclerView实现的仿微信聊天界面,item长按根据触摸位置弹出对话框

 

首先看一下实现的效果图:

 

                 

 

在上一篇文章 基于RecyclerView实现的实现多样化的item样式——类似IM即时通讯聊天界面的布局效果

 

 

的基础上实现的仿微信聊天界面,item长按,根据手的触摸位置弹出选项对话框,并完善小动画效果

列表界面的实现样过程可参见上一篇文章,这里先看一下MainActivity类:

主要的实现弹出框原理是在initPopWindow方法里,通过获得触摸点的X、Y的位置来实现跟随触摸位置弹出,

可以对这种列表选项的PopupWindow样式进行封装,使其更加灵活可扩展,这里只是为了暂时实现效果图中的样式而已。

package com.alter.popupwindowmenu.activity;

import android.app.Activity;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.Toast;

import com.alter.popupwindowmenu.Command.C;
import com.alter.popupwindowmenu.R;
import com.alter.popupwindowmenu.Utils.DensityUtil;
import com.alter.popupwindowmenu.adapter.ChatDetailAdapter;
import com.alter.popupwindowmenu.model.ChatMessage;
import com.alter.popupwindowmenu.model.UserInfo;

import java.util.ArrayList;
import java.util.List;


/**
 * @CreateDate: 2018/3/9
 * @Author: lzsheng
 * @Description:
 * @Version:
 */
public class MainActivity extends Activity {

    private final String TAG = MainActivity.class.getSimpleName();
    private RecyclerView mRecyclerView;
    private LinearLayoutManager mLinearLayoutManager;
    private ChatDetailAdapter mChatDetailAdapter;
    private List<ChatMessage> mChatMessages;
    private PopupWindow mPopupWindow;
    private View mPopContentView;
    private int mPressedPos; // 被点击的位置
    private float mRawX;
    private float mRawY;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initVariable();
        initViews();
        initData();
        setViewsData();
    }

    private void initVariable() {
        mChatMessages = new ArrayList<>();
    }

    private void initViews() {
        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview_messages);
        mLinearLayoutManager = new LinearLayoutManager(MainActivity.this);
        mRecyclerView.setLayoutManager(mLinearLayoutManager);
    }

    private void initData() {
        for (int i = 'A'; i <= 'Z'; i++) {
            ChatMessage chatMessage = new ChatMessage();
            chatMessage.setFrom((int) (Math.random() * 2) + 1);
            if (chatMessage.getFrom() == C.TYPE_MSG_RECEIVE) {
                UserInfo userInfo = new UserInfo();
                userInfo.setNickName("Mia");
                chatMessage.setUserInfo(userInfo);
            }
//            int s = (int) (Math.random() * ('Z' - 'A' + 1)) + 'A';
//            int e = (int) (Math.random() * ('Z' - 'A' + 1)) + 'A';
            int s = (int) (Math.random() * 120) + 1;
            int e = (int) (Math.random() * 120) + 1;
            Log.d(TAG, "s = " + s);
            Log.d(TAG, "e = " + e);
            int min = 0;
            int max = 0;
            if (s <= e) {
                min = s;
                max = e;
            } else {
                min = e;
                max = s;
            }
            StringBuffer buffer = new StringBuffer();
            for (int j = min; j <= max; j++) {
                int c = (int) (Math.random() * ('Z' - 'A' + 1)) + 'A';
                Log.d(MainActivity.class.getSimpleName(), "c = " + c);
//                buffer.append(String.valueOf((char) (Math.random() * max) + min));
                buffer.append(String.valueOf((char) c));
            }
            chatMessage.setMsgContent(buffer.toString());
            buffer.setLength(0);
            // 或者:
            // buffer.delete(0, buffer.length());
            mChatMessages.add(chatMessage);
            Log.d(TAG, "i = " + i + "\n===========分割线==============");
        }
    }

    private void setViewsData() {
        mChatDetailAdapter = new ChatDetailAdapter(MainActivity.this);
        mChatDetailAdapter.setChatMessages(mChatMessages);
        mRecyclerView.setAdapter(mChatDetailAdapter);
//        mRecyclerView.smoothScrollToPosition(mChatDetailAdapter.getItemCount());
//        mLinearLayoutManager.scrollToPositionWithOffset(mChatDetailAdapter.getItemCount() + 1, 0);
        mRecyclerView.scrollToPosition(mChatDetailAdapter.getItemCount() - 1);
        mChatDetailAdapter.setOnRecyclerViewItemLongClick(new ChatDetailAdapter.OnRecyclerViewItemLongClick() {
            @Override
            public void onItemLongClick(View childView, MotionEvent event, int position) {
                mRawX = event.getRawX();
                mRawY = event.getRawY();
                mPressedPos = position;
                Log.d(TAG, "e.getRawX()横坐标=" + mRawX + ", e.getRawY()纵坐标=" + mRawY);
                Log.d(TAG, "position=" + position);
                UserInfo userInfo = mChatMessages.get(position).getUserInfo();
                ChatMessage chatMessage = mChatMessages.get(position);
                int typeMsg = chatMessage.getFrom();
                String msgContent = chatMessage.getMsgContent();
                StringBuffer buffer = new StringBuffer();
                if (typeMsg == C.TYPE_MSG_RECEIVE) {
                    buffer.append("收到").append(userInfo.getNickName()).append("发送过来的消息:\n").append(msgContent);
                } else if (typeMsg == C.TYPE_MSG_SEND) {
                    buffer.append("我发出的消息:\n").append(msgContent);
                }
                Toast.makeText(MainActivity.this, buffer.toString(), Toast.LENGTH_SHORT).show();
                initPopWindow(childView, position);
            }
        });
    }

    private void initPopWindow(final View selectedView, final int position) {
        if (mPopContentView == null) {
            mPopContentView = View.inflate(this, R.layout.item_list_option_pop, null);
        }
        LinearLayout layoutDelete = (LinearLayout) mPopContentView.findViewById(R.id.layout_delete);
        // 在popupWindow还没有弹出显示之前就测量获取其宽高(单位是px像素)
        int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        mPopContentView.measure(w, h);
        int viewWidth = mPopContentView.getMeasuredWidth();//获取测量宽度px
        int viewHeight = mPopContentView.getMeasuredHeight();//获取测量高度px
        final int screenWidth = DensityUtil.getScreenWidth(this.getWindow().getDecorView().getContext());
        final int screenHeight = DensityUtil.getScreenHeight(this.getWindow().getDecorView().getContext());
        if (mPopupWindow == null) {
            mPopupWindow = new PopupWindow(mPopContentView, viewWidth, ViewGroup.LayoutParams.WRAP_CONTENT, true);
        }
        mPopupWindow.setOutsideTouchable(true);
//        mPopupWindow.setBackgroundDrawable(drawable);
        mPopupWindow.setBackgroundDrawable(new BitmapDrawable());
        int offX = 20; // 可以自己调整偏移
        int offY = 20; // 可以自己调整偏移
        float rawX = mRawX;
        float rawY = mRawY;
        if (mRawX <= screenWidth / 2) {
            rawX = mRawX + offX;
            if (mRawY < screenHeight / 3) {
                rawY = mRawY;
                mPopupWindow.setAnimationStyle(R.style.pop_anim_left_top); //设置动画
            } else {
                rawY = mRawY - viewHeight - offY;
                mPopupWindow.setAnimationStyle(R.style.pop_anim_left_bottom); //设置动画
            }
        } else {
            rawX = mRawX - viewWidth - offX;
            if (mRawY < screenHeight / 3) {
                rawY = mRawY;
                mPopupWindow.setAnimationStyle(R.style.pop_anim_right_top); //设置动画
            } else {
                rawY = mRawY - viewHeight;
                mPopupWindow.setAnimationStyle(R.style.pop_anim_right_bottom); //设置动画
            }
        }
        mPopupWindow.showAtLocation(this.getWindow().getDecorView(), Gravity.NO_GRAVITY, (int) rawX, (int) rawY);
        layoutDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mPopupWindow.dismiss();
                if (mChatMessages.size() <= 0) {
                    return;
                } else {
                    mChatMessages.remove(position);
                    mChatDetailAdapter.notifyDataSetChanged();
                    Toast.makeText(MainActivity.this, "已删除此条聊天内容", Toast.LENGTH_SHORT).show();
                }
            }
        });

        mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
            @Override
            public void onDismiss() {
                selectedView.setSelected(false);
            }
        });
    }
}

还要注意适配器里对长按事件的处理:

我给布局设置了触摸事件setOnTouchListener,并且能过长按事件将MotionEvent传递出来,这样在MainActivity中RecycleVeiw注册长按事件时,可以通过传递出来的这个MotionEvent获得触摸的位置

package com.alter.popupwindowmenu.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.LinearLayout;
import android.widget.TextView;

import com.alter.popupwindowmenu.Command.C;
import com.alter.popupwindowmenu.R;
import com.alter.popupwindowmenu.model.ChatMessage;
import com.alter.popupwindowmenu.model.UserInfo;

import java.util.List;

/**
 * @CreateDate: 2018/1/26
 * @Author: lzsheng
 * @Description: 适配器,根据不同的数据类型,展示不同的UI效果
 * @Version:
 */
public class ChatDetailAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private Context mContext;
    private List<ChatMessage> mChatMessages;
    private OnRecyclerViewItemLongClick mOnRecyclerViewItemLongClick;

    private final int TYPE_MSG_SEND = C.TYPE_MSG_SEND;              //
    private final int TYPE_MSG_RECEIVE = C.TYPE_MSG_RECEIVE;          //

    public ChatDetailAdapter(Context context) {
        this.mContext = context;
    }

    public void setChatMessages(List<ChatMessage> chatMessages) {
        mChatMessages = chatMessages;
    }

    public void setOnRecyclerViewItemLongClick(OnRecyclerViewItemLongClick onRecyclerViewItemLongClick) {
        mOnRecyclerViewItemLongClick = onRecyclerViewItemLongClick;
    }

    /**
     * @CreateDate: 2018/2/3
     * @Author: lzsheng
     * @Description: 根据数据的类型, 返回不同的ItemViewType
     * @Params: [position]
     * @Return: int
     */
    @Override
    public int getItemViewType(int position) {
        if (mChatMessages != null && mChatMessages.size() > 0) {
            int from = mChatMessages.get(position).getFrom();
            if (from == C.TYPE_MSG_SEND) {
                return TYPE_MSG_SEND;
            } else if (from == C.TYPE_MSG_RECEIVE) {
                return TYPE_MSG_RECEIVE;
            }
        }
        return 0;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType) {
            case TYPE_MSG_SEND:
                HolderChatSend holderChatSend = new HolderChatSend(
                        LayoutInflater.from(mContext).inflate(R.layout.rv_item_chat_msg_send, parent, false));
                return holderChatSend;
            case TYPE_MSG_RECEIVE:
                HolderChatReceive holderChatReceive = new HolderChatReceive(
                        LayoutInflater.from(mContext).inflate(R.layout.rv_item_chat_msg_receive, parent, false));
                return holderChatReceive;
            default:
                return null;
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (mChatMessages != null) {
            ChatMessage chatMessage = mChatMessages.get(position);
            int from = chatMessage.getFrom();
            switch (from) {
                case TYPE_MSG_SEND:
                    String msgContentSend = chatMessage.getMsgContent();
                    ((HolderChatSend) holder).tvMsgContent.setText(msgContentSend);
                    break;
                case TYPE_MSG_RECEIVE:
                    UserInfo userInfo = chatMessage.getUserInfo();
                    ((HolderChatReceive) holder).tvNickName.setText(userInfo.getNickName());
                    String msgContentReceive = chatMessage.getMsgContent();
                    ((HolderChatReceive) holder).tvMsgContent.setText(msgContentReceive);
                    break;
                default:
                    break;
            }
        }
    }

    @Override
    public int getItemCount() {
        if (mChatMessages != null && mChatMessages.size() > 0) {
            return mChatMessages.size();
        }
        return 0;
    }

    class HolderChatSend extends RecyclerView.ViewHolder implements View.OnLongClickListener {

        TextView tvNickName;
        TextView tvMsgContent;
        LinearLayout layoutChat;
        MotionEvent event;

        public HolderChatSend(View itemView) {
            super(itemView);
            tvNickName = (TextView) itemView.findViewById(R.id.textview_nick_name);
            tvMsgContent = (TextView) itemView.findViewById(R.id.textview_message);
            layoutChat = (LinearLayout) itemView.findViewById(R.id.layout_message);
            layoutChat.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View view, MotionEvent e) {
                    switch (e.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            event = e;
                            break;
                        default:
                            break;
                    }
                    // 如果onTouch返回false,首先是onTouch事件的down事件发生,此时,如果长按,触发onLongClick事件;
                    // 然后是onTouch事件的up事件发生,up完毕,最后触发onClick事件。
                    return false;
                }
            });
            layoutChat.setOnLongClickListener(this);
        }

        @Override
        public boolean onLongClick(View v) {
            if (mOnRecyclerViewItemLongClick != null) {
                mOnRecyclerViewItemLongClick.onItemLongClick(v, event, getAdapterPosition());
            }
            return false;
        }
    }

    class HolderChatReceive extends RecyclerView.ViewHolder implements View.OnLongClickListener {

        TextView tvNickName;
        TextView tvMsgContent;
        LinearLayout layoutChat;
        MotionEvent event;

        public HolderChatReceive(View itemView) {
            super(itemView);
            tvNickName = (TextView) itemView.findViewById(R.id.textview_nick_name);
            tvMsgContent = (TextView) itemView.findViewById(R.id.textview_message);
            layoutChat = (LinearLayout) itemView.findViewById(R.id.layout_message);
            layoutChat.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View view, MotionEvent e) {
                    switch (e.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            event = e;
                            break;
                        default:
                            break;
                    }
                    // 如果onTouch返回false,首先是onTouch事件的down事件发生,此时,如果长按,触发onLongClick事件;
                    // 然后是onTouch事件的up事件发生,up完毕,最后触发onClick事件。
                    return false;
                }
            });
            layoutChat.setOnLongClickListener(this);
        }

        @Override
        public boolean onLongClick(View v) {
            if (mOnRecyclerViewItemLongClick != null) {
                mOnRecyclerViewItemLongClick.onItemLongClick(v, event, getAdapterPosition());
            }
            return false;
        }
    }

    /**
     * item点击接口
     */
    public interface OnRecyclerViewItemLongClick {
        void onItemLongClick(View childView, MotionEvent event, int position);
    }
}

 

源码下载

GitHub地址

 

由于作者水平有限,语言描述及代码实现中难免有纰漏,望各位看官多提宝贵意见!

Hello , World !

感谢所有!

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

windfallsheng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值