如何在Flutter中实现安卓WhatsApp风格的聊天气泡尾巴?
安卓实现WhatsApp风格聊天气泡尾巴方案
以下是两种手动创建聊天气泡尾巴的可行方案,无需依赖第三方库:
方法一:使用ShapeDrawable组合实现(简单快捷)
通过layer-list将矩形气泡和旋转的正方形组合成带尾巴的气泡,适合消息高度变化不大的场景。
发送方气泡(带右侧尾巴)
创建drawable/chat_bubble_sent.xml:
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 主气泡背景 --> <item> <shape android:shape="rectangle"> <solid android:color="#DCF8C6" /> <!-- WhatsApp发送方绿色 --> <corners android:radius="16dp" /> <padding android:top="8dp" android:bottom="8dp" android:left="12dp" android:right="12dp" /> </shape> </item> <!-- 气泡尾巴:旋转45度的正方形 --> <item android:right="-10dp" android:top="20dp"> <rotate android:fromDegrees="45"> <shape android:shape="rectangle"> <solid android:color="#DCF8C6" /> <size android:width="10dp" android:height="10dp" /> </shape> </rotate> </item> </layer-list>
接收方气泡(带左侧尾巴)
创建drawable/chat_bubble_received.xml:
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 主气泡背景 --> <item> <shape android:shape="rectangle"> <solid android:color="#FFFFFF" /> <!-- 接收方白色 --> <corners android:radius="16dp" /> <padding android:top="8dp" android:bottom="8dp" android:left="12dp" android:right="12dp" /> </shape> </item> <!-- 气泡尾巴:旋转45度的正方形 --> <item android:left="-10dp" android:top="20dp"> <rotate android:fromDegrees="45"> <shape android:shape="rectangle"> <solid android:color="#FFFFFF" /> <size android:width="10dp" android:height="10dp" /> </shape> </rotate> </item> </layer-list>
布局中使用
直接给TextView设置背景即可:
<!-- 发送方消息 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/chat_bubble_sent" android:text="这是我发送的消息" android:textColor="#000000" /> <!-- 接收方消息 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/chat_bubble_received" android:text="这是对方发送的消息" android:textColor="#000000" />
方法二:自定义View绘制(灵活适配)
通过Canvas和Path手动绘制气泡和尾巴,能根据消息高度动态调整尾巴位置,适配复杂场景。
创建自定义ChatBubbleView
import android.content.Context; import android.graphics.Canvas; import android.graphics.Path; import android.graphics.RectF; import android.util.AttributeSet; import androidx.appcompat.widget.AppCompatTextView; public class ChatBubbleView extends AppCompatTextView { private Path bubblePath; private RectF bubbleRect; private boolean isSentMessage; // 是否为发送方消息 private final float tailSize = 10f; // 尾巴大小 private final float cornerRadius = 16f; // 气泡圆角 public ChatBubbleView(Context context) { super(context); init(); } public ChatBubbleView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { bubblePath = new Path(); bubbleRect = new RectF(); updatePadding(); } // 设置是否为发送方消息,更新内边距并重绘 public void setIsSentMessage(boolean isSent) { isSentMessage = isSent; updatePadding(); invalidate(); } private void updatePadding() { // 根据消息方向调整内边距,给尾巴留出空间 int leftPad = isSentMessage ? 12 : 22; int rightPad = isSentMessage ? 22 : 12; setPadding(leftPad, 8, rightPad, 8); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); buildBubblePath(); } // 构建气泡的Path路径 private void buildBubblePath() { bubblePath.reset(); float contentWidth = getWidth() - getPaddingLeft() - getPaddingRight(); float contentHeight = getHeight() - getPaddingTop() - getPaddingBottom(); bubbleRect.set(getPaddingLeft(), getPaddingTop(), getPaddingLeft() + contentWidth, getPaddingTop() + contentHeight); if (isSentMessage) { // 绘制发送方气泡:右侧带尾巴 bubblePath.addRoundRect(bubbleRect, cornerRadius, cornerRadius, Path.Direction.CW); float tailX = bubbleRect.right; float tailY = bubbleRect.top + contentHeight / 2; bubblePath.moveTo(tailX, tailY); bubblePath.lineTo(tailX + tailSize, tailY - tailSize); bubblePath.lineTo(tailX + tailSize, tailY + tailSize); bubblePath.close(); } else { // 绘制接收方气泡:左侧带尾巴 bubblePath.addRoundRect(bubbleRect, cornerRadius, cornerRadius, Path.Direction.CW); float tailX = bubbleRect.left; float tailY = bubbleRect.top + contentHeight / 2; bubblePath.moveTo(tailX, tailY); bubblePath.lineTo(tailX - tailSize, tailY - tailSize); bubblePath.lineTo(tailX - tailSize, tailY + tailSize); bubblePath.close(); } } @Override protected void onDraw(Canvas canvas) { // 绘制气泡背景色 int bgColor = isSentMessage ? 0xFFDCF8C6 : 0xFFFFFFFF; canvas.drawColor(bgColor); // 裁剪画布,只保留气泡区域 canvas.clipPath(bubblePath); // 绘制文本内容 super.onDraw(canvas); } }
布局中使用
<!-- 发送方消息 --> <com.yourpackage.ChatBubbleView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="这是一条发送的长消息,尾巴会自动居中显示" android:textColor="#000000" /> <!-- 接收方消息 --> <com.yourpackage.ChatBubbleView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="这是一条接收的消息" android:textColor="#000000" />
使用说明
- 在代码中调用
setIsSentMessage(true/false)切换消息方向 - 可根据需求修改
tailSize、cornerRadius和背景色参数
内容的提问来源于stack exchange,提问作者Kartik
相关产品推荐
相关产品推荐

