flutter图片聊天泡泡_Flutter 非常丰富的消息气泡效果合集

本文介绍如何在 Flutter 中创建丰富多样的消息气泡效果,适用于社交应用的聊天气泡或其他长按弹出场景。文章通过示例代码详细展示了 BubbleWidget 的封装,包括气泡的形状、尖角位置、大小、颜色等属性的定制,并提供了多种使用示例。
摘要由CSDN通过智能技术生成

这个消息气泡可以用于社交的聊天气泡,或者其他长按弹出的效果,

应用场景挺多,主要是用于学习。

先上效果图

1.BubbleWidget封装

通过系统的Canvas绘制

/// 气泡组件封装

///

/// created by hujintao

/// created at 2019-10-21

//

import 'dart:math';

import 'package:flutter/material.dart';

enum BubbleArrowDirection { top, bottom, right, left, topLeft }

class BubbleWidget extends StatelessWidget {

// 尖角位置

final position;

// 尖角高度

var arrHeight;

// 尖角角度

var arrAngle;

// 圆角半径

var radius;

// 宽度

final width;

// 高度

final height;

// 边距

double length;

// 颜色

Color color;

// 边框颜色

Color borderColor;

// 边框宽度

final strokeWidth;

// 填充样式

final style;

// 子 Widget

final child;

// 子 Widget 与起泡间距

var innerPadding;

BubbleWidget(

this.width,

this.height,

this.color,

this.position, {

Key key,

this.length = 1,

this.arrHeight = 12.0,

this.arrAngle = 60.0,

this.radius = 10.0,

this.strokeWidth = 4.0,

this.style = PaintingStyle.fill,

this.borderColor,

this.child,

this.innerPadding = 6.0,

}) : super(key: key);

@override

Widget build(BuildContext context) {

if (style == PaintingStyle.stroke && borderColor == null) {

borderColor = color;

}

if (arrAngle < 0.0 || arrAngle >= 180.0) {

arrAngle = 60.0;

}

if (arrHeight < 0.0) {

arrHeight = 0.0;

}

if (radius < 0.0 || radius > width * 0.5 || radius > height * 0.5) {

radius = 0.0;

}

if (position == BubbleArrowDirection.top ||

position == BubbleArrowDirection.bottom) {

if (length < 0.0 || length >= width - 2 * radius) {

length = width * 0.5 - arrHeight * tan(_angle(arrAngle * 0.5)) - radius;

}

} else {

if (length < 0.0 || length >= height - 2 * radius) {

length =

height * 0.5 - arrHeight * tan(_angle(arrAngle * 0.5)) - radius;

}

}

if (innerPadding < 0.0 ||

innerPadding >= width * 0.5 ||

innerPadding >= height * 0.5) {

innerPadding = 2.0;

}

Widget bubbleWidget;

if (style == PaintingStyle.fill) {

bubbleWidget = Container(

width: width,

height: height,

child: Stack(children: [

CustomPaint(

painter: BubbleCanvas(context, width, height, color, position,

arrHeight, arrAngle, radius, strokeWidth, style, length)),

_paddingWidget()

]));

} else {

bubbleWidget = Container(

width: width,

height: height,

child: Stack(children: [

CustomPaint(

painter: BubbleCanvas(

context,

width,

height,

color,

position,

arrHeight,

arrAngle,

radius,

strokeWidth,

PaintingStyle.fill,

length)),

CustomPaint(

painter: BubbleCanvas(

context,

width,

height,

borderColor,

position,

arrHeight,

arrAngle,

radius,

strokeWidth,

style,

length)),

_paddingWidget()

]));

}

return bubbleWidget;

}

Widget _paddingWidget() {

return Padding(

padding: EdgeInsets.only(

top: (position == BubbleArrowDirection.top)

? arrHeight + innerPadding

: innerPadding,

right: (position == BubbleArrowDirection.right)

? arrHeight + innerPadding

: innerPadding,

bottom: (position == BubbleArrowDirection.bottom)

? arrHeight + innerPadding

: innerPadding,

left: (position == BubbleArrowDirection.left)

? arrHeight + innerPadding

: innerPadding),

child: Center(child: this.child));

}

}

