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

烟台网站排行榜世界500强企业排名表

烟台网站排行榜,世界500强企业排名表,深圳龙华建设工程交易中心网站,全国招商代理项目背景Flutter作为全新跨平台应用框架#xff0c;在页面渲染和MD开发上优势明显#xff0c;可谓是业界一枝独秀。正好最近有这样的一个机会学习Flutter开发#xff0c;我们便尝试用它开发一个MD风格的较复杂页面#xff0c;来比较跟原生应用开发的优势。也是想通过对新框架的… 背景Flutter作为全新跨平台应用框架在页面渲染和MD开发上优势明显可谓是业界一枝独秀。正好最近有这样的一个机会学习Flutter开发我们便尝试用它开发一个MD风格的较复杂页面来比较跟原生应用开发的优势。也是想通过对新框架的学习探索找到适合自身应用的框架。页面展示首页是整个应用里边交互最为复杂的一个页面了它集合了各种滑动方式包括纵向滑动、横向滑动、嵌套滑动同时也集合了各种动效包括下拉刷新、上拉加载、头图视差、二级吸顶、回到顶部、横向Banner和纵向News轮播等。开发历程搭建了开发环境新建flutter module并学习dart语法调研用Flutter实现CoordinatorLayout的方案实现了首页主框架的demo搭建目前同样遇到了滑动冲突的问题在调研解决方案解决了滑动冲突的问题并集成了下拉刷新能力完成了各区块和feed流的静态UI内容目前剩余feed流加载更多和负二楼动效实现首页feed流的加载更多功能技术难点两级吸顶在Flutter中实现吸顶功能比较容易使用SliverPersistentHeader控件或者间接使用该控件都可以满足吸顶的功能更重要的是它支持滑动过程中任意组件的吸顶即多级吸顶功能。既然多级吸顶都支持那么两级吸顶就很轻松了首页头部和feed流tab的两级吸顶是这样实现的第一级使用SliverAppBar(它内部就是一个SliverPersistentHeader控件)不仅可以吸顶还带有折叠属性折叠属性能更好的满足头部滑动时的动效处理第二级使用SliverPersistentHeader并自定义它的delegate通过pinned属性灵活选择当前模块吸顶与否这样可以实现任意组件的吸顶功能。SliverAppBar(pinned: true,...,bottom: PreferredSize(child: Header(...),preferredSize: Size(screenWidth, 15),),),SliverPersistentHeader(pinned: false,delegate: _SliverColumnDelegate(Column(...),)),SliverPersistentHeader(pinned: true,delegate: _SliverTabBarDelegate(TabBar(...)),),pinned的原理很简单将它设置为true内容到达顶部后不会再跟随外层的ScrollView继续滚动反之内容则会滚动出容器外。而native端实现这个二级吸顶却很费力通常你可能需要事先隐藏一个跟吸顶内容一样的驻顶view在那里然后在页面滚动时计算吸顶内容是否已经划至顶部维护驻顶view的可见属性达到吸顶效果。上面粗犷的两级吸顶完成了但想要充分满足首页的折叠效果和准确的二级吸顶需求还得深挖一下AppBar内部的折叠计算方法。SliverAppBar折叠计算SliverAppBar通常作为页面头部使用是会随内容一起滑动的一个组件它的构造方法中有四个Widget类型的参数。分析Widget类型的参数是因为我们需要一个容器来满足自定义首页头部——它既能实现吸顶又可以接入自定义组件。leading // 左侧按钮title // 标题flexibleSpace // 可以展开区域bottom // 底部内容区域回顾一下首页的折叠展示效果首先排除了leading因为它的位置大小只是一个按钮的位置显示比较局限然后title受leading占位影响宽度有限制也无法满足需要之后就剩下两个参数可选了从命名上看感觉flexibleSpace更符合折叠效果的实现思路然后一直在尝试使用其实现头部折叠的需求但开发过程中发现折叠后的高度是无法达到预期的最大高度也满足不了设计图给的高度。本来想直接排除法使用起bottom的但是想到一遇到问题就绕过还是有点SUI。那么想知道为什么flexibleSpace会有高度限制必然得看一下SliverAppBar的实现源码了。class _SliverAppBarState extends StateSliverAppBar with TickerProviderStateMixin {...overrideWidget build(BuildContext context) {assert(!widget.primary || debugCheckHasMediaQuery(context));final double topPadding widget.primary ? MediaQuery.of(context).padding.top : 0.0;final double collapsedHeight (widget.pinned widget.floating widget.bottom ! null)? widget.bottom.preferredSize.height topPadding : null;return MediaQuery.removePadding(context: context,removeBottom: true,child: SliverPersistentHeader(floating: widget.floating,pinned: widget.pinned,delegate: _SliverAppBarDelegate(...collapsedHeight: collapsedHeight,topPadding: topPadding,),),);}}final double collapsedHeight (widget.pinned widget.floating widget.bottom ! null) ? widget.bottom.preferredSize.height topPadding : null;变量collapsedHeight代表了折叠后头部的高度从它的计算表达式可看出当widget.bottom null的时候collapsedHeight的值为null。换言之如果不使用bottom那么折叠高度是没有的。如果没有折叠后的高度会发生什么这个需要进一步验证。const double kToolbarHeight 56.0;class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {...final double _bottomHeight bottom?.preferredSize?.height ?? 0.0;overridedouble get minExtent collapsedHeight ?? (topPadding kToolbarHeight _bottomHeight);}从上面的源码看如果collapsedHeight null那么折叠后的头部高度就是topPadding kToolbarHeight了。topPadding是系统状态栏的高度kToolbarHeight是个自定义常量。不难看出bottom为空时折叠头部的高度就会是一个固定高度。那么反过来想要自定义高度就必须得使用bottom折叠后的头部高度完全取决于bottom的高度(一般系统状态栏的高度是确定的)。你看不是我们用排除法用了最后一个参数bottom而是我们分析后知道不用它真得不行。实现两级吸顶并明确了头部参数设置后其实整个页面框架就基本拟定了。接下来我们细化一下看看头部控件具体怎么实现。自定义头部首页头部组件包括以下内容搜索栏和城市名吸顶头图视差基于之前首页native的开发经验这些效果的实现其实可以由一个变量驱动完成即首页头部的纵向滑动偏移值。这个偏移值参照它的初始位置分为上偏移和下偏移。上偏移驱动处理搜索栏和城市名的动效下偏移则驱动处理头图视差的动效。通过自定义Header组件来处理搜索栏和城市名吸顶的动画其中主要是借助外部传入的上偏移值驱动整个动画的完成。import package:flutter/material.dart;import package:wuba_flutter_lib/home/search_bar.dart;const double SEARCH_MARGIN_LEFT 15.0; // 搜索栏left居左位置typedef OnOffsetChangeListener Function(double percent);class Header extends StatefulWidget {Header({Key key,this.offset: 0.0,// 外部驱动的偏移属性this.cityName,this.onOffsetChangeListener,}) : super(key: key);final double offset;final String cityName;final OnOffsetChangeListener onOffsetChangeListener;double searchLeft SEARCH_MARGIN_LEFT;double searchLeftEnd SEARCH_MARGIN_LEFT;overrideStateStatefulWidget createState() HeaderState();}class HeaderState extends StateHeader with TickerProviderStateMixin {AnimationController searchBgColorAnimController;AnimationColor searchBgColor;// 偏移值驱动动画属性drive(offset) {// 过渡比例double percent offset / 80.0 1.0 ? 1.0 : offset / 80.0;// 偏移比例回调if (widget.onOffsetChangeListener ! null) {widget.onOffsetChangeListener(percent);}// 搜索栏居左吸顶后的位置widget.searchLeftEnd SEARCH_MARGIN_LEFT (widget.cityName ?? ).length * 22 CITY_MARGIN_RIGHT;// 搜索栏居左位置widget.searchLeft (SEARCH_MARGIN_LEFT (widget.searchLeftEnd - SEARCH_MARGIN_LEFT) * percent);// 背景颜色控制searchBgColorAnimController.value percent;}overridevoid didUpdateWidget(Header oldWidget) {super.didUpdateWidget(oldWidget);if (widget.offset ! oldWidget.offset) {drive(widget.offset);}}overridevoid initState() {super.initState();searchBgColorAnimController new AnimationController(vsync: this);searchBgColor ColorTween(begin: Color(0xffffffff),end: Color(0xffDADDE1),).animate(CurvedAnimation(parent: searchBgColorAnimController,curve: Interval(0.0, 1.0, curve: Curves.linear),),);}overrideWidget build(BuildContext context) {return Stack(overflow: Overflow.visible,children: Widget[// 搜索栏SearchBar(left: widget.searchLeft,bgColor: searchBgColor.value,...),...],);}}头图视差 则使用了Container的矩阵变换属性主要是对y轴进行位移这个位移加以视差系数便能产生跟Header组件的视差效果。// 矩阵Matrix4 matrix Matrix4.translationValues(0.0, _offset/*驱动y轴偏移*/, 0.0);// 容器Container(transform: matrix,// 矩阵变换width: screenWidth,height: screenWidth,child: Image.asset(assets/images/home_bg.jpg, fit: BoxFit.fill)),组件化思考Flutter中分无状态组件StatelessWidget和有状态组件StatefulWidgetReact中分无状态组件Stateless Component和高级组件Stateful Component它们在组件化方面的设计思路是一样的。组件化 越来越趋向于按状态划分设计因为这样更贴合实际场景并满足需要。比如首页的区块列表场景中有一些区块一旦设置后不会再发生状态改变可理解为无状态的而另有一些区块初始化后还需要做状态变更它有了状态可视为有状态的。无状态的区块和有状态的区块进行组件封装便成了无状态组件和有状态组件。首页区块中无状态的组件主要包括BigGroup大类页SmallGroup小类页LocalNews同城头条LocalTribe同城部落BannerAd广告Banner…有状态的组件目前只有一个Notification通知提醒没有下发通知链接或者请求后台后发现没有通知内容时需要隐藏如此按照首页区块的场景我们便基于无状态组件设计封装了首页无状态组件类HomeStatelessWidget而基于有状态组件实现了首页有状态组件HomeStatefulWidget。HomeStatelessWidget类封装内部设有一个容器然后需要指定它的大小仅此而已。abstract class HomeStatelessWidgetT extends StatelessWidget implements PreferredSizeWidget {HomeStatelessWidget(this.context, this.key, this.value);final BuildContext context;final String key;final T value;Widget get child;double get height;overrideSize get preferredSize {// 指定容器大小return Size(MediaQuery.of(context).size.width, height);}overrideWidget build(BuildContext context) {return Container(// 容器width: preferredSize.width,height: preferredSize.height,color: Colors.white,alignment: Alignment.centerLeft,child: child,);}}HomeStatefulWidget类封装和HomeStatelessWidget类近似只是多了一个状态类HomeStatefulWidgetState它用于管理组件的各种状态。abstract class HomeStatefulWidgetT extends StatefulWidget implements PreferredSizeWidget {HomeSizeStatefulWidget(this.context, this.key, this.value);final BuildContext context;final String key;final T value;Widget get child;double get height;void initState(State state) {}overrideSize get preferredSize {return Size(MediaQuery.of(context).size.width, height);}overrideStateStatefulWidget createState() HomeStatefulWidgetState();}// 状态类class HomeStatefulWidgetState extends StateHomeStatefulWidget {overridevoid initState() {super.initState();widget.initState(this);}overrideWidget build(BuildContext context) {return Container(width: widget.preferredSize.width,height: widget.preferredSize.height,color: Colors.white,alignment: Alignment.centerLeft,child: widget.child,);}}无状态组件实现起来很容易只需要给它一次性赋值就可以了这里不做过多解释。接下来看看有状态的组件是如何实现的通知提醒组件因为需要改变可见性状态所以要实现首页有状态的组件类HomeStatefulWidget才能满足状态的管理如下是通知提醒组件的代码实现。这一点跟native相比优势还是很明显的。因为native端在view的设计上没有“状态”这个概念它对状态的概念完全是模糊的。class Notification extends HomeStatefulWidgetString {// 状态字段当通知内容为空时控制当前组件是否可见bool isContentEmpty true;Notification(BuildContext context, String key, String value) : super(context, key, value);overridevoid initState(StateStatefulWidget state) {super.initState(state);// 如果url不为空则请求通知接口数据if (!isUrlEmpty()) {HomeDataManager.getNotification(value).then((object) {// 获取到通知数据改变组件的可见性状态state.setState(() {isContentEmpty object null;});});}}overrideWidget get child isUrlEmpty() || isContentEmpty ? Container() : Center(child: Text(value));/// 如果url为空或是通知接口返回的内容为空则隐藏自己/// 否则显示自己。overridedouble get height isUrlEmpty() || isContentEmpty ? 0 : 40; // 判断传入的url是否为空bool isUrlEmpty() (value null || value);}滑动冲突Android中只要两个“轮子”有嵌套关系那么势必存在滑动冲突的问题。要解决嵌套滑动冲突就只能允许一个轮子驱动而另一个轮子被带动而不是两个轮子同时驱动首页中存在两级冲突问题也就是说有两层嵌套关系。一下拉刷新和首页主体二首页主体和feed流内容。这相当于有三个轮子存在相互嵌套的关联如何解决三个轮子的滑动冲突问题这里有三种思路由一个轮子驱动另外两个轮子同步被带动由一个轮子驱动另一个轮子被带动还有一个轮子卸载由一个轮子先驱动到达某个位置后转换为另一个轮子驱动然后剩下的两个轮子跟1和2情况。三种思路其实都是将三个轮子的嵌套关系进行了降维处理本质上都在解决两个轮子的冲突问题总之核心思想是不能出现两个轮子同时驱动。NestedScrollViewRefreshIndicator(// 下拉刷新child: ExtendedNestedScrollView(// 首页主体keepOnlyOneInnerNestedScrollPositionActive: true,headerSliverBuilder: (c, f) {return Widget[SliverAppBar(), // 头部搜索SliverPersistentHeader(),// 区块列表SliverPersistentHeader(),// feed流TabBar];},body: TabBarView(// feed流内容children: [Container(child: ListView(),// 推荐), Container(child: ListView(),// 家乡), Container(child: ListView(),// 部落 ), ],),),)首页主体控件使用了NestedScrollView的扩展类ExtendedNestedScrollView前者允许嵌套滚动但是对子视图的高度有要求——确定的高度。做过feed流的开发都知道它的高度并不好计算因为模板类型不同对应各自的高度不等加以本身又可以无限加载扩展高度一直在变化计算起来难度很大。基于NestedScrollView的扩展类ExtendedNestedScrollView解决了这个痛点在不依赖子视图高度的情况下同样能够满足嵌套滚动。解决滑动冲突问题离不开它的这个重要属性keepOnlyOneInnerNestedScrollPositionActive直译是仅保活一个内部嵌套的滚动位置意译便是仅允许一个内部嵌套的视图滚动即仅允许一个轮子驱动。if (widget.keepOnlyOneInnerNestedScrollPositionActive) {///get notifications and compute active one in _innerController.nestedPositionsbody NotificationListenerScrollNotification(onNotification: (ScrollNotification notification) {if (((notification is ScrollEndNotification) ||(notification is UserScrollNotification notification.direction ScrollDirection.idle)) notification.metrics is PageMetrics notification.metrics.axis Axis.horizontal) {_coordinator._innerController._computeActivatedNestedPosition(notification);}return false;},child: body);}这里的条件判断计算其实已经能看出来了它是实现了上面思路3的做法。此时首页的两级嵌套滚动冲突解决方案其实已经浮出水面了只剩下最后一个轮子的处理了具体是使用情况1还是情况2呢?ScrollController _scrollController ScrollController();_scrollListener() {setState(() {_offset _scrollController.offset;});}overridevoid initState() {super.initState();_scrollController.addListener(_scrollListener);}NestedScrollViewRefreshIndicator(// 下拉刷新// _offset 0.0 表示头部上移动这时候禁止notification事件处理notificationPredicate: (notification) _offset 0.0,child: ExtendedNestedScrollView(// 首页主体controller: _scrollController,keepOnlyOneInnerNestedScrollPositionActive: true,...),)这个问题其实不是一个单选具体在应用场景中最终两者都有用到。下滑到达顶部此时需要触发下拉刷新操作随即下拉刷新模块被带动那么就实现了思路1的做法而其他位置的滑动则不会触发这个操作所以可以理解为将其暂时卸载那么就有了思路2的做法。整体首页的实现其实是综合应用了这三种思路。下拉刷新下拉高度限制负二楼 // TODO默认的下拉刷新组件在下拉时可以一直往下没有对滑动距离做限制而首页要求下拉至头图完整出现后不再滚动。这个特定的需求RefreshIndicator并不能满足需要改动一下这个组件才可以。class NestedScrollViewRefreshIndicator extends StatefulWidget {final OnOffsetCallback onOffset;// 下拉偏移量回调final double offsetLimit;// 下拉偏移的限制值const NestedScrollViewRefreshIndicator({this.onOffset,this.offsetLimit 0.0,...});}class NestedScrollViewRefreshIndicatorStateextends StateNestedScrollViewRefreshIndicatorwith TickerProviderStateMixinNestedScrollViewRefreshIndicator {AnimationController _positionController;AnimationController _scaleController;AnimationRelativeRect _positionRect;AnimationRelativeRect _positionRectDown;AnimationRelativeRect _positionRectUp;Animatable _headerPositionTweenDown;Animatable _headerPositionTweenUp;double _headerOffset 0.0;// 头部偏移值overridevoid initState() {super.initState();_headerPositionTweenDown RelativeRectTween(begin: RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),end: RelativeRect.fromLTRB(0.0, widget.offsetLimit, 0.0, 0.0));_positionController AnimationController(vsync: this);_scaleController AnimationController(vsync: this);_positionRectDown _positionController.drive(_headerPositionTweenDown);_positionRect _mode ! _RefreshIndicatorMode.done ? _positionRectDown : _positionRectUp;if (widget.onOffset ! null) {_positionController.addListener(() {_headerOffset _positionController.value;widget.onOffset(_headerOffset);double value widget.offsetLimit * _headerOffset;_headerPositionTweenUp RelativeRectTween(begin: RelativeRect.fromLTRB(0.0, value, 0.0, 0.0),end: RelativeRect.fromLTRB(0.0, 0, 0.0, 0.0));_positionRectUp _scaleController.drive(_headerPositionTweenUp);});_scaleController.addListener(() {double value (1.0 - _scaleController.value) * _headerOffset;widget.onOffset(value);});}}setPositionRect(newMode) {setState(() {_positionRect newMode ! _RefreshIndicatorMode.done ? _positionRectDown : _positionRectUp;});}// _RefreshIndicatorMode.dragbool _handleScrollNotification(ScrollNotification notification) {...setPositionRect(_RefreshIndicatorMode.drag);return false;}// _RefreshIndicatorMode.canceled || _RefreshIndicatorMode.doneFuturevoid _dismiss(_RefreshIndicatorMode newMode) async {...setPositionRect(newMode);switch (newMode) {case _RefreshIndicatorMode.done:await _scaleController.animateTo(1.0, duration: _kIndicatorScaleDuration);break;case _RefreshIndicatorMode.canceled:await _positionController.animateTo(0.0, duration: _kIndicatorScaleDuration);break;default:assert(false);}}// _RefreshIndicatorMode.refreshvoid _show() {..._positionController.animateTo(1.0 / _kDragSizeFactorLimit, duration: _kIndicatorSnapDuration).thenvoid((void value) {setPositionRect(_RefreshIndicatorMode.refresh);});}overrideWidget build(BuildContext context) {return Stack(children: Widget[// child, // 改动前PositionedTransition(// 改动后rect: _positionRect,child: child,),...],);}}以上便是摘出的改动了下拉刷新控件的代码逻辑主要是通过位置动画限定了首页主体向下滚动的最大位移。同时通过动画偏移的计算向外输出了头部偏移的值以便于外部通过监听这个偏移值做更多的动效处理比如搜索框、天气、城市、头部、扫码、背景图等头部元素的动画处理。负二楼的效果实现其实并不复杂能理解如何通过动画原理改动下拉刷新控件从而实现个性化的动效那么实现负二楼的效果就是个举一反三的事情。加载更多加载更多的原理其实跟native的思路是一样的——判断列表滚动到最末位置触发特定事件。之前native的做法就是判断RecyclerView滑动到最后一项时向feed流最末位置插入一个特定的动画模板等加载结束后再把这个模板去掉然后把请求到的内容添加到视图列表中去这样列表组件就拥有了一个加载更多的能力。ExtendedNestedScrollView的改动double nestOffset(double value, _NestedScrollPosition target) {// 滑动到小于50的时候触发加载更多事件if (target.extentAfter 50) {_onLoadMore();}}总结这样一个由Flutter开发的首页就已经基本落地了。整个开发过程总结下来有这样几点可以分享用Flutter和Android开发首页都依赖了MD组件它们对此支持得都比较完善由于Dart语言的特性Flutter在使用这些组件时更容易扩展、灵活性更强。Flutter状态化的组件管理机制显得比Android更切合场景在区块列表的设计上得心应手这点也是众多前端框架的亮点。Flutter的动画设计api很丰富能充分满足各种UI动效让页面开发更轻松且不复杂。Flutter表达性更强又加以跨平台的解决方案减少了代码量并大大提升了开发效率为应用开发起到了开源节流的作用。Flutter作为新秀在Java老大哥已经烂熟于MVP等模式设计后Flutter在此方面还需要积累也可能Flutter本身并不需要这样的积累它等待的是比Java中更好的开发模式。参考文档Flutter扩展NestedScrollView
http://www.zqtcl.cn/news/779434/

