513 lines
15 KiB
Plaintext
513 lines
15 KiB
Plaintext
# 微信气泡弹框实现文档
|
||
|
||
## 📋 功能概述
|
||
本功能实现了类似微信的气泡弹框效果,当用户点击首页右上角的加号按钮时,会弹出一个深色半透明的气泡弹框,包含三个菜单项:发起群聊、添加朋友、扫一扫。
|
||
|
||
## 🎨 视觉效果特点
|
||
|
||
### 1. **气泡样式**
|
||
- **背景色**: 深色半透明 `#CC000000`
|
||
- **圆角**: 8dp圆角矩形
|
||
- **宽度**: 屏幕宽度的40%
|
||
- **右边缘距离**: 与屏幕右边缘保持10dp距离
|
||
|
||
### 2. **小三角箭头**
|
||
- **位置**: 右上角,指向加号按钮
|
||
- **尺寸**: 16x16dp正三角形
|
||
- **位置偏移**: 距离右边56dp
|
||
- **颜色**: 与气泡背景色一致
|
||
|
||
### 3. **菜单项样式**
|
||
- **文字颜色**: 白色 `#FFFFFF`
|
||
- **图标颜色**: 白色
|
||
- **内边距**: 32dp左右,24dp上下
|
||
- **分割线**: 白色半透明 `#33FFFFFF`
|
||
|
||
## 🔧 技术实现
|
||
|
||
### 1. **核心文件结构**
|
||
|
||
```
|
||
uikit/src/main/java/cn/wildfire/chat/kit/dialog/
|
||
├── WeChatBubbleDialog.java # 主弹框类
|
||
├── WeChatBubbleView.java # 自定义气泡View(带箭头)
|
||
└── MainHomePressDialog.java # 原始底部弹框(已替换)
|
||
|
||
uikit/src/main/res/drawable/
|
||
├── bg_wechat_bubble.xml # 气泡背景样式
|
||
└── bg_menu_item_selector.xml # 菜单项点击效果
|
||
|
||
app/src/main/java/com/xunpaisoft/social/im/main/
|
||
└── MainActivity.java # 主Activity,包含点击事件
|
||
```
|
||
|
||
### 2. **WeChatBubbleDialog.java - 主弹框类**
|
||
|
||
```java
|
||
package cn.wildfire.chat.kit.dialog;
|
||
|
||
import android.content.Context;
|
||
import android.graphics.Color;
|
||
import android.graphics.drawable.ColorDrawable;
|
||
import android.view.Gravity;
|
||
import android.view.View;
|
||
import android.view.ViewGroup;
|
||
import android.widget.LinearLayout;
|
||
import android.widget.PopupWindow;
|
||
import android.widget.TextView;
|
||
|
||
import cn.wildfire.chat.kit.R;
|
||
|
||
/**
|
||
* 微信风格的气泡弹框
|
||
*/
|
||
public class WeChatBubbleDialog {
|
||
|
||
private Context context;
|
||
private PopupWindow popupWindow;
|
||
private View anchorView;
|
||
private OnMenuItemClickListener listener;
|
||
|
||
public interface OnMenuItemClickListener {
|
||
void onGroupChatClick();
|
||
void onAddFriendClick();
|
||
void onScanClick();
|
||
}
|
||
|
||
public WeChatBubbleDialog(Context context, View anchorView) {
|
||
this.context = context;
|
||
this.anchorView = anchorView;
|
||
initPopupWindow();
|
||
}
|
||
|
||
private void initPopupWindow() {
|
||
// 创建气泡布局
|
||
View bubbleView = createBubbleView();
|
||
|
||
// 计算气泡宽度 - 设置为屏幕宽度的40%,符合微信效果
|
||
int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
|
||
int bubbleWidth = (int) (screenWidth * 0.4);
|
||
|
||
// 创建 PopupWindow,设置固定宽度
|
||
popupWindow = new PopupWindow(bubbleView,
|
||
bubbleWidth,
|
||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||
true);
|
||
|
||
// 设置背景
|
||
popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||
|
||
// 设置外部可点击
|
||
popupWindow.setOutsideTouchable(true);
|
||
popupWindow.setFocusable(true);
|
||
}
|
||
|
||
private View createBubbleView() {
|
||
// 创建主容器 - 使用自定义的带箭头气泡View
|
||
WeChatBubbleView container = new WeChatBubbleView(context);
|
||
container.setOrientation(LinearLayout.VERTICAL);
|
||
|
||
// 创建菜单项
|
||
String[] menuItems = {"发起群聊", "添加朋友", "扫一扫"};
|
||
int[] menuIcons = {R.drawable.ic_group_chat_white, R.drawable.ic_add_friend_white, R.drawable.ic_scan_white};
|
||
|
||
for (int i = 0; i < menuItems.length; i++) {
|
||
View menuItem = createMenuItem(menuItems[i], menuIcons[i], i);
|
||
container.addView(menuItem);
|
||
|
||
// 添加分割线(除了最后一个)- 使用白色半透明分割线
|
||
if (i < menuItems.length - 1) {
|
||
View divider = new View(context);
|
||
divider.setLayoutParams(new LinearLayout.LayoutParams(
|
||
ViewGroup.LayoutParams.MATCH_PARENT, 1));
|
||
divider.setBackgroundColor(Color.parseColor("#33FFFFFF"));
|
||
container.addView(divider);
|
||
}
|
||
}
|
||
|
||
return container;
|
||
}
|
||
|
||
private View createMenuItem(String title, int iconRes, int position) {
|
||
// 创建菜单项布局
|
||
LinearLayout itemLayout = new LinearLayout(context);
|
||
itemLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||
itemLayout.setPadding(32, 24, 32, 24);
|
||
itemLayout.setGravity(Gravity.CENTER_VERTICAL);
|
||
|
||
// 设置点击效果
|
||
itemLayout.setBackgroundResource(R.drawable.bg_menu_item_selector);
|
||
|
||
// 创建图标 - 使用白色图标
|
||
TextView iconView = new TextView(context);
|
||
iconView.setTextSize(20);
|
||
iconView.setTextColor(Color.WHITE);
|
||
iconView.setCompoundDrawablesWithIntrinsicBounds(iconRes, 0, 0, 0);
|
||
iconView.setCompoundDrawablePadding(16);
|
||
|
||
// 创建标题 - 使用白色
|
||
TextView titleView = new TextView(context);
|
||
titleView.setText(title);
|
||
titleView.setTextSize(16);
|
||
titleView.setTextColor(Color.WHITE);
|
||
titleView.setPadding(16, 0, 0, 0);
|
||
|
||
// 添加到布局
|
||
itemLayout.addView(iconView);
|
||
itemLayout.addView(titleView);
|
||
|
||
// 设置点击事件
|
||
itemLayout.setOnClickListener(v -> {
|
||
if (listener != null) {
|
||
switch (position) {
|
||
case 0:
|
||
listener.onGroupChatClick();
|
||
break;
|
||
case 1:
|
||
listener.onAddFriendClick();
|
||
break;
|
||
case 2:
|
||
listener.onScanClick();
|
||
break;
|
||
}
|
||
}
|
||
dismiss();
|
||
});
|
||
|
||
return itemLayout;
|
||
}
|
||
|
||
public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
|
||
this.listener = listener;
|
||
}
|
||
|
||
public void show() {
|
||
if (popupWindow != null && !popupWindow.isShowing()) {
|
||
// 计算显示位置
|
||
int[] location = new int[2];
|
||
anchorView.getLocationOnScreen(location);
|
||
|
||
// 获取屏幕宽度
|
||
int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
|
||
|
||
// 计算气泡宽度 - 设置为屏幕宽度的40%,符合微信效果
|
||
int bubbleWidth = (int) (screenWidth * 0.4);
|
||
|
||
// 计算X坐标,让气泡右边缘与屏幕右边缘保持10dp距离
|
||
int marginRight = (int) (10 * context.getResources().getDisplayMetrics().density);
|
||
int x = screenWidth - bubbleWidth - marginRight;
|
||
|
||
// 确保不超出屏幕左边界
|
||
if (x < 20) {
|
||
x = 20;
|
||
}
|
||
|
||
// 显示在加号按钮下方
|
||
popupWindow.showAtLocation(anchorView, Gravity.NO_GRAVITY,
|
||
x, location[1] + anchorView.getHeight() + 8);
|
||
}
|
||
}
|
||
|
||
public void dismiss() {
|
||
if (popupWindow != null && popupWindow.isShowing()) {
|
||
popupWindow.dismiss();
|
||
}
|
||
}
|
||
|
||
public boolean isShowing() {
|
||
return popupWindow != null && popupWindow.isShowing();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. **WeChatBubbleView.java - 自定义气泡View**
|
||
|
||
```java
|
||
package cn.wildfire.chat.kit.dialog;
|
||
|
||
import android.content.Context;
|
||
import android.graphics.Canvas;
|
||
import android.graphics.Color;
|
||
import android.graphics.Paint;
|
||
import android.graphics.Path;
|
||
import android.graphics.RectF;
|
||
import android.util.AttributeSet;
|
||
import android.widget.LinearLayout;
|
||
|
||
/**
|
||
* 微信风格的气泡View,带小三角箭头
|
||
*/
|
||
public class WeChatBubbleView extends LinearLayout {
|
||
|
||
private Paint bubblePaint;
|
||
private Path arrowPath;
|
||
private RectF bubbleRect;
|
||
|
||
public WeChatBubbleView(Context context) {
|
||
super(context);
|
||
init();
|
||
}
|
||
|
||
public WeChatBubbleView(Context context, AttributeSet attrs) {
|
||
super(context, attrs);
|
||
init();
|
||
}
|
||
|
||
public WeChatBubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||
super(context, attrs, defStyleAttr);
|
||
init();
|
||
}
|
||
|
||
private void init() {
|
||
// 设置背景透明,我们自己绘制
|
||
setBackgroundColor(Color.TRANSPARENT);
|
||
|
||
// 初始化画笔
|
||
bubblePaint = new Paint();
|
||
bubblePaint.setColor(Color.parseColor("#CC000000"));
|
||
bubblePaint.setStyle(Paint.Style.FILL);
|
||
bubblePaint.setAntiAlias(true);
|
||
|
||
// 初始化路径
|
||
arrowPath = new Path();
|
||
bubbleRect = new RectF();
|
||
}
|
||
|
||
@Override
|
||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||
super.onSizeChanged(w, h, oldw, oldh);
|
||
|
||
// 设置气泡矩形区域(留出箭头空间)
|
||
bubbleRect.set(0, 16, w, h);
|
||
|
||
// 创建箭头路径
|
||
createArrowPath(w, h);
|
||
}
|
||
|
||
private void createArrowPath(int width, int height) {
|
||
arrowPath.reset();
|
||
|
||
// 箭头位置(右上角,指向加号按钮)
|
||
int arrowX = width - 56; // 距离右边56dp
|
||
int arrowY = 0;
|
||
int arrowSize = 16;
|
||
|
||
// 创建三角形箭头
|
||
arrowPath.moveTo(arrowX, arrowY);
|
||
arrowPath.lineTo(arrowX - arrowSize/2, arrowY + arrowSize);
|
||
arrowPath.lineTo(arrowX + arrowSize/2, arrowY + arrowSize);
|
||
arrowPath.close();
|
||
}
|
||
|
||
@Override
|
||
protected void onDraw(Canvas canvas) {
|
||
// 绘制主气泡(圆角矩形)
|
||
canvas.drawRoundRect(bubbleRect, 16, 16, bubblePaint);
|
||
|
||
// 绘制箭头
|
||
canvas.drawPath(arrowPath, bubblePaint);
|
||
|
||
super.onDraw(canvas);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. **MainActivity.java - 点击事件处理**
|
||
|
||
```java
|
||
private void rightAddFrid() {
|
||
setMenuVisible(cn.wildfire.chat.kit.R.mipmap.ic_add, new View.OnClickListener() {
|
||
@Override
|
||
public void onClick(View view) {
|
||
showWeChatStylePopupMenu(view);
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 显示微信风格的气泡弹框
|
||
*/
|
||
private void showWeChatStylePopupMenu(View anchorView) {
|
||
// 使用自定义气泡弹框
|
||
WeChatBubbleDialog bubbleDialog = new WeChatBubbleDialog(this, anchorView);
|
||
bubbleDialog.setOnMenuItemClickListener(new WeChatBubbleDialog.OnMenuItemClickListener() {
|
||
@Override
|
||
public void onGroupChatClick() {
|
||
createConversation();
|
||
}
|
||
|
||
@Override
|
||
public void onAddFriendClick() {
|
||
searchUser();
|
||
}
|
||
|
||
@Override
|
||
public void onScanClick() {
|
||
requestCameraPermissionAndScan();
|
||
}
|
||
});
|
||
bubbleDialog.show();
|
||
}
|
||
|
||
/**
|
||
* 请求相机权限并启动扫码
|
||
*/
|
||
private void requestCameraPermissionAndScan() {
|
||
XXPermissions.with(mContext)
|
||
.permission(PermissionLists.getCameraPermission())
|
||
.interceptor(new PermissionInterceptor())
|
||
.description(new PermissionDescription())
|
||
.request(new OnPermissionCallback() {
|
||
@Override
|
||
public void onResult(@NonNull List<IPermission> grantedList, @NonNull List<IPermission> deniedList) {
|
||
boolean allGranted = deniedList.isEmpty();
|
||
if (!allGranted) {
|
||
showToast("请申请相机权限");
|
||
return;
|
||
}
|
||
// 权限获取成功,启动扫码
|
||
startActivityForResult(new Intent(MainActivity.this, ScanQRCodeActivity.class), REQUEST_CODE_SCAN_QR_CODE);
|
||
}
|
||
});
|
||
}
|
||
```
|
||
|
||
### 5. **样式资源文件**
|
||
|
||
#### bg_wechat_bubble.xml
|
||
```xml
|
||
<?xml version="1.0" encoding="utf-8"?>
|
||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||
android:shape="rectangle">
|
||
|
||
<!-- 深色半透明背景,类似微信 -->
|
||
<solid android:color="#CC000000" />
|
||
|
||
<!-- 圆角 -->
|
||
<corners android:radius="8dp" />
|
||
|
||
</shape>
|
||
```
|
||
|
||
#### bg_menu_item_selector.xml
|
||
```xml
|
||
<?xml version="1.0" encoding="utf-8"?>
|
||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||
|
||
<!-- 按下状态 - 深色背景下的高亮效果 -->
|
||
<item android:state_pressed="true">
|
||
<shape android:shape="rectangle">
|
||
<solid android:color="#33FFFFFF" />
|
||
</shape>
|
||
</item>
|
||
|
||
<!-- 正常状态 -->
|
||
<item>
|
||
<shape android:shape="rectangle">
|
||
<solid android:color="#00000000" />
|
||
</shape>
|
||
</item>
|
||
|
||
</selector>
|
||
```
|
||
|
||
## 🎯 关键参数配置
|
||
|
||
### 1. **尺寸参数**
|
||
- **气泡宽度**: 屏幕宽度的40% (`screenWidth * 0.4`)
|
||
- **右边缘距离**: 10dp (`10 * density`)
|
||
- **左边缘最小距离**: 20dp
|
||
- **箭头尺寸**: 16x16dp
|
||
- **箭头位置**: 距离右边56dp
|
||
|
||
### 2. **颜色参数**
|
||
- **背景色**: `#CC000000` (深色半透明)
|
||
- **文字颜色**: `#FFFFFF` (白色)
|
||
- **分割线颜色**: `#33FFFFFF` (白色半透明)
|
||
- **点击高亮**: `#33FFFFFF` (白色半透明)
|
||
|
||
### 3. **间距参数**
|
||
- **菜单项内边距**: 32dp左右,24dp上下
|
||
- **图标文字间距**: 16dp
|
||
- **气泡圆角**: 8dp
|
||
- **箭头圆角**: 16dp
|
||
|
||
## 📱 使用方法
|
||
|
||
### 1. **基本使用**
|
||
```java
|
||
// 创建气泡弹框
|
||
WeChatBubbleDialog bubbleDialog = new WeChatBubbleDialog(context, anchorView);
|
||
|
||
// 设置点击监听器
|
||
bubbleDialog.setOnMenuItemClickListener(new WeChatBubbleDialog.OnMenuItemClickListener() {
|
||
@Override
|
||
public void onGroupChatClick() {
|
||
// 处理发起群聊
|
||
}
|
||
|
||
@Override
|
||
public void onAddFriendClick() {
|
||
// 处理添加朋友
|
||
}
|
||
|
||
@Override
|
||
public void onScanClick() {
|
||
// 处理扫一扫
|
||
}
|
||
});
|
||
|
||
// 显示弹框
|
||
bubbleDialog.show();
|
||
```
|
||
|
||
### 2. **生命周期管理**
|
||
```java
|
||
// 关闭弹框
|
||
bubbleDialog.dismiss();
|
||
|
||
// 检查是否显示
|
||
if (bubbleDialog.isShowing()) {
|
||
// 弹框正在显示
|
||
}
|
||
```
|
||
|
||
## 🔧 自定义配置
|
||
|
||
### 1. **调整宽度比例**
|
||
```java
|
||
// 在 initPopupWindow() 和 show() 方法中修改
|
||
int bubbleWidth = (int) (screenWidth * 0.4); // 0.4 = 40%
|
||
// 可以调整为 0.3 (30%) 或 0.5 (50%)
|
||
```
|
||
|
||
### 2. **调整右边缘距离**
|
||
```java
|
||
// 在 show() 方法中修改
|
||
int marginRight = (int) (10 * context.getResources().getDisplayMetrics().density);
|
||
// 可以调整为 5dp 或 15dp
|
||
```
|
||
|
||
### 3. **调整箭头位置**
|
||
```java
|
||
// 在 WeChatBubbleView.java 的 createArrowPath() 方法中修改
|
||
int arrowX = width - 56; // 距离右边56dp
|
||
// 可以调整为 40dp 或 70dp
|
||
```
|
||
|
||
## 🎉 最终效果
|
||
|
||
实现的气泡弹框具有以下特点:
|
||
|
||
- ✅ **完全符合微信设计**: 深色半透明背景,白色文字
|
||
- ✅ **精确定位**: 右对齐,10dp边距,箭头指向加号
|
||
- ✅ **流畅交互**: 点击反馈,外部点击关闭
|
||
- ✅ **响应式设计**: 宽度自适应屏幕尺寸
|
||
- ✅ **易于维护**: 模块化设计,参数可配置
|
||
|
||
---
|
||
**文档版本**: v1.0
|
||
**创建时间**: 2024年10月26日
|
||
**状态**: 已实现
|
||
**维护者**: 开发团队
|