当前位置: 首页 > Web前端 > CSS

Flutter:CSS规则映射flutter控件-position

时间:2023-03-30 15:43:30 CSS

CSS庞大而复杂,灵活而复杂。如何将CSS规则映射到Flutter控件上确实是一个很大的挑战。CSS有那么多的规则和属性,还有各种简写形式。不管怎么说,要实现CSS的所有效果肯定是不可能的,但是能实现到什么程度,哪些部分还需要去实践。CSS是应用于标签的规则。要实现转换,首先要对标签进行解析,即将文本的结构化数据转换为内存的对象数据。html和applets都是这样。准备简单的解析标签问题不大,有现成的html/xml解析库;关键是如何解析css,实现css规则的各种匹配,后代选择器,级联覆盖覆盖等效果是不现实的。幸运的是,网络上有很多功能强大的现成工具供我们选择。我们的最终目标是将具有css属性的单个节点转化为对应的flutter控件,所以我们首先需要获取单个节点的css属性是什么?如果我们直接将css文件中的各种属性转换成内联样式,那么在解析节点的时候就会知道当前节点对应的所有css属性,所以我们需要想办法转换成内联样式的css库或者工具样式。目标明确了,怎么做其实很简单。@team-griffin/css-longhand、css-longhand、css-shorthand-expand、css-shorthand-expanders、fela-plugin-expand-shorthand、grunt-css等工具有很多,随便查一下-longhand,inline-style-expand-shorthand等。最流行的是juice,所以我们解析的是juice转换后的html/xml文件,使用下面的类表示单个节点的CSS:classCSSStyle{final地图<字符串,字符串>_attrs;constCSSStyle(this._attrs);细绳?operator[](Stringkey)=>_attrs[key];双倍的?_getDouble(Stringkey)=>_attrs[key]?.let((it)=>double.tryParse(it));}css的作用CSS不仅可以帮助我们判断当前节点的控件类型,还具有一些额外的padding/margin等控件,将这些属性对应的控件作为父节点放在当前节点的控件上,类似如下:Widgetw=builder.build(element,children);CSSStylecss=...;finalpadding=css.padding;if(padding!=null){w=Padding(padding:padding,child:w,);}finalmargin=css.margin;if(margin!=null){w=Padding(填充:margin,child:w,);}因为flutter的控件非常丰富和强大,其核心思想是充分利用现有控件的组合;当然可以像kraken自带的控件一样画出实体的解决方案,但是需要非常深入的flutter渲染技巧,所以先不实现开始分析.position属性的问题是如何实现position属性。详细的介绍可以参考MDN。它的值主要包括相对的、绝对的、固定的。静态值等于没有设定值。relative属性的含义很容易理解。实际上它是偏移量:finalpositionCSS=css['position'];if(positionCSS=='relative'){finalleft=css._getDouble('left');最后top=css._getDouble('top');finalright=css._getDouble('right');最后底部=css._getDouble('底部');最后dx=左??对吗?.let((it)=>-right);最后dy=top??底部?.let((it)=>-底部);if(dx!=null||dy!=null){w=Transform.translate(offset:Offset(dx??0,你吗??0,),孩子:w,);}}left,top是dx,dy,right,bottom是-dx,-dyabsolute比较好理解,absolute元素不参与兄弟元素布局做好了,就是相对于父节点的偏移量,那么这个布局的效果在Android中是FrameLayout,在flutter中是对应的Stack。理解起来并不难,但在实践中有点棘手。声明为绝对的元素或节点将被忽略。对应什么样的控件?它的父节点首先必须是一个Stack。这是什么意思?这意味着我们在解析一个节点对应的控件时,必须考虑其子节点的属性!这不再是一个对应的问题,是解析问题。使用下面的类来表示html/xml中的一个节点:class_AssembleElement{finalStringname;最终的CSSStyle样式;最后的字符串?类;最终地图<字符串,字符串>?额外的;最终列表<_AssembleElement>子项;_AssembleElement(this.name,this.style,this.klass,this.extra,this.children);@overrideStringtoString(){return'<$namestyle="$style"${extra?.let((it)=>'extra="$extra"')??''}>';}}style是style=""中的内联css属性集合,extra表示节点除了class和style之外其实class可以省略,但是因为做一些调试操作比较方便,所以可以单独取出。所以我们在判断当前节点应该对应哪个控件的地方加入如下逻辑:Widgetbuild(_AssembleElemente,Listchildren){finalalignChildren=e.children.w这里((e)=>e.style['position']=='absolute');如果(alignChildren.length>0){returnStack(children:children,);}...}以Stack为父节点,当前节点对应很容易,当然是在flutter中定位,所以在外围“圈”起来:elseif(positionCSS=='absolute'){finalleft=css._getDouble('左');最后top=css._getDouble('top');finalright=css._getDouble('right');最后底部=css._getDouble('底部');w=Positioned(left:left,top:top,right:right,bottom:bottom,child:w,);}两者结合可以生成正确的控制对象。fixed最麻烦的就是固定值。根据文档描述,元素将被移出正常的文档流,而不是为元素保留,而是通过指定元素相对于屏幕视口的位置来指定元素的位置。通常用在页面上一直显示不滚动的视图中,类似于flutter中的FloatingActionButton,效果其实是Stack的形式,只是需要做不同的处理。因为会从正常的文档流中移除,所以解析节点需要单独放置,并且解析完成后,在根节点外放置一个根节点,可以分为3个步骤:当解析遇到属性声明为position:fixed的节点,单独放置节点(_fixedPosition):children=elementNodes.map((child)=>_fromXml(child,ancestorStyle)).toList();children.removeWhere((e){finalisFixed=e.style['position']=='fixed';我f(isFixed){_fixedPosition.add(e);}返回isFixed;});解析后添加新的根节点:varrootElement=_fromXml(root,null);如果(_fixedPosition.isNotEmpty){最终子节点=[rootElement,..._fixedPosition,];rootElement=_AssembleElement('_floatStack',constCSSStyle({}),null,null,children);_fixedPosition.clear();}在创建整个视图时应用这个特殊的根节点:_assembleWidget表示具体创建单个节点视图的方法,0表示depth.Widgetbuild(BuildContextcontext,_AssembleElementroot){if(root.name=='_floatStack'){finalchildren=root.children.map((e)=>_assembleWidget(e,0)).toList(growable:false);返回堆栈(儿童:儿童,);}return_assembleWidget(root,0);}当前节点的生成逻辑其实和absolute一样:elseif(positionCSS=='absolute'||positionCSS=='fixed'){...}这样,我们就可以比较完整的实现CSS的position属性的作用了!归根结底,转化为flutter控件的关键是要清晰Semantics,实现keypresentation,然后走fundamentals,摒弃一些secondaryandmarginaleffects。