class BubbleCanvas extends CustomPainter {

BuildContext context;

final position;

final arrHeight;

final arrAngle;

final radius;

final width;

final height;

final length;

final color;

final strokeWidth;

final style;

BubbleCanvas(

this.context,

this.width,

this.height,

this.color,

this.position,

this.arrHeight,

this.arrAngle,

this.radius,

this.strokeWidth,

this.style,

this.length);

@override

void paint(Canvas canvas, Size size) {

Path path = Path();

path.arcTo(

Rect.fromCircle(

center: Offset(

(position == BubbleArrowDirection.left)

? radius + arrHeight

: radius,

(position == BubbleArrowDirection.top)

? radius + arrHeight

: radius),

radius: radius),

pi,

pi * 0.5,

false);

if (position == BubbleArrowDirection.top) {

path.lineTo(length + radius, arrHeight);

path.lineTo(

length + radius + arrHeight * tan(_angle(arrAngle * 0.5)), 0.0);

path.lineTo(length + radius + arrHeight * tan(_angle(arrAngle * 0.5)) * 2,

arrHeight);

}

path.lineTo(

(position == BubbleArrowDirection.right)

? width - radius - arrHeight

: width - radius,

(position == BubbleArrowDirection.top) ? arrHeight : 0.0);

path.arcTo(

Rect.fromCircle(

center: Offset(

(position == BubbleArrowDirection.right)

? width - radius - arrHeight

: width - radius,

(position == BubbleArrowDirection.top)

? radius + arrHeight

: radius),

radius: radius),

-pi * 0.5,

pi * 0.5,

false);

if (position == BubbleArrowDirection.right) {

path.lineTo(width - arrHeight, length + radius);

path.lineTo(

width, length + radius + arrHeight * tan(_angle(arrAngle * 0.5)));

path.lineTo(width - arrHeight,

length + radius + arrHeight * tan(_angle(arrAngle * 0.5)) * 2);

}

path.lineTo(

(position == BubbleArrowDirection.right) ? width - arrHeight : width,

(position == BubbleArrowDirection.bottom)

? height - radius - arrHeight

: height - radius);

path.arcTo(

Rect.fromCircle(

center: Offset(

(position == BubbleArrowDirection.right)

? width - radius - arrHeight

: width - radius,

(position == BubbleArrowDirection.bottom)

? height - radius - arrHeight

: height - radius),

radius: radius),

pi * 0,

pi * 0.5,

false);

if (position == BubbleArrowDirection.bottom) {

path.lineTo(width - radius - length, height - arrHeight);

path.lineTo(

width - radius - length - arrHeight * tan(_angle(arrAngle * 0.5)),

height);

path.lineTo(

width - radius - length - arrHeight * tan(_angle(arrAngle * 0.5)) * 2,

height - arrHeight);

}

path.lineTo(

(position == BubbleArrowDirection.left) ? radius + arrHeight : radius,

(position == BubbleArrowDirection.bottom)

? height - arrHeight

: height);

path.arcTo(

Rect.fromCircle(

center: Offset(

(position == BubbleArrowDirection.left)

? radius + arrHeight

: radius,

(position == BubbleArrowDirection.bottom)

? height - radius - arrHeight

: height - radius),

radius: radius),

pi * 0.5,

pi * 0.5,

false);

if (position == BubbleArrowDirection.left) {

path.lineTo(arrHeight, height - radius - length);

path.lineTo(0.0,

height - radius - length - arrHeight * tan(_angle(arrAngle * 0.5)));

path.lineTo(

arrHeight,

height -

radius -

length -

arrHeight * tan(_angle(arrAngle * 0.5)) * 2);

}

path.lineTo((position == BubbleArrowDirection.left) ? arrHeight : 0.0,

(position == BubbleArrowDirection.top) ? radius + arrHeight : radius);

path.close();

canvas.drawPath(

path,

Paint()

..color = color

..style = style

..strokeCap = StrokeCap.round

..strokeWidth = strokeWidth);

}

@override

bool shouldRepaint(CustomPainter oldDelegate) {

return true;

}

}

double _angle(angle) {

return angle * pi / 180;

}

2.气泡组件使用

注意事项

必填参数

宽度 ScreenUtil().setWidth(326),

高度 ScreenUtil().setWidth(64),

背景颜色 Color(0xff333333),

位置 BubbleArrowDirection.bottom

可选参数

箭头宽度 length: ScreenUtil().setWidth(20)

箭头高度 arrHeight : ScreenUtil().setWidth(12)

箭头读书 arrAngle: 75.0,

子Widget与起泡间距 innerPadding

import 'package:flutter/material.dart';

import 'package:flutter_screenutil/flutter_screenutil.dart';

import 'package:fpdxapp/components/bubble/bubble_widget.dart';

class BubblePage extends StatelessWidget {

@override

Widget build(BuildContext context) {

return Scaffold(

body: ListView(children: [

SizedBox(

height: 20,

),

///1- 复制删除,撤回消息-气泡BottomRight

Padding(

padding: EdgeInsets.all(4.0),

child: BubbleWidget(

ScreenUtil().setWidth(326),

ScreenUtil().setWidth(64),

Color(0xff333333),

BubbleArrowDirection.bottom,

length: ScreenUtil().setWidth(20),

child: Row(

mainAxisSize: MainAxisSize.max,

children: [

// 复制按钮

GestureDetector(

onTap: () {},

child: Container(

child: Center(

child: Text(

'复制',

style: TextStyle(

color: Color(0xffE4E4E4),

fontSize: ScreenUtil().setSp(20)),

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

// line

Container(

width: ScreenUtil().setWidth(1),

color: Color(0xff707070)),

// 删除按钮

GestureDetector(

onTap: () {},

child: Container(

child: Center(

child: Text(

'删除',

style: TextStyle(

color: Color(0xffE4E4E4),

fontSize: ScreenUtil().setSp(20)),

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

// line

Container(

width: ScreenUtil().setWidth(1),

color: Color(0xff707070)),

// 撤回按钮

GestureDetector(

onTap: () {},

child: Container(

child: Center(

child: Text(

'撤回',

style: TextStyle(

color: Color(0xffE4E4E4),

fontSize: ScreenUtil().setSp(20)),

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

],

),

arrHeight: ScreenUtil().setWidth(12),

arrAngle: 75.0,

innerPadding: 0.0,

)),

SizedBox(

height: 5,

),

///2- 复制删除,撤回消息-气泡BottomLeft

Padding(

padding: EdgeInsets.all(4.0),

child: BubbleWidget(

ScreenUtil().setWidth(326),

ScreenUtil().setWidth(64),

Color(0xff333333),

BubbleArrowDirection.bottom,

length: ScreenUtil().setWidth(250),

child: Row(

mainAxisSize: MainAxisSize.max,

children: [

// 复制按钮

GestureDetector(

onTap: () {},

child: Container(

child: Center(

child: Text(

'复制',

style: TextStyle(

color: Color(0xffE4E4E4),

fontSize: ScreenUtil().setSp(20)),

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

// line

Container(

width: ScreenUtil().setWidth(1), color: Color(0xff707070)),

// 删除按钮

GestureDetector(

onTap: () {},

child: Container(

child: Center(

child: Text(

'删除',

style: TextStyle(

color: Color(0xffE4E4E4),

fontSize: ScreenUtil().setSp(20)),

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

// line

Container(

width: ScreenUtil().setWidth(1), color: Color(0xff707070)),

// 撤回按钮

GestureDetector(

onTap: () {},

child: Container(

child: Center(

child: Text(

'撤回',

style: TextStyle(

color: Color(0xffE4E4E4),

fontSize: ScreenUtil().setSp(20)),

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

],

),

arrHeight: ScreenUtil().setWidth(12),

arrAngle: 75.0,

innerPadding: 0.0,

),

),

SizedBox(

height: 5,

),

///3- 复制删除,撤回消息-气泡TopLeft

Padding(

padding: EdgeInsets.all(4.0),

child: BubbleWidget(

ScreenUtil().setWidth(326),

ScreenUtil().setWidth(64),

Color(0xff333333),

BubbleArrowDirection.top,

length: ScreenUtil().setWidth(20),

child: Row(

mainAxisSize: MainAxisSize.max,

children: [

// 复制按钮

GestureDetector(

onTap: () {},

child: Container(

child: Center(

child: Text(

'复制',

style: TextStyle(

color: Color(0xffE4E4E4),

fontSize: ScreenUtil().setSp(20)),

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

// line

Container(

width: ScreenUtil().setWidth(1),

color: Color(0xff707070)),

// 删除按钮

GestureDetector(

onTap: () {},

child: Container(

child: Center(

child: Text(

'删除',

style: TextStyle(

color: Color(0xffE4E4E4),

fontSize: ScreenUtil().setSp(20)),

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

// line

Container(

width: ScreenUtil().setWidth(1),

color: Color(0xff707070)),

// 撤回按钮

GestureDetector(

onTap: () {},

child: Container(

child: Center(

child: Text(

'撤回',

style: TextStyle(

color: Color(0xffE4E4E4),

fontSize: ScreenUtil().setSp(20)),

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

],

),

arrHeight: ScreenUtil().setWidth(12),

arrAngle: 75.0,

innerPadding: 0.0,

)),

SizedBox(

height: 5,

),

///4- 复制删除,撤回消息-气泡TopRight

Padding(

padding: EdgeInsets.all(4.0),

child: BubbleWidget(

ScreenUtil().setWidth(326),

ScreenUtil().setWidth(64),

Color(0xff333333),

BubbleArrowDirection.top,

length: ScreenUtil().setWidth(250),

child: Row(

mainAxisSize: MainAxisSize.max,

children: [

// 复制按钮

GestureDetector(

onTap: () {},

child: Container(

child: Center(

child: Text(

'复制',

style: TextStyle(

color: Color(0xffE4E4E4),

fontSize: ScreenUtil().setSp(20)),

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

// line

Container(

width: ScreenUtil().setWidth(1), color: Color(0xff707070)),

// 删除按钮

GestureDetector(

onTap: () {},

child: Container(

child: Center(

child: Text(

'删除',

style: TextStyle(

color: Color(0xffE4E4E4),

fontSize: ScreenUtil().setSp(20)),

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

// line

Container(

width: ScreenUtil().setWidth(1), color: Color(0xff707070)),

// 撤回按钮

GestureDetector(

onTap: () {},

child: Container(

child: Center(

child: Text(

'撤回',

style: TextStyle(

color: Color(0xffE4E4E4),

fontSize: ScreenUtil().setSp(20)),

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

],

),

arrHeight: ScreenUtil().setWidth(12),

arrAngle: 75.0,

innerPadding: 0.0,

),

),

SizedBox(

height: 5,

),

// 气泡右

Padding(

padding: EdgeInsets.all(4.0),

child: Container(

alignment: Alignment.centerRight,

child: BubbleWidget(200.0, 40.0, Colors.blue.withOpacity(0.7),

BubbleArrowDirection.right,

child: Text('你好,我是BubbleWidget!',

style:

TextStyle(color: Colors.white, fontSize: 14.0))))),

Padding(

padding: EdgeInsets.all(4.0),

child: Container(

alignment: Alignment.bottomLeft,

child: BubbleWidget(300.0, 40.0, Colors.red.withOpacity(0.7),

BubbleArrowDirection.top,

length: 20,

child: Text('你好,你有什么特性化?',

style:

TextStyle(color: Colors.white, fontSize: 14.0))))),

Padding(

padding: EdgeInsets.all(4.0),

child: Container(

alignment: Alignment.centerRight,

child: BubbleWidget(300.0, 90.0, Colors.blue.withOpacity(0.7),

BubbleArrowDirection.right,

child: Text('我可以自定义:\n尖角方向,尖角高度,尖角角度,\n距圆角位置,圆角大小,边框样式等!',

style:

TextStyle(color: Colors.white, fontSize: 16.0))))),

Padding(

padding: EdgeInsets.all(4.0),

child: Container(

alignment: Alignment.centerLeft,

child: BubbleWidget(140.0, 40.0, Colors.cyan.withOpacity(0.7),

BubbleArrowDirection.left,

child: Text('你有什么不足?',

style:

TextStyle(color: Colors.white, fontSize: 14.0))))),

Padding(

padding: EdgeInsets.all(4.0),

child: Container(

alignment: Alignment.centerRight,

child: BubbleWidget(350.0, 60.0, Colors.green.withOpacity(0.7),

BubbleArrowDirection.right,

child: Text('我现在还不会动态计算高度,只可用作背景!',

style:

TextStyle(color: Colors.white, fontSize: 16.0))))),

Padding(

padding: EdgeInsets.all(4.0),

child: Container(

alignment: Alignment.centerLeft,

child: BubbleWidget(

105.0,

60.0,

Colors.deepOrange.withOpacity(0.7),

BubbleArrowDirection.left,

child: Text('继续加油!',

style:

TextStyle(color: Colors.white, fontSize: 16.0))))),

]),

appBar: AppBar(

centerTitle: true,

leading: GestureDetector(

child: Icon(Icons.arrow_back_ios,

size: 20, color: Color(0xff333333)),

onTap: () {

Navigator.of(context).maybePop();

},

),

title: Text(

'气泡合集',

style: TextStyle(color: Colors.black),

),

),

);

}

}

作者:StevenHu

分享到:更多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值