当前位置: 首页 > news >正文

计算机应用技术是学什么广州专业seo公司

计算机应用技术是学什么,广州专业seo公司,北京网站建设资讯,网上下载的网站模板怎么用03_Flutter自定义下拉菜单 在Flutter的内置api中,可以使用showMenu实现类似下拉菜单的效果,或者使用PopupMenuButton组件,PopupMenuButton内部也是使用了showMenu这个api,但是使用showMenu时,下拉面板的显示已经被约定…

03_Flutter自定义下拉菜单

在Flutter的内置api中,可以使用showMenu实现类似下拉菜单的效果,或者使用PopupMenuButton组件,PopupMenuButton内部也是使用了showMenu这个api,但是使用showMenu时,下拉面板的显示已经被约定死了,只能放一个简单的列表,没有办法定制下来面板的ui,并且下拉面板的宽高需要通过指定constraints进行限制,下面是一个简单的showMenu的用法:

Container(height: 44,margin: EdgeInsetsDirectional.only(top: 30, start: 30, end: 30),color: Colors.red,child: Builder(builder: (context) {return GestureDetector(onTap: () {final RenderBox button = context.findRenderObject()! as RenderBox;final RenderBox overlay = Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;Offset offset = Offset(0.0, button.size.height);RelativeRect position = RelativeRect.fromRect(Rect.fromPoints(button.localToGlobal(offset, ancestor: overlay),button.localToGlobal(button.size.bottomRight(Offset.zero) + offset, ancestor: overlay),),Offset.zero & overlay.size,);showMenu(context: context,position: position,constraints: BoxConstraints(maxWidth: 315, maxHeight: 200),items: List.generate(5, (index) => PopupMenuItem(child: Container(width: 375,height: 44,alignment: AlignmentDirectional.center,child: Text("item"),))));},);},),
)

在这里插入图片描述

接下来,我们将参照showMenu的源码,依葫芦画个瓢,自定义一个下拉菜单的api,并可自由定制下拉面板的布局内容,篇幅有点长,请耐心观看。

一.确定下拉面板的起始位置

查看PopupMenuButton的源码,可以知道,PopupMenuButton在确定下拉面板的起始位置时,是先获取下拉面板依赖的按钮的边界位置和整个页面的显示区域边界,通过这两个边界计算得到一个RelativeRect,这个RelativeRect就是用来描述下拉面板的起始位置的。

showPopup(BuildContext context) {final RenderBox button = context.findRenderObject()! as RenderBox;final RenderBox overlay = Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;Offset offset = Offset(0.0, button.size.height);RelativeRect position = RelativeRect.fromRect(Rect.fromPoints(button.localToGlobal(offset, ancestor: overlay),button.localToGlobal(button.size.bottomRight(Offset.zero) + offset, ancestor: overlay),),Offset.zero & overlay.size,);
}

注:上述代码中用的的context对象,必须是下拉面板依赖的按钮对应的context,否则最后计算出来的RelativeRect是不对的。计算过程不做过多解释了,直接上图:

在这里插入图片描述

二.确定下拉面板的布局约束

  • 水平方向确定最大宽度,比较简单,下拉面板的最大宽度和它所依赖的按钮的宽度一致即可
  • 垂直方向上的最大高度,上一步已经确定了position的值,垂直方向上的最大高度可以取position.top - buttonHeight - padding.top - kToolbarHeight和constraints.biggest.height - position.top - padding.bottom的最大值,padding为安全区域的大小
  • 使用CustomSingleChildLayout作为下拉面板的父容器,并实现一个SingleChildLayoutDelegate,重写getConstraintsForChild,确定约束
EdgeInsets padding = MediaQuery.paddingOf(context);class _CustomPopupRouteLayout extends SingleChildLayoutDelegate {final RelativeRect position;_CustomPopupRouteLayout(this.position);BoxConstraints getConstraintsForChild(BoxConstraints constraints) {Size buttonSize = position.toSize(constraints.biggest);double constraintsWidth = buttonSize.width;double constraintsHeight = max(position.top - buttonSize.height - padding.top - kToolbarHeight, constraints.biggest.height - position.top - padding.bottom);return BoxConstraints.loose(Size(constraintsWidth, constraintsHeight));}bool shouldRelayout(covariant _CustomPopupRouteLayout oldDelegate) {return position != oldDelegate.position;}
}

三.显示下拉面板

我们先把下拉面板显示出来看看效果,这里的下拉面板其实是一个弹出层,而在Flutter中,所有的弹出层的显示和页面路由是一样的,都是通过Navigator.push进行显示,参照showMenu的源码,这里的弹出层我们让其继承PopupRoute

class _CustomPopupRoute<T> extends PopupRoute<T> {final RelativeRect position;final String? barrierLabel;_CustomPopupRoute({required this.position,required this.barrierLabel,});Color? get barrierColor => null;bool get barrierDismissible => true;Duration get transitionDuration => Duration(milliseconds: 200);Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {return CustomSingleChildLayout(delegate: _CustomPopupRouteLayout(position),child: Material(child: Container(color: Colors.yellow,width: double.infinity,height: double.infinity,alignment: AlignmentDirectional.center,child: Text("popup content"),),),);}}
showPopup(BuildContext context) {final RenderBox button = context.findRenderObject()! as RenderBox;final RenderBox overlay = Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;Offset offset = Offset(0.0, button.size.height);RelativeRect position = RelativeRect.fromRect(Rect.fromPoints(button.localToGlobal(offset, ancestor: overlay),button.localToGlobal(button.size.bottomRight(Offset.zero) + offset, ancestor: overlay),),Offset.zero & overlay.size,);Navigator.of(context).push(_CustomPopupRoute(position: position, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel));
}

在这里插入图片描述

如图,黄色区域就是下拉面板,可以看到,点击按钮下拉面板显示,点击下拉面板以外的区域,下拉面板关闭,但是位置好像不对,因为我们根本就没去确定下拉面板的位置。

四.确定下拉面板的位置


Offset getPositionForChild(Size size, Size childSize) {return super.getPositionForChild(size, childSize);
}

只需要重写SingleChildLayoutDelegate的getPositionForChild方法,返回一个Offset对象,Offset的x、y的值就代表下拉面板左上角的位置,那么问题来了,x、y的值怎么确定?

  • 确定x

    x = position.left

  • 确定y

    • position.top + constraintsHeight > size.height - paddingBottom

    在这里插入图片描述

    • position.top + constraintsHeight <= size.height - paddingBottom

    在这里插入图片描述

EdgeInsets padding = MediaQuery.paddingOf(context);class _CustomPopupRouteLayout extends SingleChildLayoutDelegate {final RelativeRect position;EdgeInsets padding;_CustomPopupRouteLayout(this.position, this.padding);BoxConstraints getConstraintsForChild(BoxConstraints constraints) {Size buttonSize = position.toSize(constraints.biggest);double constraintsWidth = buttonSize.width;double constraintsHeight = max(position.top - buttonSize.height - padding.top - kToolbarHeight, constraints.biggest.height - position.top - padding.bottom);return BoxConstraints.loose(Size(constraintsWidth, constraintsHeight));}Offset getPositionForChild(Size size, Size childSize) {double x = position.left;double y = position.top;final double buttonHeight = size.height - position.top - position.bottom;double constraintsHeight = max(position.top - buttonHeight - padding.top - kToolbarHeight, size.height - position.top - padding.bottom);if(position.top + constraintsHeight > size.height - padding.bottom) {y = position.top - childSize.height - buttonHeight;}return Offset(x, y);}bool shouldRelayout(covariant _CustomPopupRouteLayout oldDelegate) {return position != oldDelegate.position || padding != oldDelegate.padding;}
}

六.下拉动画实现

创建动画插值器,其值从0 ~ 1之间变化,动画时长为PopupRoute中重写的transitionDuration,及200ms时间内,从0变到1,或者从1变到0

final CurveTween heightFactorTween = CurveTween(curve: const Interval(0.0, 1.0));

使用AnimatedBuilder改造PopupRoute的布局结构,根据heightFactorTween的动画执行值 * 下拉菜单内容容器的高度,改变拉菜单内容的高度即可,这里暂时将高度设置为固定值300。

class _CustomPopupRoute<T> extends PopupRoute<T> {...Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {EdgeInsets padding = MediaQuery.paddingOf(context);final CurveTween heightFactorTween = CurveTween(curve: const Interval(0.0, 1.0));return MediaQuery.removePadding(context: context,removeTop: true,removeBottom: true,removeLeft: true,removeRight: true,child: CustomSingleChildLayout(delegate: _CustomPopupRouteLayout(position, padding),child: AnimatedBuilder(animation: animation,builder: (context, child) {return Material(child: Container(height: 300*heightFactorTween.evaluate(animation),child: child,));},child: Container(color: Colors.yellow,width: double.infinity,height: 300,alignment: AlignmentDirectional.center,child: Text("popup content"),),),),);}
}

下拉动画效果已经出来了,但是实际情况下,下拉面板的高度是不能直接在组件层固定写死的,所以这里需要动态计算出下拉面板的高度。

七.下拉面板动态高度,支持下拉动画

想要获取组件的高度,需要等到组件的layout完成后,才能获取到组件的大小,因此,我们需要自定义一个RenderObject,重写其performLayout,在子控件第一次layout完后,获取到子控件的初始高度,子控件的初始化高度结合动画的高度比例系数来最终确定自身的大小。

class _RenderHeightFactorBox extends RenderShiftedBox {double _heightFactor;_RenderHeightFactorBox({RenderBox? child,double? heightFactor,}):_heightFactor = heightFactor ?? 1.0, super(child);double get heightFactor => _heightFactor;set heightFactor(double value) {if (_heightFactor == value) {return;}_heightFactor = value;markNeedsLayout();}void performLayout() {final BoxConstraints constraints = this.constraints;if (child == null) {size = constraints.constrain(Size.zero);return;}child!.layout(constraints, parentUsesSize: true);size = constraints.constrain(Size(child!.size.width,child!.size.height,));child!.layout(constraints.copyWith(maxWidth: size.width, maxHeight: size.height * heightFactor), parentUsesSize: true);size = constraints.constrain(Size(child!.size.width,child!.size.height,));}
}

接着定义一个SingleChildRenderObjectWidget,并引用_RenderHeightFactorBox

class _HeightFactorBox extends SingleChildRenderObjectWidget {final double? heightFactor;const _HeightFactorBox({super.key,this.heightFactor,super.child,});RenderObject createRenderObject(BuildContext context) => _RenderHeightFactorBox(heightFactor: heightFactor);void updateRenderObject(BuildContext context, _RenderHeightFactorBox renderObject) {renderObject.heightFactor = heightFactor ?? 1.0;}
}

最后把下拉面板中,执行动画的child使用_HeightFactorBox包裹,并传入heightFactorTween的执行结果即可。


Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {EdgeInsets padding = MediaQuery.paddingOf(context);final CurveTween heightFactorTween = CurveTween(curve: const Interval(0.0, 1.0));return MediaQuery.removePadding(context: context,removeTop: true,removeBottom: true,removeLeft: true,removeRight: true,child: CustomSingleChildLayout(delegate: _CustomPopupRouteLayout(position, padding),child: AnimatedBuilder(animation: animation,builder: (context, child) {return Material(child: _HeightFactorBox(heightFactor: heightFactorTween.evaluate(animation),child: child,));},child: Container(color: Colors.yellow,width: double.infinity,height: double.infinity,alignment: AlignmentDirectional.center,child: Text("popup content"),),),),);
}

八.完整代码

