背景:
在移动APP开发中开发者最不愿意花时间却要花很多时间来适配各种各样的机型屏幕尺寸,本人经历过纯手动frame布局,利用自动布局框架masonry编写界面控件自动布局,如今入手flexbox来布局控件,谈谈我个人的感受以及研读FlexLib框架核心源码的心得。
001 个人感受
一个牛逼的产品应该是这样大部分用户使用后不错并且自愿分享给大家,自发去完善它。这里介绍一款微软出品的VS Code代码编写IDE,通过配置就可以让其支持JS XML 等语法的提示,用上了VSCode 你会感觉自此在编码的道路上会提高效率 行云入水 平步青云。下面进入正题个人感受,flexbox布局使用的是XML语言来编写,XML语言可是在编程语言中老资格,现在依旧活跃在各大平台,就好比四大天王一样。总之一句话一段代码就可以将一个控件的名字 属性 布局都搞定。这里需要强调一点flexbox是利用可拓展容器的机制来防止控件排布,一个大容器中存在许多子容器(视图),这些子容器里的子控件排布自己设定。怎么理解这句话?在一个公司里大家在同一个项目组,项目组里又分成iOS 安卓 前端 后端 UI等小组,小组里面又分成组长和组员,组长负责协调组员之间的任务安排。如果这样还不理解 咱们换个说法 校长管年级组长年级组长管班主任班主任管学生。
002 研读心得
越是简单的操作其背后往往是心血。从一个布局XML文件讲起再过渡到核心FlexNode类。
<UIView layout="flex:1" attr="">
<UIView layout="" attr="">
<UILabel name="titleLab" layout="margin:10" attr="font:bold|18,color:#333333,linesNum:0,text:"/>
<UILabel name="contentLab" layout="margin:10,marginTop:0" attr="fontSize:15,color:#333333,linesNum:0,text:"/>
</UIView>
<UIView name="backV" layout="flex:1" attr="">
</UIView>
<UIView layout="flexDirection:row,margin:10" attr="">
<UILabel name="areaLab" layout="" attr="fontSize:12,color:#333333,linesNum:0,text:"/>
<UILabel name="commonLab" layout="marginLeft:3" attr="fontSize:12,color:#333333,linesNum:0,text:"/>
<UILabel name="timeLab" layout="marginLeft:3" attr="fontSize:12,color:#333333,linesNum:0,text:"/>
</UIView>
</UIView>
这个这段代码所实现的是一个类似于朋友圈图文混排的视图,这里有三个子容器分别
如果子容器的layout没有写内容那么在它里面的子容器是垂直排布,如果需要水平排布那么需要增加一个标识符flexDirection:row,那么其中的控件就是水平排布。
那么这些内容是如何生成控件的呢?下面就来揭晓
流程:解析该XML文件,获得flexNode对象核心代码
self.flexNode = [FlexNode loadNodeFromRes:NSStringFromClass(self.class) Owner:self];
XML首次加载之后就存放在缓存中便于快速获取。Flexnode 设置布局属性,设置图形属性,创建子视图。
代码如下:
// 设置布局属性
[self configureLayoutWithBlock:^(YGLayout* layout){
layout.isEnabled = YES;
NSArray<FlexAttr*>* layoutParam = node.layoutParams ;
for (FlexAttr* attr in layoutParam) {
if([attr.name compare:@"@" options:NSLiteralSearch]==NSOrderedSame){
NSArray* ary = [[FlexStyleMgr instance]getStyleByRefPath:attr.value];
for(FlexAttr* styleAttr in ary)
{
FlexApplyLayoutParam(layout, styleAttr.name, styleAttr.value);
}
}else{
FlexApplyLayoutParam(layout, attr.name, attr.value);
}
}
}];
// 设置视图属性
if(node.viewAttrs.count > 0){
NSArray<FlexAttr*>* attrParam = node.viewAttrs ;
for (FlexAttr* attr in attrParam) {
if([attr.name compare:@"@" options:NSLiteralSearch]==NSOrderedSame){
NSArray* ary = [[FlexStyleMgr instance]getStyleByRefPath:attr.value];
for(FlexAttr* styleAttr in ary)
{
FlexSetViewAttr(self, styleAttr.name, styleAttr.value,self);
}
}else{
FlexSetViewAttr(self, attr.name, attr.value,self);
}
}
}
// 创建子view
for (FlexNode* subnode in node.children) {
UIView* child = [subnode buildViewTree:self
RootView:rootView];
if(child!=nil && ![child isKindOfClass:[FlexModalView class]])
{
[self addSubview:child];
}
}
FlexNode类包含该控件类名,该控件的名子,触发方法,布局属性组,视图属性组,,子视图。接下来构建一整个视图
生成该视图的类->依据名字生成一个视图对象->生成属性组名字
绑定变量->增加tap手势建立触发方法->配置布局->子视图增加到该视图中->将该视图注册到其rootview中去->最后返回该视图
代码如下:
-(UIView*)buildViewTree:(NSObject*)owner
RootView:(FlexRootView*)rootView
{
if( self.viewClassName==nil){
return nil;
}
Class cls = NSClassFromString(self.viewClassName) ;
if(cls == nil){
NSLog(@"Flexbox: class %@ not found.", self.viewClassName);
return nil;
}
if(![cls isSubclassOfClass:[UIView class]]){
NSLog(@"Flexbox: %@ is not child class of UIView.", self.viewClassName);
return nil;
}
UIView* view = [owner createView:cls Name:self.name];
if(view == nil)
{
@try{
view = [[cls alloc]init];
if(view == nil)
{
NSLog(@"Flexbox: Class %@ init return nil",cls);
return nil;
}
[view afterInit:owner rootView:rootView];
}@catch(NSException* exception){
NSLog(@"Flexbox: Class %@ init failed - %@",cls,exception);
return nil;
}
}
if(self.name.length>0){
@try{
view.viewAttrs.name = self.name ;
if([owner needBindVariable]){
[owner setValue:view forKey:self.name];
}
}@catch(NSException* exception){
NSLog(@"Flexbox: name %@ not found in owner %@",self.name,[owner class]);
}@finally
{
}
}
if(self.onPress.length>0){
SEL sel = NSSelectorFromString(self.onPress);
if(sel!=nil){
if([owner respondsToSelector:sel]){
UITapGestureRecognizer *tap=[[UITapGestureRecognizer alloc]initWithTarget:owner action:sel];
tap.cancelsTouchesInView = NO;
tap.delaysTouchesBegan = NO;
[view addGestureRecognizer:tap];
}else{
NSLog(@"Flexbox: owner %@ not respond %@", [owner class] , self.onPress);
}
}else{
NSLog(@"Flexbox: wrong onPress name %@", self.onPress);
}
}
[view configureLayoutWithBlock:^(YGLayout* layout){
layout.isEnabled = YES;
NSArray<FlexAttr*>* layoutParam = self.layoutParams ;
for (FlexAttr* attr in layoutParam) {
if([attr.name compare:@"@" options:NSLiteralSearch]==NSOrderedSame){
NSArray* ary = [[FlexStyleMgr instance]getStyleByRefPath:attr.value];
for(FlexAttr* styleAttr in ary)
{
FlexApplyLayoutParam(layout, styleAttr.name, styleAttr.value);
}
}else{
FlexApplyLayoutParam(layout, attr.name, attr.value);
}
}
}];
if(self.viewAttrs.count > 0){
NSArray<FlexAttr*>* attrParam = self.viewAttrs ;
for (FlexAttr* attr in attrParam) {
if([attr.name compare:@"@" options:NSLiteralSearch]==NSOrderedSame){
NSArray* ary = [[FlexStyleMgr instance]getStyleByRefPath:attr.value];
for(FlexAttr* styleAttr in ary)
{
FlexSetViewAttr(view, styleAttr.name, styleAttr.value,owner);
}
}else{
FlexSetViewAttr(view, attr.name, attr.value,owner);
}
}
}
if(self.children.count > 0 &&
![view buildChildElements:self.children Owner:owner RootView:rootView]){
NSArray* children = self.children ;
for(FlexNode* node in children){
UIView* child = [node buildViewTree:owner RootView:rootView] ;
if(child!=nil && ![child isKindOfClass:[FlexModalView class]])
{
[view addSubview:child];
}
}
}
if(![view isKindOfClass:[FlexModalView class]]){
[rootView registSubView:view];
}else{
[(FlexModalView*)view setOwnerRootView:rootView];
}
[view postCreate];
[owner postCreateView:view];
if(view.isHidden){
view.yoga.isIncludedInLayout = NO ;
}
return view;
}
补充一下FLexbox热加载
在delegate中增加这段代码
FlexRestorePreviewSetting(); //Debug模式下可以热更新UI,仅限于xml文件部分的UI,原生部分的UI刷新还是需要编译运行。
001首选打开终端切换到项目中所有XML文件夹下
002 使用命令python -m SimpleHTTPServer
003 在模拟器中配置预览URL连接:http://192.168.101.206.8000
打开vs code 就可以直接修改控件布局 然后在模拟器中实时看到UI的变化
cd /Users/userName/Desktop/XML
python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
192.168.101.206 - - [31/Jul/2019 17:44:54] "GET /Name.xml HTTP/1.1" 200 -
003 总结
flexbox的确让开发者节省很多时间,一段代码就可以实现控件的布局,并且还支持热加载,只需要改变布局文件就可以不用重启xcode就可以看得到布局变化这样就可以节省开发与UI之间条界面的时间,缩短项目时间,能够及时交付。
参考文献: