flutter 仿微信长按弹窗复制撤回粘贴收藏等自定义定制

本文主要是介绍flutter 仿微信长按弹窗复制撤回粘贴收藏等自定义定制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

根据 https://blog.csdn.net/qq_23756803/article/details/99519441

这里的代码实现了功能,但是很多地方依然有问题,需要配置图片,还需要处理样式的箭头,以及多行的bug

所以我自己修改了很多地方的代码,封装了一个

 

import 'package:flutter/material.dart';
import 'package:lvsongguo/utils/widget_w_popup/triangle_painter.dart';const double _kMenuScreenPadding = 8.0;
const List<String> wPopupMenuActions = ['复制','转发','收藏','删除','撤回','提醒','翻译','标记',
];class WPopupMenu extends StatefulWidget {WPopupMenu({Key key,@required this.onValueChanged,@required this.actions,@required this.child,this.pressType = PressType.longPress,this.pageMaxChildCount = 5,this.backgroundColor = Colors.black,this.menuWidth = 225,this.menuHeight = 42,});final ValueChanged<int> onValueChanged;final List<String> actions;final Widget child;final PressType pressType; // 点击方式 长按 还是单击final int pageMaxChildCount;final Color backgroundColor;final double menuWidth;final double menuHeight;@override_WPopupMenuState createState() => _WPopupMenuState();
}class _WPopupMenuState extends State<WPopupMenu> {double width;double height;RenderBox button;RenderBox overlay;OverlayEntry entry;@overridevoid initState() {super.initState();WidgetsBinding.instance.addPostFrameCallback((call) {width = context.size.width;height = context.size.height;button = context.findRenderObject();overlay = Overlay.of(context).context.findRenderObject();});}@overrideWidget build(BuildContext context) {return WillPopScope(onWillPop: () {if (entry != null) {removeOverlay();}return Future.value(true);},child: GestureDetector(child: widget.child,onTap: () {if (widget.pressType == PressType.singleClick) {onTap();}},onLongPress: () {if (widget.pressType == PressType.longPress) {onTap();}},onDoubleTap: () {if (widget.pressType == PressType.doubleClick) {onTap();}},),);}void onTap() {Widget menuWidget = _MenuPopWidget(context,height,width,widget.actions,widget.pageMaxChildCount,widget.backgroundColor,widget.menuWidth,widget.menuHeight,button,overlay,(index) {if (index != -1) widget.onValueChanged(index);removeOverlay();},);entry = OverlayEntry(builder: (context) {return menuWidget;});Overlay.of(context).insert(entry);}void removeOverlay() {entry.remove();entry = null;}
}enum PressType {// 长按longPress,// 单击singleClick,// 双击doubleClick,
}class _MenuPopWidget extends StatefulWidget {final BuildContext btnContext;final List<String> actions;final int _pageMaxChildCount;final Color backgroundColor;final double menuWidth;final double menuHeight;final double _height;final double _width;final RenderBox button;final RenderBox overlay;final ValueChanged<int> onValueChanged;_MenuPopWidget(this.btnContext,this._height,this._width,this.actions,this._pageMaxChildCount,this.backgroundColor,this.menuWidth,this.menuHeight,this.button,this.overlay,this.onValueChanged,);@override_MenuPopWidgetState createState() => _MenuPopWidgetState();
}class _MenuPopWidgetState extends State<_MenuPopWidget> {int _curPage = 0;final double _arrowWidth = 40;final double _separatorWidth = 1;final double _triangleHeight = 10;RelativeRect position;@overridevoid initState() {super.initState();position = RelativeRect.fromRect(Rect.fromPoints(widget.button.localToGlobal(Offset.zero, ancestor: widget.overlay),widget.button.localToGlobal(Offset.zero, ancestor: widget.overlay),),Offset.zero & widget.overlay.size,);}@overrideWidget build(BuildContext context) {// 这里计算出来 当前页的 child 一共有多少个int _curPageChildCount = (_curPage + 1) * widget._pageMaxChildCount > widget.actions.length? widget.actions.length % widget._pageMaxChildCount: widget._pageMaxChildCount;double _curArrowWidth = 0;int _curArrowCount = 0; // 一共几个箭头if (widget.actions.length > widget._pageMaxChildCount) {// 数据长度大于 widget._pageMaxChildCountif (_curPage == 0) {// 如果是第一页_curArrowWidth = _arrowWidth;_curArrowCount = 1;} else if ((_curPage + 1) * widget._pageMaxChildCount >= widget.actions.length) {// 如果不是第一页 则需要也显示左箭头_curArrowWidth = _arrowWidth;_curArrowCount = 2;} else {_curArrowWidth = _arrowWidth * 2;_curArrowCount = 2;}}double _curPageWidth =widget.menuWidth + (_curPageChildCount - 1 + _curArrowCount) * _separatorWidth + _curArrowWidth;return GestureDetector(behavior: HitTestBehavior.opaque,onTap: () {widget.onValueChanged(-1);},child: MediaQuery.removePadding(context: context,removeTop: true,removeBottom: true,removeLeft: true,removeRight: true,child: Builder(builder: (BuildContext context) {var isInverted = (position.top +(MediaQuery.of(context).size.height - position.top - position.bottom) / 2.0 -(widget.menuHeight + _triangleHeight)) <(widget.menuHeight + _triangleHeight) * 2;return CustomSingleChildLayout(delegate: _PopupMenuRouteLayout(position, widget.menuHeight + _triangleHeight,Directionality.of(widget.btnContext), widget._width, widget.menuWidth, widget._height),child: SizedBox(height: widget.menuHeight + _triangleHeight,width: _curPageWidth,child: Material(color: Colors.transparent,child: Column(mainAxisSize: MainAxisSize.min,children: <Widget>[isInverted? CustomPaint(size: Size(_curPageWidth, _triangleHeight),painter: TrianglePainter(color: widget.backgroundColor,position: position,isInverted: true,size: widget.button.size,screenWidth: MediaQuery.of(context).size.width,),): Container(),Expanded(child: Stack(children: <Widget>[ClipRRect(borderRadius: BorderRadius.all(Radius.circular(5)),child: Container(color: widget.backgroundColor,height: widget.menuHeight,),),Row(mainAxisSize: MainAxisSize.min,children: <Widget>[_curPage == 0? Container(height: widget.menuHeight,): InkWell(onTap: () {setState(() {_curPage--;});},child: Container(width: _arrowWidth,height: widget.menuHeight,child: Icon(Icons.arrow_left, color: Colors.white),),),_curPage == 0? Container(height: widget.menuHeight,): Container(width: 1,height: widget.menuHeight,color: Colors.grey,),_buildList(_curPageChildCount, _curPageWidth, _curArrowWidth, _curArrowCount),_curArrowCount > 0? (_curPage + 1) * widget._pageMaxChildCount >= widget.actions.length? Container(height: widget.menuHeight,): Container(width: 1,color: Colors.grey,height: widget.menuHeight,): Container(height: widget.menuHeight,),_curArrowCount > 0? (_curPage + 1) * widget._pageMaxChildCount >= widget.actions.length? Container(height: widget.menuHeight,): InkWell(onTap: () {if ((_curPage + 1) * widget._pageMaxChildCount < widget.actions.length)setState(() {_curPage++;});},child: Container(width: _arrowWidth,height: widget.menuHeight,child: Container(width: _arrowWidth,height: widget.menuHeight,child: Icon(Icons.arrow_right, color: Colors.white),),),): Container(height: widget.menuHeight,),],),],),),isInverted? Container(): CustomPaint(size: Size(_curPageWidth, _triangleHeight),painter: TrianglePainter(color: widget.backgroundColor,position: position,size: widget.button.size,screenWidth: MediaQuery.of(context).size.width,),),],),),),);},),),);}Widget _buildList(int _curPageChildCount, double _curPageWidth, double _curArrowWidth, int _curArrowCount) {return ListView.separated(shrinkWrap: true,physics: NeverScrollableScrollPhysics(),scrollDirection: Axis.horizontal,itemCount: _curPageChildCount,itemBuilder: (BuildContext context, int index) {return GestureDetector(onTap: () {widget.onValueChanged(_curPage * widget._pageMaxChildCount + index);},child: SizedBox(width: (_curPageWidth - _curArrowWidth - (_curPageChildCount - 1 + _curArrowCount) * _separatorWidth) /_curPageChildCount,height: widget.menuHeight,child: Center(child: Text(widget.actions[_curPage * widget._pageMaxChildCount + index],style: TextStyle(color: Colors.white, fontSize: 16),),),),);},separatorBuilder: (BuildContext context, int index) {return Container(width: 1,height: widget.menuHeight,color: Colors.grey,);},);}
}// Positioning of the menu on the screen.
class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {_PopupMenuRouteLayout(this.position, this.selectedItemOffset, this.textDirection, this.width, this.menuWidth, this.height);// Rectangle of underlying button, relative to the overlay's dimensions.final RelativeRect position;// The distance from the top of the menu to the middle of selected item.//// This will be null if there's no item to position in this way.final double selectedItemOffset;// Whether to prefer going to the left or to the right.final TextDirection textDirection;final double width;final double height;final double menuWidth;// We put the child wherever position specifies, so long as it will fit within// the specified parent size padded (inset) by 8. If necessary, we adjust the// child's position so that it fits.@overrideBoxConstraints getConstraintsForChild(BoxConstraints constraints) {// The menu can be at most the size of the overlay minus 8.0 pixels in each// direction.return BoxConstraints.loose(constraints.biggest - const Offset(_kMenuScreenPadding * 2.0, _kMenuScreenPadding * 2.0));}@overrideOffset getPositionForChild(Size size, Size childSize) {// size: The size of the overlay.// childSize: The size of the menu, when fully open, as determined by// getConstraintsForChild.// Find the ideal vertical position.double y;if (selectedItemOffset == null) {y = position.top;} else {y = position.top + (size.height - position.top - position.bottom) / 2.0 - selectedItemOffset;}// Find the ideal horizontal position.double x;// 如果menu 的宽度 小于 child 的宽度,则直接把menu 放在 child 中间if (childSize.width < width) {x = position.left + (width - childSize.width) / 2;} else {// 如果靠右if (position.left > size.width - (position.left + width)) {if (size.width - (position.left + width) > childSize.width / 2 + _kMenuScreenPadding) {x = position.left - (childSize.width - width) / 2;} else {x = position.left + width - childSize.width;}} else if (position.left < size.width - (position.left + width)) {if (position.left > childSize.width / 2 + _kMenuScreenPadding) {x = position.left - (childSize.width - width) / 2;} elsex = position.left;} else {x = position.right - width / 2 - childSize.width / 2;}}if (y < _kMenuScreenPadding)y = _kMenuScreenPadding;else if (y + childSize.height > size.height - _kMenuScreenPadding)y = size.height - childSize.height;else if (y < childSize.height * 2) {y = position.top + height;}return Offset(x, y);}@overridebool shouldRelayout(_PopupMenuRouteLayout oldDelegate) {return position != oldDelegate.position;}
}

这篇关于flutter 仿微信长按弹窗复制撤回粘贴收藏等自定义定制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/725867

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

禁止复制的网页怎么复制

禁止复制的网页怎么复制 文章目录 禁止复制的网页怎么复制前言准备工作操作步骤一、在浏览器菜单中找到“开发者工具”二、点击“检查元素(inspect element)”按钮三、在网页中选取需要的片段,锁定对应的元素四、复制被选中的元素五、粘贴到记事本,以`.html`为后缀命名六、打开`xxx.html`,优雅地复制 前言 在浏览网页的时候,有的网页内容无法复制。比如「360

Oracle type (自定义类型的使用)

oracle - type   type定义: oracle中自定义数据类型 oracle中有基本的数据类型,如number,varchar2,date,numeric,float....但有时候我们需要特殊的格式, 如将name定义为(firstname,lastname)的形式,我们想把这个作为一个表的一列看待,这时候就要我们自己定义一个数据类型 格式 :create or repla

Flutter Button使用

Material 组件库中有多种按钮组件如ElevatedButton、TextButton、OutlineButton等,它们的父类是于ButtonStyleButton。         基本的按钮特点:         1.按下时都会有“水波文动画”。         2.onPressed属性设置点击回调,如果不提供该回调则按钮会处于禁用状态,禁用状态不响应用户点击。

PDFQFZ高效定制:印章位置、大小随心所欲

前言 在科技编织的快节奏时代,我们不仅追求速度,更追求质量,让每一分努力都转化为生活的甜蜜果实——正是在这样的背景下,一款名为PDFQFZ-PDF的实用软件应运而生,它以其独特的功能和高效的处理能力,在PDF文档处理领域脱颖而出。 它的开发,源自于对现代办公效率提升的迫切需求。在数字化办公日益普及的今天,PDF作为一种跨平台、不易被篡改的文档格式,被广泛应用于合同签署、报告提交、证书打印等各个

HTML5自定义属性对象Dataset

原文转自HTML5自定义属性对象Dataset简介 一、html5 自定义属性介绍 之前翻译的“你必须知道的28个HTML5特征、窍门和技术”一文中对于HTML5中自定义合法属性data-已经做过些介绍,就是在HTML5中我们可以使用data-前缀设置我们需要的自定义属性,来进行一些数据的存放,例如我们要在一个文字按钮上存放相对应的id: <a href="javascript:" d

一步一步将PlantUML类图导出为自定义格式的XMI文件

一步一步将PlantUML类图导出为自定义格式的XMI文件 说明: 首次发表日期:2024-09-08PlantUML官网: https://plantuml.com/zh/PlantUML命令行文档: https://plantuml.com/zh/command-line#6a26f548831e6a8cPlantUML XMI文档: https://plantuml.com/zh/xmi