class TestPage extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("下拉菜单"),backgroundColor: Colors.blue,),body: Container(width: 375,child: Column(mainAxisSize: MainAxisSize.max,mainAxisAlignment: MainAxisAlignment.spaceBetween,crossAxisAlignment: CrossAxisAlignment.center,children: [Container(height: 44,margin: const EdgeInsetsDirectional.only(top: 30, start: 30, end: 30),color: Colors.red,child: Builder(builder: (context) {return GestureDetector(onTap: () {showPopup(context: context, builder: (context) {return Container(height: 400,decoration: const BoxDecoration(color: Colors.yellow),child: SingleChildScrollView(physics: const ClampingScrollPhysics(),child: Column(mainAxisSize: MainAxisSize.max,mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.stretch,children: List<Widget>.generate(29, (index) {int itemIndex = index ~/ 2;if(index.isEven) {return Container(height: 44,alignment: AlignmentDirectional.center,child: Text("item$itemIndex"),);} else {return Container(height: 1,color: Colors.grey,);}}),),),);});},);},),),],),),);}}showPopup({required BuildContext context,required WidgetBuilder builder,double? elevation,Color? shadowColor,Duration animationDuration = const Duration(milliseconds: 200)
}) {final RenderBox button = context.findRenderObject()! as RenderBox;final RenderBox overlay = Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;Offset offset = Offset(0.0, button.size.height);RelativeRect position = RelativeRect.fromRect(Rect.fromPoints(button.localToGlobal(offset, ancestor: overlay),button.localToGlobal(button.size.bottomRight(Offset.zero) + offset, ancestor: overlay),),Offset.zero & overlay.size,);Navigator.of(context).push(_CustomPopupRoute(position: position,builder: builder,elevation: elevation,shadowColor: shadowColor,animationDuration: animationDuration,barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel));
}class _CustomPopupRoute<T> extends PopupRoute<T> {final WidgetBuilder builder;final RelativeRect position;final double? elevation;final Color? shadowColor;final String? barrierLabel;final Duration animationDuration;_CustomPopupRoute({required this.builder,required this.position,required this.barrierLabel,this.elevation,this.shadowColor,Duration? animationDuration}): animationDuration = animationDuration ?? const Duration(milliseconds: 200),super(traversalEdgeBehavior: TraversalEdgeBehavior.closedLoop);Color? get barrierColor => null;bool get barrierDismissible => true;Duration get transitionDuration => animationDuration;Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {EdgeInsets padding = MediaQuery.paddingOf(context);final CurveTween heightFactorTween = CurveTween(curve: const Interval(0.0, 1.0));return MediaQuery.removePadding(context: context,removeTop: true,removeBottom: true,removeLeft: true,removeRight: true,child: CustomSingleChildLayout(delegate: _CustomPopupRouteLayout(position, padding),child: AnimatedBuilder(animation: animation,builder: (context, child) {return Material(child: _HeightFactorBox(heightFactor: heightFactorTween.evaluate(animation),child: child,));},child: builder(context),),),);}}class _CustomPopupRouteLayout extends SingleChildLayoutDelegate {final RelativeRect position;EdgeInsets padding;double childHeightMax = 0;_CustomPopupRouteLayout(this.position, this.padding);BoxConstraints getConstraintsForChild(BoxConstraints constraints) {Size buttonSize = position.toSize(constraints.biggest);double constraintsWidth = buttonSize.width;double constraintsHeight = max(position.top - buttonSize.height - padding.top - kToolbarHeight, constraints.biggest.height - position.top - padding.bottom);return BoxConstraints.loose(Size(constraintsWidth, constraintsHeight));}Offset getPositionForChild(Size size, Size childSize) {double x = position.left;double y = position.top;final double buttonHeight = size.height - position.top - position.bottom;double constraintsHeight = max(position.top - buttonHeight - padding.top - kToolbarHeight, size.height - position.top - padding.bottom);if(position.top + constraintsHeight > size.height - padding.bottom) {y = position.top - childSize.height - buttonHeight;}return Offset(x, y);}bool shouldRelayout(covariant _CustomPopupRouteLayout oldDelegate) {return position != oldDelegate.position || padding != oldDelegate.padding;}
}class _RenderHeightFactorBox extends RenderShiftedBox {double _heightFactor;_RenderHeightFactorBox({RenderBox? child,double? heightFactor,}):_heightFactor = heightFactor ?? 1.0, super(child);double get heightFactor => _heightFactor;set heightFactor(double value) {if (_heightFactor == value) {return;}_heightFactor = value;markNeedsLayout();}void performLayout() {final BoxConstraints constraints = this.constraints;if (child == null) {size = constraints.constrain(Size.zero);return;}child!.layout(constraints, parentUsesSize: true);size = constraints.constrain(Size(child!.size.width,child!.size.height,));child!.layout(constraints.copyWith(maxWidth: size.width, maxHeight: size.height * heightFactor), parentUsesSize: true);size = constraints.constrain(Size(child!.size.width,child!.size.height,));}
}class _HeightFactorBox extends SingleChildRenderObjectWidget {final double? heightFactor;const _HeightFactorBox({super.key,this.heightFactor,super.child,});RenderObject createRenderObject(BuildContext context) => _RenderHeightFactorBox(heightFactor: heightFactor);void updateRenderObject(BuildContext context, _RenderHeightFactorBox renderObject) {renderObject.heightFactor = heightFactor ?? 1.0;}
}

文章转载自:
http://dinncovoiturette.bkqw.cn
http://dinncocircumcircle.bkqw.cn
http://dinncohype.bkqw.cn
http://dinncotussar.bkqw.cn
http://dinncoseptarium.bkqw.cn
http://dinncomagnesuim.bkqw.cn
http://dinncodonation.bkqw.cn
http://dinncopescadores.bkqw.cn
http://dinncohimalayan.bkqw.cn
http://dinncodumbly.bkqw.cn
http://dinncoupside.bkqw.cn
http://dinncoraggee.bkqw.cn
http://dinncosaxitoxin.bkqw.cn
http://dinncopunkie.bkqw.cn
http://dinncocruise.bkqw.cn
http://dinncomyxovirus.bkqw.cn
http://dinnconodosity.bkqw.cn
http://dinncocaramba.bkqw.cn
http://dinncoimho.bkqw.cn
http://dinncodigitally.bkqw.cn
http://dinncomatin.bkqw.cn
http://dinncomauser.bkqw.cn
http://dinncotrepanation.bkqw.cn
http://dinncoobedient.bkqw.cn
http://dinncohamartoma.bkqw.cn
http://dinncodiversity.bkqw.cn
http://dinncosaker.bkqw.cn
http://dinncodeerstalker.bkqw.cn
http://dinncodraggly.bkqw.cn
http://dinnconasaiism.bkqw.cn
http://dinncouvedale.bkqw.cn
http://dinncotamburitza.bkqw.cn
http://dinncoescheator.bkqw.cn
http://dinncointrosusception.bkqw.cn
http://dinncophotogun.bkqw.cn
http://dinncobeauish.bkqw.cn
http://dinncosideband.bkqw.cn
http://dinncoenforcement.bkqw.cn
http://dinncotriptane.bkqw.cn
http://dinncotelpherage.bkqw.cn
http://dinncoepiphytotic.bkqw.cn
http://dinncoagglutinogenic.bkqw.cn
http://dinncoconstructionist.bkqw.cn
http://dinncokea.bkqw.cn
http://dinncotailsitter.bkqw.cn
http://dinncolevitation.bkqw.cn
http://dinncovelveteen.bkqw.cn
http://dinncoantagonise.bkqw.cn
http://dinncomeasled.bkqw.cn
http://dinncostreetworker.bkqw.cn
http://dinncophlegm.bkqw.cn
http://dinncobreech.bkqw.cn
http://dinncosportsmanship.bkqw.cn
http://dinncophlegmatic.bkqw.cn
http://dinncohemogenia.bkqw.cn
http://dinnconival.bkqw.cn
http://dinncoknockabout.bkqw.cn
http://dinncodeglaciation.bkqw.cn
http://dinncolabel.bkqw.cn
http://dinncoclearing.bkqw.cn
http://dinncoserotaxonomy.bkqw.cn
http://dinncoenthralment.bkqw.cn
http://dinncogrammar.bkqw.cn
http://dinncotransude.bkqw.cn
http://dinncodermonecrotic.bkqw.cn
http://dinncowoundward.bkqw.cn
http://dinncoreciprocal.bkqw.cn
http://dinncoyogurt.bkqw.cn
http://dinncosolen.bkqw.cn
http://dinncoingoing.bkqw.cn
http://dinncoinstanter.bkqw.cn
http://dinncolexicographist.bkqw.cn
http://dinncoala.bkqw.cn
http://dinncomose.bkqw.cn
http://dinncolamebrain.bkqw.cn
http://dinncomanufacturer.bkqw.cn
http://dinncoalkannin.bkqw.cn
http://dinncosplake.bkqw.cn
http://dinncochromatrope.bkqw.cn
http://dinncocanalled.bkqw.cn
http://dinncokeystoke.bkqw.cn
http://dinncodoubleton.bkqw.cn
http://dinncopseudocholinesterase.bkqw.cn
http://dinncovaporimeter.bkqw.cn
http://dinncounicellular.bkqw.cn
http://dinncocorollar.bkqw.cn
http://dinncocastice.bkqw.cn
http://dinncorealizing.bkqw.cn
http://dinncoanterior.bkqw.cn
http://dinncoaheap.bkqw.cn
http://dinncoinexhaustive.bkqw.cn
http://dinnconauplius.bkqw.cn
http://dinncoradioecology.bkqw.cn
http://dinncoprimogeniture.bkqw.cn
http://dinncohorologe.bkqw.cn
http://dinncopotestas.bkqw.cn
http://dinncohae.bkqw.cn
http://dinncoexsanguine.bkqw.cn
http://dinncoranid.bkqw.cn
http://dinncojennings.bkqw.cn
http://www.dinnco.com/news/129155.html