相关文章:

  • 个人网站建设价格网站做视频转流量
  • 点网站出图片怎么做深圳市中心在哪
  • 企业网站建设58同城网站优化排名软件哪些最好
  • 最专业企业营销型网站建设企业宣传海报设计制作
  • 石家庄建站公司软件开发岗位介绍
  • 网站开发知识视频教程公司网站总感觉少点什么找什么人做
  • 做网站ps建立多大的画布网站排名监控工具
  • 烟台网站开发网站建设横幅标语
  • 微信公众号素材网站在线资源链接
  • 网站开发地图板块浮动国际重大新闻事件10条
  • 成品网站app开发wordpress宽度调整
  • 小型网站建设需要多少钱网站发布内容是否过滤
  • 网站如何推广运营漳平网站编辑价格
  • 海洋优质的网站建设企业微信下载官方网站
  • 十大免费ae模板网站wordpress 远程设置
  • 青岛网站的优化云南抖音推广
  • 做中英文版的网站需要注意什么如何偷别人dedecms网站的模板
  • 免费微网站制作最近三天发生的重要新闻
  • 网站优化网络推广seo编程软件python
  • 建设部网站官网合同免费申请网站永久
  • 遵化建设局网站哈尔滨网站制作公司价格
  • 科技因子网站建设方案河南网站推广优化公司
  • 什么网站了解国家建设的行情如何建设自己的php网站
  • 大连市平台网站外包公司和劳务派遣
  • 广州建网站公司排名嵌入式软件开发工程师工作内容
  • 计算机软件网站建设免费asp网站源码
  • 网站建设介绍ppt镇江网站搜索引擎优化
  • 珠海自助建站软件泉州网站开发
  • ios个人开发者账号多少钱拼多多seo怎么优化
  • 五金网站建设信息产业部备案网站