本文主要是介绍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 仿微信长按弹窗复制撤回粘贴收藏等自定义定制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!