自定义

gic提供了强大的自定义能力,而且自定义也非常的简单你可以,gic提供了如下的自定义能力:

  1. 自定义元素。包括UI元素、Panel
  2. 对已有元素进行属性扩展。
  3. 自定义behavior
  4. 自定义动画
  5. 自定义事件
  6. 自定义指令

通过以上的自定义能力,可以不夸张的说,你可以实现任何你想要的功能。事实上,如果是对已有的项目进行改造,自定义过程其实是一个简单对已有功能封装的过程。

自定义元素

其实只要是在XML中能写出来的,都属于元素,也就是说你可以自定义任何的元素。这里举一个自定义一个switch-button元素的例子。switch-button属于UI元素,而在gic中,所有的UI元素都必须是继承自ASDisplayNode的,这是因为gic的布局系统是基于Texture开发的,那么UI势必也要是ASDisplayNode了。

因此首先是创建一个继承自ASDisplayNode的类,命名为SwitchButton,然后是实现gic_elementName方法,返回元素名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@interface SwitchButton : ASDisplayNode<GICLayoutElementProtocol>
@end

@implementation SwitchButton
+(NSString *)gic_elementName{
return @"switch-button";
}
-(id)init{
self = [super init];
__weak typeof (self) wself = self;
[self setViewBlock:^UIView * _Nonnull{
UISwitch *swicth= [[UISwitch alloc] init];
[swicth sizeToFit];
wself.style.width = ASDimensionMake(swicth.frame.size.width);
wself.style.height = ASDimensionMake(swicth.frame.size.height);
return swicth;
}];
self.backgroundColor = [UIColor clearColor];
return self;
}
@end

这里面注意init方法,在init方法里面调用了setViewBlock的方法,在block里面创建一个UISwitch并且初始化自身的高宽。其实这完全是ASDisplayNode的一套规则,具体的可以参考如下链接。

ASDisplayNode 参考

下一步就是将该元素注册到元素池里面,

1
2
// 注册自定义元素
[GICElementsCache registElement:[SwitchButton class]];

然后就可以直接在xml中使用了。

1
<switch-button />

但是仅仅是这样话,就没意思了。因为点击了又不会产生数据上的交互,因此我们需要添加一个属性,可以用来绑定数据。这里面就添加一个checked的属性,用来表示这个是否打开。另外switch-button本身自己就可以改变是否选中的状态,因此我们需要对这个属性做双向绑定的支持,以便在用户点击了按钮以后将是否选中的状态反向更新到数据源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@implementation SwitchButton
+(NSString *)gic_elementName{
return @"switch-button";
}

+(NSDictionary<NSString *,GICAttributeValueConverter *> *)gic_elementAttributs{
return @{
@"checked":[[GICBoolConverter alloc] initWithPropertySetter:^(NSObject *target, id value) {
dispatch_async(dispatch_get_main_queue(), ^{
[((SwitchButton *)target).view setOn:[value boolValue]];
});
}],
};
}

-(id)init{
self = [super init];
__weak typeof (self) wself = self;
[self setViewBlock:^UIView * _Nonnull{
UISwitch *swicth= [[UISwitch alloc] init];
[swicth sizeToFit];
wself.style.width = ASDimensionMake(swicth.frame.size.width);
wself.style.height = ASDimensionMake(swicth.frame.size.height);
return swicth;
}];
self.backgroundColor = [UIColor clearColor];
return self;
}

-(void)gic_createTowWayBindingWithAttributeName:(NSString *)attributeName withSignalBlock:(void (^)(RACSignal *))signalBlock{
signalBlock([self.view rac_newOnChannel]);
}
@end

gic_createTowWayBindingWithAttributeName这个方式是GICLayoutElementProtocol协议的一部分,专门用来实现双向绑定的协议。这里面使用ARC来实现,因此需要你返回一个RACSignal

好了,这样一个支持双向绑定的switch-button就完成了。然后在xml中写如下代码即可:

1
<switch-button checked="{{ exp=isShow,mode=2 }}"/>

属性转换器。

在上面的例子中,你有没有注意到自定义属性的那个地方gic_elementAttributsgic_elementAttributs方法也是协议的一部分,告知解析器该元素支持哪些属性,由于xml中的value全部是字符串的,因此需要将字符串转换成不同的value,比如上面例子中,将字符串转换成Bool值,那么就需要使用GICBoolConvertergic本身已经自带了很多的转换器了,针对不同的数据类型,你可以使用不同的转换器。而gic对于动画的支持也是通过属性转换器来实现的。你也可以在自定义的过程中实现自己的转换器,是需要创建一个继承自GICAttributeValueConverter的类并且实现里面的convert方法即可,当然如果你自定义的属性需要支持动画,那么还需要实现里面的convertAnimationValue方法。

##对已有元素进行属性扩展。

对已有的元素进行属性扩展也是简单之级,直接调用GICElementsCacheinjectAttributes:forElementName:方法即可。比如,对scorll-view进行属性扩展。

1
2
3
4
5
6
7
8
9
// 对scroll-view注入扩展属性
[GICElementsCache injectAttributes:@{ @"inset-behavior":[[GICNumberConverter alloc] initWithPropertySetter:^(NSObject *target, id value) {
if (@available(iOS 11.0, *)) {
// 考虑到解析的时候有可能是非UI线程解析的,这里使用GCD在主线程上访问view
dispatch_async(dispatch_get_main_queue(), ^{
[(UIScrollView *)((ASDisplayNode *)target).view setContentInsetAdjustmentBehavior:[value integerValue]];
});
}
}]} forElementName:@"scroll-view"];

自定义behavior

自定义behaviorbehavior一节已经介绍过了,直接去看就行了。

自定义动画

自定义动画的方式可以直接参考源码中的GICAttributeAnimation,这是gic实现属性动画的类,代码比较少,实现方式也很简单,相信你一看就能明白的。

自定义事件

事件的自定义在介绍自定义behavior一节也已经提到过了,可以直接看behavior那一节

自定义指令

自定义指令,你可以参考源码中的GICDirectiveIf实现,也很简单,相信你能一看就懂。你也可以试着自定义一个switch指令

JSAPI的自定义

JSAPI的自定义参考如下文档:

JSAPI扩展