【2020-1-10】

效果图

remind_bubble.gif

使用案例

class BubbleDemo extends StatefulWidget {

@override

State createState() {

return _BubbleDemoState();

}

}

class _BubbleDemoState extends State {

// 是否显示左边气泡

bool leftTipReplay = false;

// 是否显示左边气泡

bool rightTipReplay = false;

@override

Widget build(BuildContext context) {

return Scaffold(

appBar: CustomAppbar.appBar(

context,

title: '提醒气泡组件',

theme: CustomAppbarTheme.white,

),

body: Column(

children: [

SizedBox(height: 50,),

// 气泡左边

GestureDetector(

child: Stack(

children: [

//主题

Container(

width: Screen.width,

height: 200,

color: Colors.red,

child: Center(

child: Text(

'BUBBLE_LEFT',

style: TextStyle(

fontSize: 36,

color: Colors.white,

),

),

),

),

Positioned(

top: 20,

left: -20,

child: _showLeftBubble(),

),

],

),

onTap: () {

rightTipReplay = false;

leftTipReplay = true;

setState(() {});

},

),

SizedBox(height: 50,),

// 气泡右边

GestureDetector(

child: Stack(

children: [

//主题

Container(

width: Screen.width,

height: 200,

color: Colors.blueAccent,

child: Center(

child: Text(

'BUBBLE_RIGHT',

style: TextStyle(

fontSize: 36,

color: Colors.white,

),

),

),

),

Positioned(

top: 20,

left: -20,

child: _showRightBubble(),

),

],

),

onTap: () {

leftTipReplay = false;

rightTipReplay = true;

setState(() {});

},

)

],

),

);

}

Widget _showLeftBubble() {

return leftTipReplay

? Container(

width: AdaptationUtils.setWidth(228.0),

height: AdaptationUtils.setWidth(50.0),

child: TipBubble(

content: '双方在倒计时结束前确认CP关系后即可查看对方联系方式',

tipReplay: leftTipReplay,

// tipDuration: 90000,

top: AdaptationUtils.px(-20.0),

left: AdaptationUtils.px(64.0),

hornDirction: 'left',

),

)

: Container();

}

Widget _showRightBubble() {

return rightTipReplay

? Container(

width: AdaptationUtils.setWidth(228.0),

height: AdaptationUtils.setWidth(50.0),

child: TipBubble(

content: '双方在倒计时结束前确认CP关系后即可查看对方联系方式',

tipReplay: rightTipReplay,

// tipDuration: 90000,

top: AdaptationUtils.px(-20.0),

left: AdaptationUtils.px(64.0),

hornDirction: 'right',

),

)

: Container();

}

}

先上效果图(聊天气泡)

效果图

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),

),

),

);

}

}

方案二:通过 自定义PopupRoute 实现BubbleWidget气泡

通过 自定义PopupRoute 实现多个Bubble只选择一个,避免多个

PopupWindow 自定义

通过路由实现气泡的显示和隐藏

通过 GlobalKey 获取Widget的位置

显示

Navigator.push(

context,

PopRoute(

child: Bubble()

),

);

效果图

效果图

关键源码封装

import 'package:flutter/material.dart';

class Popup extends StatelessWidget {

final Widget child;

final Function onClick; //点击child事件

final EdgeInsetsGeometry padding;

Popup({

@required this.child,

this.onClick,

this.padding,

});

@override

Widget build(BuildContext context) {

return Material(

color: Colors.transparent,

child: GestureDetector(

child: Stack(

children: [

Container(

width: MediaQuery.of(context).size.width,

height: MediaQuery.of(context).size.height,

color: Colors.transparent,

),

Padding(

child: child,

padding: padding,

),

],

),

onTap: () {

//点击空白处

Navigator.of(context).pop();

},

),

);

}

}

class PopRoute extends PopupRoute {

final Duration _duration = Duration(milliseconds: 300);

Widget child;

PopRoute({@required this.child});

@override

Color get barrierColor => null;

@override

bool get barrierDismissible => true;

@override

String get barrierLabel => null;

@override

Widget buildPage(BuildContext context, Animation animation,

Animation secondaryAnimation) {

return child;

}

@override

Duration get transitionDuration => _duration;

}

使用案例

import 'package:flutter/material.dart';

import 'package:flutter_screenutil/flutter_screenutil.dart';

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

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

import 'package:fpdxapp/components/custom_appbar.dart';

import 'package:fpdxapp/constants/icons.dart';

import 'package:fpdxapp/utils/toast.dart';

import 'package:fpdxapp/utils/widget.dart';

class BubbleDemo extends StatefulWidget {

@override

State createState() {

return _BubbleDemoState();

}

}

class _BubbleDemoState extends State {

GlobalKey btnKey = GlobalKey(debugLabel: '1');

List menus = [{'text':'ShowMenu1','isYourSelf':true}, {'text':'ShowMenu2','isYourSelf':false}, {'text':'ShowMenu3','isYourSelf':true}];

@override

Widget build(BuildContext context) {

return Scaffold(

appBar: CustomAppbar.appBar(

context,

title: '气泡',

theme: CustomAppbarTheme.white,

),

body: Container(

child: Column(

children: menus.map((item) {

return CellForRow(

text: item['text'],

isYourSelf: item['isYourSelf'],

);

}).toList(),

),

margin: EdgeInsets.only(top: 100),

),

);

}

}

class CellForRow extends StatefulWidget {

final String text;

final bool isYourSelf;

CellForRow({this.text,this.isYourSelf = true});

@override

_CellForRowState createState() => _CellForRowState();

}

class _CellForRowState extends State {

GlobalKey btnKey = GlobalKey();

@override

void initState() {

// TODO: implement initState

super.initState();

if (this.widget.text != null) {

btnKey = GlobalKey(debugLabel: this.widget.text);

}

}

@override

Widget build(BuildContext context) {

return Container(

child: MaterialButton(

key: btnKey,

height: 45.0,

onPressed: () {

_showBubble(

context,

globalKey: btnKey,

valueChanged: (i, text) {

Toast.show("值变化了${text}");

},

isYourSelf: this.widget.isYourSelf,

);

},

child: Text(this.widget.text),

),

);

}

///弹出退出按钮

void _showBubble(@required BuildContext context,

{Widget child,

GlobalKey globalKey,

List titles,

Function(int i, String text) valueChanged,

bool isYourSelf = true}) {

if (globalKey == null) {

return;

}

if (titles == null) {

titles = ['复制', '删除', '撤回'];

}

Rect rect = WidgetUtil.getRectWithWidgetGlobalKey(globalKey);

print('气泡===============>>>>>>>>>>>>rect${rect}');

Navigator.push(

context,

PopRoute(

child: isYourSelf

? Popup(

child: BubbleWidget(

ScreenUtil().setWidth(109) * titles.length,

ScreenUtil().setWidth(64),

Color(0xff333333),

BubbleArrowDirection.bottom,

length: ScreenUtil().setWidth(250),

child: Row(

mainAxisSize: MainAxisSize.max,

children: titles.asMap().keys.map((i) {

return Container(

child: Row(

children: [

GestureDetector(

onTap: () {

if (valueChanged != null) {

valueChanged(i, titles[i]);

}

Navigator.of(context).pop();

},

child: Container(

child: Center(

child: Text(

titles[i],

style: TextStyle(

color: Color(0xffE4E4E4),

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

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

i == titles.length - 1

? Container()

: Container(

width: ScreenUtil().setWidth(1),

color: Color(0xff707070)),

],

),

);

}).toList(),

),

arrHeight: ScreenUtil().setWidth(12),

arrAngle: 75.0,

innerPadding: 0.0,

),

padding: EdgeInsets.fromLTRB(

rect.left + ScreenUtil().setWidth(40),

rect.top - ScreenUtil().setWidth(20),

rect.right,

rect.bottom),

)

: Popup(

child: BubbleWidget(

ScreenUtil().setWidth(109) * titles.length,

ScreenUtil().setWidth(64),

Color(0xff333333),

BubbleArrowDirection.bottom,

length: ScreenUtil().setWidth(20),

child: Row(

mainAxisSize: MainAxisSize.max,

children: titles.asMap().keys.map((i) {

return Container(

child: Row(

children: [

GestureDetector(

onTap: () {

if (valueChanged != null) {

valueChanged(i, titles[i]);

}

Navigator.of(context).pop();

},

child: Container(

child: Center(

child: Text(

titles[i],

style: TextStyle(

color: Color(0xffE4E4E4),

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

),

),

width: ScreenUtil().setWidth(108),

height: ScreenUtil().setWidth(64),

),

),

i == titles.length - 1

? Container()

: Container(

width: ScreenUtil().setWidth(1),

color: Color(0xff707070)),

],

),

);

}).toList(),

),

arrHeight: ScreenUtil().setWidth(12),

arrAngle: 75.0,

innerPadding: 0.0,

),

padding: EdgeInsets.fromLTRB(

rect.left + ScreenUtil().setWidth(40),

rect.top - ScreenUtil().setWidth(20),

rect.right,

rect.bottom),

),

),

);

}

}

flutter图片聊天泡泡_Flutter 气泡效果合集(全网最全)相关推荐

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

    这个消息气泡可以用于社交的聊天气泡,或者其他长按弹出的效果, 应用场景挺多,主要是用于学习. 先上效果图 1.BubbleWidget封装 通过系统的Canvas绘制 /// 气泡组件封装 /// / ...

  2. flutter图片聊天泡泡_Flutter极致的业务封装——各类聊天气泡(一)

    前言 真的有段时间没写博客了,因为过去的一段时间工作实在是太忙了

  3. flutter图片聊天泡泡_flutter即时聊天IM仿微信|flutter聊天界面

    鉴于Flutter最近比较火,今天给大家分享的是基于flutter+dart全家桶技术开发的仿微信界面聊天FlutterChat项目实例.实现了消息+表情.图片预览.红包.长按菜单.视频/仿朋友圈等功 ...

  4. flutter图片聊天泡泡_Flutter/dart聊天实例|仿微信界面|红包|朋友圈

    FlutterChatroom项目是基于flutter+dart+image_picker等技术实现的仿微信app聊天室实战项目. 一.技术框架编码/技术:Vscode + Flutter 1.12. ...

  5. flutter图片聊天泡泡_flutter/dart聊天实例|flutter仿微信

    > Flutter 是 Google 开源的 UI 框架,能高效构建多平台精美应用. > [FlutterChat聊天室项目](https://juejin.im/post/5ebb5c4 ...

  6. reflect动画,Flutter Animations动画效果合集(全网最全)

    CurvedAnimation(曲线动画) Tween(补间动画/伸缩动画) Loading加载动画(ProgressDialog) Fade 渐入淡出动画 AnimatedBuilder(曲线动画) ...

  7. CSS3常用动画效果合集(最全)

    CSS3常见的动画效果合集: /* animation */ .a-bounce,.a-flip,.a-flash,.a-shake,.a-swing,.a-wobble,.a-ring{-webki ...

  8. flutter图片聊天泡泡_[Flutter]聊天气泡组件

    import 'dart:math'; import 'package:flutter/material.dart'; // 尖角方向枚举 enum BubbleAngleDirection { le ...

  9. flutter图片聊天泡泡_基于 Flutter+Dart 聊天实例 | Flutter 仿微信界面聊天室

    1.项目介绍 Flutter是目前比较流行的跨平台开发技术,凭借其出色的性能获得很多前端技术爱好者的关注,比如阿里闲鱼,美团,腾讯等大公司都有投入相关案例生产使用. flutter_chatroom项 ...

最新文章

  1. 纳微科技完成近亿元融资,华兴医疗产业基金独家投资
  2. Android JNI 传递对象
  3. 杭电2031进制转换
  4. AngularJS2 环境搭建:
  5. AUTOSAR从入门到精通100讲(三十五)-AUTOSAR BswM三部曲-概念实践代码分析
  6. Centos中查找文件、目录、内容
  7. wordpress教程:默认http头信息X-Pingback的隐藏与修改
  8. 联通辟谣“不支持华为 5G”;罗永浩称索尼手机不如锤子;Linux 5.2.1 发布 | 极客头条...
  9. XNOR.ai融资1200万美元
  10. mysql 存储过程 调度_mysql 存储过程和事件调度
  11. Serv-U权限提升再提升--记一次虚拟主机入侵
  12. 查看python的模块和函数帮助文档方法
  13. 神推荐:西瓜导航你值得拥有
  14. Kali系统学习:弱点扫描工具NMAP实战演示
  15. activiti工作流数据库表详细说明
  16. 如何识别图片文字?这几个识别图片文字软件简单又高效
  17. 关于火车采集文章发布到wordpress后台待审核模块的设置
  18. GO语言-第二节-顺序编程
  19. Yobili优碧俪打造轻奢营养食品,以品质破圈
  20. matlab用多个cpu,Matlab 多核 多个CPU 并行运算

热门文章

  1. uni-app接入友盟
  2. 数字营销(三)如何确定合适的流量渠道?
  3. The server of Nginx(二)——Nginx基本功能配置
  4. 软件工程——理论与实践(第二版)课后习题整理
  5. 新疆、内蒙、青海三省区骆驼齐聚柴达木上演“激情与速度”
  6. “小度小度”开启AI硬件的“量贩”时代
  7. 基于用户的音乐推荐平台
  8. 2009年7月最新的精辟句子和2009最贫嘴的15句话
  9. 数据采集程序-----直播间
  10. 助力武汉抗击疫情,湖北科研资料共享