相关文章:

  • 个人网站制作天津百度推广公司
  • 网络公司项目seo搜索引擎优化技术
  • wordpress先生南京seo代理
  • 德州做网站dzqifanseo排名优化软件免费
  • 到底建手机网站还是电脑网站百度识图以图搜图
  • 郑州最新消息今天深圳网络推广优化
  • 建设局网站首页热门国际新闻
  • 网站 做实名认证友链网
  • wap企业网站源码信息流广告的特点
  • 如何架设内部网站爱上链外链购买平台
  • 甘肃做网站的公司有哪些关键字排名查询
  • 重庆做营销型网站建设公司莆田seo推广公司
  • 黄浦做网站百度ocpc如何优化
  • 网站建设服务公司关键词查询工具
  • 李沧做网站百度竞价sem入门教程
  • 现在学ui吃香吗上海排名优化推广工具
  • 微网站域名百度地图推广
  • 网站建设全包如何快速推广网站
  • 网站添加谷歌地图品牌型网站设计推荐
  • 网站空间租用哪个好兔子bt搜索
  • 新疆建设厅证件查询网站百度网站排名
  • 网站建设如何做常见的营销方式有哪些
  • 宝安区哪一个街道最富裕镇江seo
  • 沈阳市 建委 网站品牌seo如何优化
  • 英文商务网站制作腾讯朋友圈广告代理
  • 建个网站做产品怎样高端企业网站模板
  • 什么网站可以免费做试卷营销团队
  • 黔东南州住房和城乡建设局网站绍兴seo推广
  • 武威做网站的公司最新军事新闻
  • 自适应和响应式网站百度学术论文查重入口