From 0fbabd39d240247325e2ee40c6bd7562276c672d Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Thu, 28 Nov 2019 13:59:55 +0800 Subject: [PATCH 1/7] feat:add CollectionView --- iOS/Pod/Classes/DoricRegistry.m | 4 + .../Classes/Shader/DoricCollectionItemNode.h | 10 +++ .../Classes/Shader/DoricCollectionItemNode.m | 33 ++++++++ iOS/Pod/Classes/Shader/DoricCollectionNode.h | 9 +++ iOS/Pod/Classes/Shader/DoricCollectionNode.m | 71 +++++++++++++++++ iOS/Pod/Classes/Shader/DoricSliderNode.m | 24 +++--- js-framework/src/widget/collection.ts | 79 +++++++++++++++++++ js-framework/src/widget/index.widget.ts | 3 +- 8 files changed, 221 insertions(+), 12 deletions(-) create mode 100644 iOS/Pod/Classes/Shader/DoricCollectionItemNode.h create mode 100644 iOS/Pod/Classes/Shader/DoricCollectionItemNode.m create mode 100644 iOS/Pod/Classes/Shader/DoricCollectionNode.h create mode 100644 iOS/Pod/Classes/Shader/DoricCollectionNode.m create mode 100644 js-framework/src/widget/collection.ts diff --git a/iOS/Pod/Classes/DoricRegistry.m b/iOS/Pod/Classes/DoricRegistry.m index 0d7f9ea6..adae7587 100644 --- a/iOS/Pod/Classes/DoricRegistry.m +++ b/iOS/Pod/Classes/DoricRegistry.m @@ -38,6 +38,8 @@ #import "DoricNavigatorPlugin.h" #import "DoricNavBarPlugin.h" #import "DoricRefreshableNode.h" +#import "DoricCollectionItemNode.h" +#import "DoricCollectionNode.h" @interface DoricRegistry () @@ -78,6 +80,8 @@ - (void)innerRegister { [self registerViewNode:DoricSliderNode.class withName:@"Slider"]; [self registerViewNode:DoricSlideItemNode.class withName:@"SlideItem"]; [self registerViewNode:DoricRefreshableNode.class withName:@"Refreshable"]; + [self registerViewNode:DoricCollectionItemNode.class withName:@"CollectionItem"]; + [self registerViewNode:DoricCollectionNode.class withName:@"Collection"]; } - (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name { diff --git a/iOS/Pod/Classes/Shader/DoricCollectionItemNode.h b/iOS/Pod/Classes/Shader/DoricCollectionItemNode.h new file mode 100644 index 00000000..74965d32 --- /dev/null +++ b/iOS/Pod/Classes/Shader/DoricCollectionItemNode.h @@ -0,0 +1,10 @@ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import +#import "DoricStackNode.h" + + +@interface DoricCollectionItemNode : DoricStackNode +@end \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricCollectionItemNode.m b/iOS/Pod/Classes/Shader/DoricCollectionItemNode.m new file mode 100644 index 00000000..da58fc8a --- /dev/null +++ b/iOS/Pod/Classes/Shader/DoricCollectionItemNode.m @@ -0,0 +1,33 @@ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import "DoricCollectionItemNode.h" + +@interface DoricCollectionItemView : DoricStackView +@end + +@implementation DoricCollectionItemView +@end + +@interface DoricCollectionItemNode () +@end + + +@implementation DoricCollectionItemNode +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + self.reusable = YES; + } + return self; +} + +- (void)initWithSuperNode:(DoricSuperNode *)superNode { + [super initWithSuperNode:superNode]; + self.reusable = YES; +} + +- (DoricStackView *)build { + return [DoricCollectionItemView new]; +} +@end diff --git a/iOS/Pod/Classes/Shader/DoricCollectionNode.h b/iOS/Pod/Classes/Shader/DoricCollectionNode.h new file mode 100644 index 00000000..90dbf200 --- /dev/null +++ b/iOS/Pod/Classes/Shader/DoricCollectionNode.h @@ -0,0 +1,9 @@ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import +#import "DoricSuperNode.h" + +@interface DoricCollectionNode : DoricSuperNode +@end \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricCollectionNode.m b/iOS/Pod/Classes/Shader/DoricCollectionNode.m new file mode 100644 index 00000000..5678cee6 --- /dev/null +++ b/iOS/Pod/Classes/Shader/DoricCollectionNode.m @@ -0,0 +1,71 @@ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import "DoricCollectionNode.h" +#import "DoricCollectionItemNode.h" +#import "DoricExtensions.h" + +@interface DoricCollectionViewCell : UICollectionViewCell +@property(nonatomic, strong) DoricCollectionItemNode *viewNode; +@end + +@implementation DoricCollectionViewCell +@end + +@interface DoricCollectionView : UICollectionView +@end + +@implementation DoricCollectionView +- (CGSize)sizeThatFits:(CGSize)size { + if (self.subviews.count > 0) { + CGFloat width = size.width; + CGFloat height = size.height; + for (UIView *child in self.subviews) { + CGSize childSize = [child measureSize:size]; + width = MAX(childSize.width, width); + height = MAX(childSize.height, height); + } + return CGSizeMake(width, height); + } + return size; +} + +- (void)layoutSelf:(CGSize)targetSize { + [super layoutSelf:targetSize]; + [self reloadData]; +} +@end + +@interface DoricCollectionNode () +@property(nonatomic, strong) NSMutableDictionary *itemViewIds; +@property(nonatomic, assign) NSUInteger itemCount; +@property(nonatomic, assign) NSUInteger batchCount; +@end + +@implementation DoricCollectionNode +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + _itemViewIds = [NSMutableDictionary new]; + _batchCount = 15; + } + return self; +} + +- (UICollectionView *)build { + UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; + [flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal]; + + return [[[DoricCollectionView alloc] initWithFrame:CGRectZero + collectionViewLayout:flowLayout] + also:^(UICollectionView *it) { + it.backgroundColor = [UIColor whiteColor]; + it.pagingEnabled = YES; + it.delegate = self; + it.dataSource = self; + [it registerClass:[DoricCollectionViewCell class] forCellWithReuseIdentifier:@"doricCell"]; + }]; +} + + +@end \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricSliderNode.m b/iOS/Pod/Classes/Shader/DoricSliderNode.m index 9e495f8c..899b88f8 100644 --- a/iOS/Pod/Classes/Shader/DoricSliderNode.m +++ b/iOS/Pod/Classes/Shader/DoricSliderNode.m @@ -24,11 +24,11 @@ #import "Doric.h" #import "DoricSlideItemNode.h" -@interface DoricCollectionViewCell : UICollectionViewCell +@interface DoricSliderViewCell : UICollectionViewCell @property(nonatomic, strong) DoricSlideItemNode *doricSlideItemNode; @end -@implementation DoricCollectionViewCell +@implementation DoricSliderViewCell @end @interface DoricSliderNode () @@ -37,18 +37,20 @@ @interface DoricSliderNode () 0) { + CGFloat width = size.width; CGFloat height = size.height; for (UIView *child in self.subviews) { CGSize childSize = [child measureSize:size]; + width = MAX(childSize.width, width); height = MAX(childSize.height, height); } - return CGSizeMake(size.width, size.height); + return CGSizeMake(width, height); } return size; } @@ -72,14 +74,14 @@ - (UICollectionView *)build { UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; [flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal]; - return [[[DoricCollectionView alloc] initWithFrame:CGRectZero - collectionViewLayout:flowLayout] + return [[[DoricSliderView alloc] initWithFrame:CGRectZero + collectionViewLayout:flowLayout] also:^(UICollectionView *it) { it.backgroundColor = [UIColor whiteColor]; it.pagingEnabled = YES; it.delegate = self; it.dataSource = self; - [it registerClass:[DoricCollectionViewCell class] forCellWithReuseIdentifier:@"doricCell"]; + [it registerClass:[DoricSliderViewCell class] forCellWithReuseIdentifier:@"doricCell"]; }]; } @@ -118,7 +120,7 @@ - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collection NSUInteger position = (NSUInteger) indexPath.row; NSDictionary *model = [self itemModelAt:position]; NSDictionary *props = model[@"props"]; - DoricCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; + DoricSliderViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; if (!cell.doricSlideItemNode) { DoricSlideItemNode *slideItemNode = [[DoricSlideItemNode alloc] initWithContext:self.doricContext]; [slideItemNode initWithSuperNode:self]; @@ -159,8 +161,8 @@ - (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { __block DoricViewNode *ret = nil; [self.doricContext.driver ensureSyncInMainQueue:^{ for (UICollectionViewCell *collectionViewCell in self.view.visibleCells) { - if ([collectionViewCell isKindOfClass:[DoricCollectionViewCell class]]) { - DoricSlideItemNode *node = ((DoricCollectionViewCell *) collectionViewCell).doricSlideItemNode; + if ([collectionViewCell isKindOfClass:[DoricSliderViewCell class]]) { + DoricSlideItemNode *node = ((DoricSliderViewCell *) collectionViewCell).doricSlideItemNode; if ([viewId isEqualToString:node.viewId]) { ret = node; break; diff --git a/js-framework/src/widget/collection.ts b/js-framework/src/widget/collection.ts new file mode 100644 index 00000000..280bf13f --- /dev/null +++ b/js-framework/src/widget/collection.ts @@ -0,0 +1,79 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Stack } from './layouts' +import { Property, IView, Superview } from '../ui/view' + +export class CollectionItem extends Stack { + /** + * Set to reuse native view + */ + @Property + identifier?: string +} +export interface ICollection extends IView { + renderItem: (index: number) => CollectionItem + itemCount: number + batchCount?: number +} + +export class Collection extends Superview implements ICollection { + private cachedViews: Map = new Map + private ignoreDirtyCallOnce = false + allSubviews() { + return this.cachedViews.values() + } + + @Property + itemCount = 0 + + @Property + renderItem!: (index: number) => CollectionItem + + @Property + batchCount = 15 + + reset() { + this.cachedViews.clear() + this.itemCount = 0 + } + private getItem(itemIdx: number) { + let view = this.cachedViews.get(`${itemIdx}`) + if (view === undefined) { + view = this.renderItem(itemIdx) + view.superview = this + this.cachedViews.set(`${itemIdx}`, view) + } + return view + } + + isDirty() { + if (this.ignoreDirtyCallOnce) { + this.ignoreDirtyCallOnce = false + //Ignore the dirty call once. + return false + } + return super.isDirty() + } + + private renderBunchedItems(start: number, length: number) { + this.ignoreDirtyCallOnce = true; + return new Array(Math.min(length, this.itemCount - start)).fill(0).map((_, idx) => { + const listItem = this.getItem(start + idx) + return listItem.toModel() + }) + } +} + diff --git a/js-framework/src/widget/index.widget.ts b/js-framework/src/widget/index.widget.ts index 12a1efc1..9e8dde39 100644 --- a/js-framework/src/widget/index.widget.ts +++ b/js-framework/src/widget/index.widget.ts @@ -19,4 +19,5 @@ export * from './image' export * from './list' export * from './slider' export * from './scroller' -export * from './refreshable' \ No newline at end of file +export * from './refreshable' +export * from './collection' \ No newline at end of file From 96649e1248eb631c42be64bfd0c19c61a37fb8db Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Thu, 28 Nov 2019 14:02:11 +0800 Subject: [PATCH 2/7] feat:add iOS blend code --- iOS/Pod/Classes/Shader/DoricCollectionNode.m | 71 ++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/iOS/Pod/Classes/Shader/DoricCollectionNode.m b/iOS/Pod/Classes/Shader/DoricCollectionNode.m index 5678cee6..77a62fab 100644 --- a/iOS/Pod/Classes/Shader/DoricCollectionNode.m +++ b/iOS/Pod/Classes/Shader/DoricCollectionNode.m @@ -5,6 +5,7 @@ #import "DoricCollectionNode.h" #import "DoricCollectionItemNode.h" #import "DoricExtensions.h" +#import @interface DoricCollectionViewCell : UICollectionViewCell @property(nonatomic, strong) DoricCollectionItemNode *viewNode; @@ -67,5 +68,75 @@ - (UICollectionView *)build { }]; } +- (void)blendView:(UICollectionView *)view forPropName:(NSString *)name propValue:(id)prop { + if ([@"itemCount" isEqualToString:name]) { + self.itemCount = [prop unsignedIntegerValue]; + [self.view reloadData]; + } else if ([@"renderPage" isEqualToString:name]) { + [self.itemViewIds removeAllObjects]; + [self clearSubModel]; + [self.view reloadData]; + } else if ([@"batchCount" isEqualToString:name]) { + self.batchCount = [prop unsignedIntegerValue]; + } else { + [super blendView:view forPropName:name propValue:prop]; + } +} + +- (NSDictionary *)itemModelAt:(NSUInteger)position { + NSString *viewId = self.itemViewIds[@(position)]; + if (viewId && viewId.length > 0) { + return [self subModelOf:viewId]; + } else { + DoricAsyncResult *result = [self callJSResponse:@"renderBunchedItems", @(position), @(self.batchCount), nil]; + JSValue *models = [result waitUntilResult]; + NSArray *array = [models toArray]; + [array enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { + NSString *thisViewId = obj[@"id"]; + [self setSubModel:obj in:thisViewId]; + NSUInteger pos = position + idx; + self.itemViewIds[@(pos)] = thisViewId; + }]; + return array[0]; + } +} + +- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { + __block DoricViewNode *ret = nil; + [self.doricContext.driver ensureSyncInMainQueue:^{ + for (UICollectionViewCell *collectionViewCell in self.view.visibleCells) { + if ([collectionViewCell isKindOfClass:[DoricCollectionViewCell class]]) { + DoricCollectionItemNode *node = ((DoricCollectionViewCell *) collectionViewCell).viewNode; + if ([viewId isEqualToString:node.viewId]) { + ret = node; + break; + } + } + } + }]; + return ret; +} + +- (void)blendSubNode:(NSDictionary *)subModel { + NSString *viewId = subModel[@"id"]; + DoricViewNode *viewNode = [self subNodeWithViewId:viewId]; + if (viewNode) { + [viewNode blend:subModel[@"props"]]; + } else { + NSMutableDictionary *model = [[self subModelOf:viewId] mutableCopy]; + [self recursiveMixin:subModel to:model]; + [self setSubModel:model in:viewId]; + } + [self.itemViewIds enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) { + if ([viewId isEqualToString:obj]) { + *stop = YES; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[key integerValue] inSection:0]; + [UIView performWithoutAnimation:^{ + [self.view reloadItemsAtIndexPaths:@[indexPath]]; + }]; + } + }]; +} + @end \ No newline at end of file From f0de868a34f734c65936236923ff3d05be93271f Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Thu, 28 Nov 2019 14:43:53 +0800 Subject: [PATCH 3/7] feat:iOS simulator support devkit --- iOS/Example/Example/ViewController.m | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/iOS/Example/Example/ViewController.m b/iOS/Example/Example/ViewController.m index 6110d39c..8acb2e3e 100644 --- a/iOS/Example/Example/ViewController.m +++ b/iOS/Example/Example/ViewController.m @@ -53,9 +53,19 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N return cell; } +- (BOOL)isSimulator { + return TARGET_OS_SIMULATOR == 1; +} + - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.row == 0) { - [self.navigationController pushViewController:[QRScanViewController new] animated:NO]; + if (self.isSimulator) { + NSString *result = @"127.0.0.1"; + [[DoricDriver instance] connectDevKit:[NSString stringWithFormat:@"ws://%@:7777", result]]; + ShowToast([NSString stringWithFormat:@"Connected to %@", result], BOTTOM); + } else { + [self.navigationController pushViewController:[QRScanViewController new] animated:NO]; + } return; } NSString *file = self.demoFilePaths[(NSUInteger) indexPath.row]; From f2b4ba23c46285ec7532f08f0285455d6b554874 Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Thu, 28 Nov 2019 14:44:15 +0800 Subject: [PATCH 4/7] feat:add CollectionDemo --- demo/index.ts | 1 + demo/rollup.config.js | 6 +-- demo/src/CollectionDemo.ts | 31 ++++++++++++ iOS/Pod/Classes/Shader/DoricCollectionNode.m | 50 +++++++++++++++++++- js-framework/src/widget/collection.ts | 17 ++++++- 5 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 demo/src/CollectionDemo.ts diff --git a/demo/index.ts b/demo/index.ts index 7b1bac66..cba98715 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -13,4 +13,5 @@ export default [ 'src/NavigatorDemo', 'src/NavbarDemo', 'src/RefreshableDemo', + 'src/CollectionDemo', ] \ No newline at end of file diff --git a/demo/rollup.config.js b/demo/rollup.config.js index 350e6bb7..3e5b5b75 100644 --- a/demo/rollup.config.js +++ b/demo/rollup.config.js @@ -15,9 +15,9 @@ export default bundles.map(bundle => { commonjs() ], external: ['reflect-metadata', 'doric'], - onwarn: function(warning) { - if ( warning.code === 'THIS_IS_UNDEFINED' ) { return; } - console.warn( warning.message ); + onwarn: function (warning) { + if (warning.code === 'THIS_IS_UNDEFINED') { return; } + console.warn(warning.message); } } }) \ No newline at end of file diff --git a/demo/src/CollectionDemo.ts b/demo/src/CollectionDemo.ts new file mode 100644 index 00000000..19e5d250 --- /dev/null +++ b/demo/src/CollectionDemo.ts @@ -0,0 +1,31 @@ +import { Group, Panel, collection, layoutConfig, CollectionItem, } from "doric"; +import { colors, label } from "./utils"; + +const imageUrls = [ + 'http://b.hiphotos.baidu.com/image/pic/item/908fa0ec08fa513db777cf78376d55fbb3fbd9b3.jpg', + 'http://f.hiphotos.baidu.com/image/pic/item/0e2442a7d933c8956c0e8eeadb1373f08202002a.jpg', + 'http://f.hiphotos.baidu.com/image/pic/item/b151f8198618367aa7f3cc7424738bd4b31ce525.jpg', + 'http://b.hiphotos.baidu.com/image/pic/item/0eb30f2442a7d9337119f7dba74bd11372f001e0.jpg', + 'http://a.hiphotos.baidu.com/image/h%3D300/sign=b38f3fc35b0fd9f9bf175369152cd42b/9a504fc2d5628535bdaac29e9aef76c6a6ef63c2.jpg', + 'http://h.hiphotos.baidu.com/image/pic/item/810a19d8bc3eb1354c94a704ac1ea8d3fd1f4439.jpg', + 'http://calonye.com/wp-content/uploads/2015/08/0-wx_fmtgiftpwebpwxfrom5wx_lazy1-9.gif', + 'http://hbimg.b0.upaiyun.com/ca29ea125b7f2d580f573e48eb594b1ef509757f34a08-m0hK45_fw658', + 'https://misc.aotu.io/ONE-SUNDAY/SteamEngine.png', +] +@Entry +class CollectionDemo extends Panel { + build(rootView: Group): void { + collection({ + layoutConfig: layoutConfig().atmost(), + itemCount: 50, + renderItem: (idx) => { + return new CollectionItem().apply({ + bgColor: colors[idx % colors.length], + width: 70 + idx, + height: 70 + idx, + }) + }, + }) + .in(rootView) + } +} \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricCollectionNode.m b/iOS/Pod/Classes/Shader/DoricCollectionNode.m index 77a62fab..b2550167 100644 --- a/iOS/Pod/Classes/Shader/DoricCollectionNode.m +++ b/iOS/Pod/Classes/Shader/DoricCollectionNode.m @@ -40,6 +40,7 @@ - (void)layoutSelf:(CGSize)targetSize { @interface DoricCollectionNode () @property(nonatomic, strong) NSMutableDictionary *itemViewIds; +@property(nonatomic, strong) NSMutableDictionary *itemSizeInfo; @property(nonatomic, assign) NSUInteger itemCount; @property(nonatomic, assign) NSUInteger batchCount; @end @@ -55,7 +56,7 @@ - (instancetype)initWithContext:(DoricContext *)doricContext { - (UICollectionView *)build { UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; - [flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal]; + [flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical]; return [[[DoricCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:flowLayout] @@ -72,7 +73,7 @@ - (void)blendView:(UICollectionView *)view forPropName:(NSString *)name propValu if ([@"itemCount" isEqualToString:name]) { self.itemCount = [prop unsignedIntegerValue]; [self.view reloadData]; - } else if ([@"renderPage" isEqualToString:name]) { + } else if ([@"renderItem" isEqualToString:name]) { [self.itemViewIds removeAllObjects]; [self clearSubModel]; [self.view reloadData]; @@ -138,5 +139,50 @@ - (void)blendSubNode:(NSDictionary *)subModel { }]; } +- (void)callItem:(NSUInteger)position size:(CGSize)size { + NSValue *old = self.itemSizeInfo[@(position)]; + if (old && CGSizeEqualToSize([old CGSizeValue], size)) { + return; + } + self.itemSizeInfo[@(position)] = [NSValue valueWithCGSize:size]; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:position inSection:0]; + [UIView performWithoutAnimation:^{ + [self.view reloadItemsAtIndexPaths:@[indexPath]]; + }]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return self.itemCount; +} + +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + NSUInteger position = (NSUInteger) indexPath.row; + NSDictionary *model = [self itemModelAt:position]; + NSDictionary *props = model[@"props"]; + DoricCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; + if (!cell.viewNode) { + DoricCollectionItemNode *itemNode = [[DoricCollectionItemNode alloc] initWithContext:self.doricContext]; + [itemNode initWithSuperNode:self]; + cell.viewNode = itemNode; + [cell.contentView addSubview:itemNode.view]; + } + DoricCollectionItemNode *node = cell.viewNode; + node.viewId = model[@"id"]; + [node blend:props]; + CGSize size = [node.view measureSize:CGSizeMake(collectionView.width, collectionView.height)]; + [node.view layoutSelf:size]; + [self callItem:position size:size]; + return cell; +} + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + NSUInteger position = (NSUInteger) indexPath.row; + NSValue *value = self.itemSizeInfo[@(position)]; + if (value) { + return [value CGSizeValue]; + } else { + return CGSizeMake(100, 100); + } +} @end \ No newline at end of file diff --git a/js-framework/src/widget/collection.ts b/js-framework/src/widget/collection.ts index 280bf13f..aecd54e3 100644 --- a/js-framework/src/widget/collection.ts +++ b/js-framework/src/widget/collection.ts @@ -14,7 +14,8 @@ * limitations under the License. */ import { Stack } from './layouts' -import { Property, IView, Superview } from '../ui/view' +import { Property, IView, Superview, View } from '../ui/view' +import { layoutConfig } from '../util/index.util' export class CollectionItem extends Stack { /** @@ -77,3 +78,17 @@ export class Collection extends Superview implements ICollection { } } +export function collection(config: ICollection) { + const ret = new Collection + for (let key in config) { + Reflect.set(ret, key, Reflect.get(config, key, config), ret) + } + return ret +} + +export function collectionItem(item: View) { + return (new CollectionItem).also((it) => { + it.layoutConfig = layoutConfig().wrap() + it.addChild(item) + }) +} From 0031d7a17db31b36e368ebd8ddcde8e53ae9a988 Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Thu, 28 Nov 2019 15:25:34 +0800 Subject: [PATCH 5/7] feat:rename collcetion to flowlayout --- demo/index.ts | 2 +- .../{CollectionDemo.ts => FlowLayoutDemo.ts} | 14 +++---- iOS/Pod/Classes/DoricRegistry.m | 8 ++-- ...onItemNode.h => DoricFlowLayoutItemNode.h} | 2 +- ...onItemNode.m => DoricFlowLayoutItemNode.m} | 12 +++--- ...CollectionNode.h => DoricFlowLayoutNode.h} | 2 +- ...CollectionNode.m => DoricFlowLayoutNode.m} | 42 +++++++++++-------- .../widget/{collection.ts => flowlayout.ts} | 25 ++++++----- js-framework/src/widget/index.widget.ts | 2 +- 9 files changed, 61 insertions(+), 48 deletions(-) rename demo/src/{CollectionDemo.ts => FlowLayoutDemo.ts} (79%) rename iOS/Pod/Classes/Shader/{DoricCollectionItemNode.h => DoricFlowLayoutItemNode.h} (66%) rename iOS/Pod/Classes/Shader/{DoricCollectionItemNode.m => DoricFlowLayoutItemNode.m} (61%) rename iOS/Pod/Classes/Shader/{DoricCollectionNode.h => DoricFlowLayoutNode.h} (62%) rename iOS/Pod/Classes/Shader/{DoricCollectionNode.m => DoricFlowLayoutNode.m} (81%) rename js-framework/src/widget/{collection.ts => flowlayout.ts} (80%) diff --git a/demo/index.ts b/demo/index.ts index cba98715..62eca345 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -13,5 +13,5 @@ export default [ 'src/NavigatorDemo', 'src/NavbarDemo', 'src/RefreshableDemo', - 'src/CollectionDemo', + 'src/FlowLayoutDemo', ] \ No newline at end of file diff --git a/demo/src/CollectionDemo.ts b/demo/src/FlowLayoutDemo.ts similarity index 79% rename from demo/src/CollectionDemo.ts rename to demo/src/FlowLayoutDemo.ts index 19e5d250..3d868636 100644 --- a/demo/src/CollectionDemo.ts +++ b/demo/src/FlowLayoutDemo.ts @@ -1,4 +1,4 @@ -import { Group, Panel, collection, layoutConfig, CollectionItem, } from "doric"; +import { Group, Panel, flowlayout, layoutConfig, FlowLayoutItem } from "doric"; import { colors, label } from "./utils"; const imageUrls = [ @@ -13,16 +13,16 @@ const imageUrls = [ 'https://misc.aotu.io/ONE-SUNDAY/SteamEngine.png', ] @Entry -class CollectionDemo extends Panel { +class FlowDemo extends Panel { build(rootView: Group): void { - collection({ + flowlayout({ layoutConfig: layoutConfig().atmost(), - itemCount: 50, + itemCount: 500, renderItem: (idx) => { - return new CollectionItem().apply({ + return new FlowLayoutItem().apply({ bgColor: colors[idx % colors.length], - width: 70 + idx, - height: 70 + idx, + width: 200, + height: 50 + idx * 10, }) }, }) diff --git a/iOS/Pod/Classes/DoricRegistry.m b/iOS/Pod/Classes/DoricRegistry.m index adae7587..48f6ac46 100644 --- a/iOS/Pod/Classes/DoricRegistry.m +++ b/iOS/Pod/Classes/DoricRegistry.m @@ -38,8 +38,8 @@ #import "DoricNavigatorPlugin.h" #import "DoricNavBarPlugin.h" #import "DoricRefreshableNode.h" -#import "DoricCollectionItemNode.h" -#import "DoricCollectionNode.h" +#import "DoricFlowLayoutItemNode.h" +#import "DoricFlowLayoutNode.h" @interface DoricRegistry () @@ -80,8 +80,8 @@ - (void)innerRegister { [self registerViewNode:DoricSliderNode.class withName:@"Slider"]; [self registerViewNode:DoricSlideItemNode.class withName:@"SlideItem"]; [self registerViewNode:DoricRefreshableNode.class withName:@"Refreshable"]; - [self registerViewNode:DoricCollectionItemNode.class withName:@"CollectionItem"]; - [self registerViewNode:DoricCollectionNode.class withName:@"Collection"]; + [self registerViewNode:DoricFlowLayoutItemNode.class withName:@"FlowLayoutItem"]; + [self registerViewNode:DoricFlowLayoutNode.class withName:@"FlowLayout"]; } - (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name { diff --git a/iOS/Pod/Classes/Shader/DoricCollectionItemNode.h b/iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.h similarity index 66% rename from iOS/Pod/Classes/Shader/DoricCollectionItemNode.h rename to iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.h index 74965d32..8c3d22be 100644 --- a/iOS/Pod/Classes/Shader/DoricCollectionItemNode.h +++ b/iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.h @@ -6,5 +6,5 @@ #import "DoricStackNode.h" -@interface DoricCollectionItemNode : DoricStackNode +@interface DoricFlowLayoutItemNode : DoricStackNode @end \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricCollectionItemNode.m b/iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.m similarity index 61% rename from iOS/Pod/Classes/Shader/DoricCollectionItemNode.m rename to iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.m index da58fc8a..dc1e34ae 100644 --- a/iOS/Pod/Classes/Shader/DoricCollectionItemNode.m +++ b/iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.m @@ -2,19 +2,19 @@ // Created by pengfei.zhou on 2019/11/28. // -#import "DoricCollectionItemNode.h" +#import "DoricFlowLayoutItemNode.h" -@interface DoricCollectionItemView : DoricStackView +@interface DoricFlowLayoutItemView : DoricStackView @end -@implementation DoricCollectionItemView +@implementation DoricFlowLayoutItemView @end -@interface DoricCollectionItemNode () +@interface DoricFlowLayoutItemNode () @end -@implementation DoricCollectionItemNode +@implementation DoricFlowLayoutItemNode - (instancetype)initWithContext:(DoricContext *)doricContext { if (self = [super initWithContext:doricContext]) { self.reusable = YES; @@ -28,6 +28,6 @@ - (void)initWithSuperNode:(DoricSuperNode *)superNode { } - (DoricStackView *)build { - return [DoricCollectionItemView new]; + return [DoricFlowLayoutItemView new]; } @end diff --git a/iOS/Pod/Classes/Shader/DoricCollectionNode.h b/iOS/Pod/Classes/Shader/DoricFlowLayoutNode.h similarity index 62% rename from iOS/Pod/Classes/Shader/DoricCollectionNode.h rename to iOS/Pod/Classes/Shader/DoricFlowLayoutNode.h index 90dbf200..1c999e15 100644 --- a/iOS/Pod/Classes/Shader/DoricCollectionNode.h +++ b/iOS/Pod/Classes/Shader/DoricFlowLayoutNode.h @@ -5,5 +5,5 @@ #import #import "DoricSuperNode.h" -@interface DoricCollectionNode : DoricSuperNode +@interface DoricFlowLayoutNode : DoricSuperNode @end \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricCollectionNode.m b/iOS/Pod/Classes/Shader/DoricFlowLayoutNode.m similarity index 81% rename from iOS/Pod/Classes/Shader/DoricCollectionNode.m rename to iOS/Pod/Classes/Shader/DoricFlowLayoutNode.m index b2550167..f467f92b 100644 --- a/iOS/Pod/Classes/Shader/DoricCollectionNode.m +++ b/iOS/Pod/Classes/Shader/DoricFlowLayoutNode.m @@ -2,22 +2,22 @@ // Created by pengfei.zhou on 2019/11/28. // -#import "DoricCollectionNode.h" -#import "DoricCollectionItemNode.h" +#import "DoricFlowLayoutNode.h" +#import "DoricFlowLayoutItemNode.h" #import "DoricExtensions.h" #import -@interface DoricCollectionViewCell : UICollectionViewCell -@property(nonatomic, strong) DoricCollectionItemNode *viewNode; +@interface DoricFlowLayoutViewCell : UICollectionViewCell +@property(nonatomic, strong) DoricFlowLayoutItemNode *viewNode; @end -@implementation DoricCollectionViewCell +@implementation DoricFlowLayoutViewCell @end -@interface DoricCollectionView : UICollectionView +@interface DoricFlowLayoutView : UICollectionView @end -@implementation DoricCollectionView +@implementation DoricFlowLayoutView - (CGSize)sizeThatFits:(CGSize)size { if (self.subviews.count > 0) { CGFloat width = size.width; @@ -38,17 +38,18 @@ - (void)layoutSelf:(CGSize)targetSize { } @end -@interface DoricCollectionNode () +@interface DoricFlowLayoutNode () @property(nonatomic, strong) NSMutableDictionary *itemViewIds; @property(nonatomic, strong) NSMutableDictionary *itemSizeInfo; @property(nonatomic, assign) NSUInteger itemCount; @property(nonatomic, assign) NSUInteger batchCount; @end -@implementation DoricCollectionNode +@implementation DoricFlowLayoutNode - (instancetype)initWithContext:(DoricContext *)doricContext { if (self = [super initWithContext:doricContext]) { _itemViewIds = [NSMutableDictionary new]; + _itemSizeInfo = [NSMutableDictionary new]; _batchCount = 15; } return self; @@ -58,14 +59,14 @@ - (UICollectionView *)build { UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; [flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical]; - return [[[DoricCollectionView alloc] initWithFrame:CGRectZero + return [[[DoricFlowLayoutView alloc] initWithFrame:CGRectZero collectionViewLayout:flowLayout] also:^(UICollectionView *it) { it.backgroundColor = [UIColor whiteColor]; it.pagingEnabled = YES; it.delegate = self; it.dataSource = self; - [it registerClass:[DoricCollectionViewCell class] forCellWithReuseIdentifier:@"doricCell"]; + [it registerClass:[DoricFlowLayoutViewCell class] forCellWithReuseIdentifier:@"doricCell"]; }]; } @@ -106,8 +107,8 @@ - (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { __block DoricViewNode *ret = nil; [self.doricContext.driver ensureSyncInMainQueue:^{ for (UICollectionViewCell *collectionViewCell in self.view.visibleCells) { - if ([collectionViewCell isKindOfClass:[DoricCollectionViewCell class]]) { - DoricCollectionItemNode *node = ((DoricCollectionViewCell *) collectionViewCell).viewNode; + if ([collectionViewCell isKindOfClass:[DoricFlowLayoutViewCell class]]) { + DoricFlowLayoutItemNode *node = ((DoricFlowLayoutViewCell *) collectionViewCell).viewNode; if ([viewId isEqualToString:node.viewId]) { ret = node; break; @@ -159,14 +160,14 @@ - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collection NSUInteger position = (NSUInteger) indexPath.row; NSDictionary *model = [self itemModelAt:position]; NSDictionary *props = model[@"props"]; - DoricCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; + DoricFlowLayoutViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; if (!cell.viewNode) { - DoricCollectionItemNode *itemNode = [[DoricCollectionItemNode alloc] initWithContext:self.doricContext]; + DoricFlowLayoutItemNode *itemNode = [[DoricFlowLayoutItemNode alloc] initWithContext:self.doricContext]; [itemNode initWithSuperNode:self]; cell.viewNode = itemNode; [cell.contentView addSubview:itemNode.view]; } - DoricCollectionItemNode *node = cell.viewNode; + DoricFlowLayoutItemNode *node = cell.viewNode; node.viewId = model[@"id"]; [node blend:props]; CGSize size = [node.view measureSize:CGSizeMake(collectionView.width, collectionView.height)]; @@ -185,4 +186,11 @@ - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollection } } -@end \ No newline at end of file +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { + return 0; +} + +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { + return 0; +} +@end diff --git a/js-framework/src/widget/collection.ts b/js-framework/src/widget/flowlayout.ts similarity index 80% rename from js-framework/src/widget/collection.ts rename to js-framework/src/widget/flowlayout.ts index aecd54e3..c339cb2a 100644 --- a/js-framework/src/widget/collection.ts +++ b/js-framework/src/widget/flowlayout.ts @@ -17,31 +17,36 @@ import { Stack } from './layouts' import { Property, IView, Superview, View } from '../ui/view' import { layoutConfig } from '../util/index.util' -export class CollectionItem extends Stack { +export class FlowLayoutItem extends Stack { /** * Set to reuse native view */ @Property identifier?: string } -export interface ICollection extends IView { - renderItem: (index: number) => CollectionItem +export interface IFlowLayout extends IView { + renderItem: (index: number) => FlowLayoutItem itemCount: number batchCount?: number + column?: number } -export class Collection extends Superview implements ICollection { - private cachedViews: Map = new Map +export class FlowLayout extends Superview implements IFlowLayout { + private cachedViews: Map = new Map private ignoreDirtyCallOnce = false + allSubviews() { return this.cachedViews.values() } + @Property + column = 1 + @Property itemCount = 0 @Property - renderItem!: (index: number) => CollectionItem + renderItem!: (index: number) => FlowLayoutItem @Property batchCount = 15 @@ -78,16 +83,16 @@ export class Collection extends Superview implements ICollection { } } -export function collection(config: ICollection) { - const ret = new Collection +export function flowlayout(config: IFlowLayout) { + const ret = new FlowLayout for (let key in config) { Reflect.set(ret, key, Reflect.get(config, key, config), ret) } return ret } -export function collectionItem(item: View) { - return (new CollectionItem).also((it) => { +export function flowItem(item: View) { + return (new FlowLayoutItem).also((it) => { it.layoutConfig = layoutConfig().wrap() it.addChild(item) }) diff --git a/js-framework/src/widget/index.widget.ts b/js-framework/src/widget/index.widget.ts index 9e8dde39..14449949 100644 --- a/js-framework/src/widget/index.widget.ts +++ b/js-framework/src/widget/index.widget.ts @@ -20,4 +20,4 @@ export * from './list' export * from './slider' export * from './scroller' export * from './refreshable' -export * from './collection' \ No newline at end of file +export * from './flowlayout' \ No newline at end of file From 8a15297ebd28f6861ca5ac40f01eadcce81cfaf1 Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Thu, 28 Nov 2019 17:41:12 +0800 Subject: [PATCH 6/7] feat:iOS FlowLayout --- demo/src/FlowLayoutDemo.ts | 16 +- .../Classes/Refresh/DoricRefreshableNode.h | 15 ++ .../Classes/Refresh/DoricRefreshableNode.m | 15 ++ .../Classes/Refresh/DoricSwipeRefreshLayout.h | 15 ++ .../Classes/Refresh/DoricSwipeRefreshLayout.m | 15 ++ .../Classes/Shader/DoricFlowLayoutItemNode.h | 15 ++ .../Classes/Shader/DoricFlowLayoutItemNode.m | 15 ++ iOS/Pod/Classes/Shader/DoricFlowLayoutNode.h | 15 ++ iOS/Pod/Classes/Shader/DoricFlowLayoutNode.m | 158 ++++++++++++++++-- js-framework/src/widget/flowlayout.ts | 17 +- 10 files changed, 274 insertions(+), 22 deletions(-) diff --git a/demo/src/FlowLayoutDemo.ts b/demo/src/FlowLayoutDemo.ts index 3d868636..1bdb13ed 100644 --- a/demo/src/FlowLayoutDemo.ts +++ b/demo/src/FlowLayoutDemo.ts @@ -1,4 +1,4 @@ -import { Group, Panel, flowlayout, layoutConfig, FlowLayoutItem } from "doric"; +import { Group, Panel, flowlayout, layoutConfig, FlowLayoutItem, text, Color, IFlowLayout, LayoutSpec, Gravity } from "doric"; import { colors, label } from "./utils"; const imageUrls = [ @@ -18,11 +18,21 @@ class FlowDemo extends Panel { flowlayout({ layoutConfig: layoutConfig().atmost(), itemCount: 500, + columnCount: 3, + columnSpace: 10, + rowSpace: 10, renderItem: (idx) => { return new FlowLayoutItem().apply({ bgColor: colors[idx % colors.length], - width: 200, - height: 50 + idx * 10, + height: 50 + (idx % 3) * 20, + layoutConfig: layoutConfig().w(LayoutSpec.AT_MOST), + }).also(it => { + it.addChild(text({ + text: `${idx}`, + textColor: Color.WHITE, + textSize: 20, + layoutConfig: layoutConfig().wrap().a(Gravity.Center) + })) }) }, }) diff --git a/iOS/Pod/Classes/Refresh/DoricRefreshableNode.h b/iOS/Pod/Classes/Refresh/DoricRefreshableNode.h index 61bb6845..db9e5357 100644 --- a/iOS/Pod/Classes/Refresh/DoricRefreshableNode.h +++ b/iOS/Pod/Classes/Refresh/DoricRefreshableNode.h @@ -1,3 +1,18 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // // Created by pengfei.zhou on 2019/11/26. // diff --git a/iOS/Pod/Classes/Refresh/DoricRefreshableNode.m b/iOS/Pod/Classes/Refresh/DoricRefreshableNode.m index 340590e8..289e4443 100644 --- a/iOS/Pod/Classes/Refresh/DoricRefreshableNode.m +++ b/iOS/Pod/Classes/Refresh/DoricRefreshableNode.m @@ -1,3 +1,18 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // // Created by pengfei.zhou on 2019/11/26. // diff --git a/iOS/Pod/Classes/Refresh/DoricSwipeRefreshLayout.h b/iOS/Pod/Classes/Refresh/DoricSwipeRefreshLayout.h index 45725251..f24a0edb 100644 --- a/iOS/Pod/Classes/Refresh/DoricSwipeRefreshLayout.h +++ b/iOS/Pod/Classes/Refresh/DoricSwipeRefreshLayout.h @@ -1,3 +1,18 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // // Created by pengfei.zhou on 2019/11/26. // diff --git a/iOS/Pod/Classes/Refresh/DoricSwipeRefreshLayout.m b/iOS/Pod/Classes/Refresh/DoricSwipeRefreshLayout.m index 5431c3d6..42809bbe 100644 --- a/iOS/Pod/Classes/Refresh/DoricSwipeRefreshLayout.m +++ b/iOS/Pod/Classes/Refresh/DoricSwipeRefreshLayout.m @@ -1,3 +1,18 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // // Created by pengfei.zhou on 2019/11/26. // diff --git a/iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.h b/iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.h index 8c3d22be..ffea52f1 100644 --- a/iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.h +++ b/iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.h @@ -1,3 +1,18 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // // Created by pengfei.zhou on 2019/11/28. // diff --git a/iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.m b/iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.m index dc1e34ae..417e87e0 100644 --- a/iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.m +++ b/iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.m @@ -1,3 +1,18 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // // Created by pengfei.zhou on 2019/11/28. // diff --git a/iOS/Pod/Classes/Shader/DoricFlowLayoutNode.h b/iOS/Pod/Classes/Shader/DoricFlowLayoutNode.h index 1c999e15..53cd9a39 100644 --- a/iOS/Pod/Classes/Shader/DoricFlowLayoutNode.h +++ b/iOS/Pod/Classes/Shader/DoricFlowLayoutNode.h @@ -1,3 +1,18 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // // Created by pengfei.zhou on 2019/11/28. // diff --git a/iOS/Pod/Classes/Shader/DoricFlowLayoutNode.m b/iOS/Pod/Classes/Shader/DoricFlowLayoutNode.m index f467f92b..b6258613 100644 --- a/iOS/Pod/Classes/Shader/DoricFlowLayoutNode.m +++ b/iOS/Pod/Classes/Shader/DoricFlowLayoutNode.m @@ -1,3 +1,18 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // // Created by pengfei.zhou on 2019/11/28. // @@ -7,6 +22,99 @@ #import "DoricExtensions.h" #import +@protocol DoricFlowLayoutDelegate +- (CGFloat)doricFlowLayoutItemHeightAtIndexPath:(NSIndexPath *)indexPath; + +- (CGFloat)doricFlowLayoutColumnSpace; + +- (CGFloat)doricFlowLayoutRowSpace; + +- (NSInteger)doricFlowLayoutColumnCount; + +@end + +@interface DoricFlowLayout : UICollectionViewLayout +@property(nonatomic, readonly) NSInteger columnCount; +@property(nonatomic, readonly) CGFloat columnSpace; +@property(nonatomic, readonly) CGFloat rowSpace; +@property(nonatomic, strong) NSMutableDictionary *columnHeightInfo; +@property(nonatomic, weak) id delegate; +@end + +@implementation DoricFlowLayout +- (instancetype)init { + if (self = [super init]) { + _columnHeightInfo = [NSMutableDictionary new]; + } + return self; +} + +- (NSInteger)columnCount { + return self.delegate.doricFlowLayoutColumnCount ?: 2; +} + +- (CGFloat)columnSpace { + return self.delegate.doricFlowLayoutColumnSpace ?: 0; +} + +- (CGFloat)rowSpace { + return self.delegate.doricFlowLayoutRowSpace ?: 0; +} + +- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { + return YES; +} + +- (void)prepareLayout { + [super prepareLayout]; + for (int i = 0; i < self.columnCount; i++) { + self.columnHeightInfo[@(i)] = @(0); + } +} + +- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { + for (int i = 0; i < self.columnCount; i++) { + self.columnHeightInfo[@(i)] = @(0); + } + NSMutableArray *array = [NSMutableArray array]; + NSInteger count = [self.collectionView numberOfItemsInSection:0]; + for (int i = 0; i < count; i++) { + UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; + [array addObject:attrs]; + } + return array; +} + +- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { + NSNumber *minYOfColumn = @(0); + for (NSNumber *key in self.columnHeightInfo.allKeys) { + if ([self.columnHeightInfo[key] floatValue] < [self.columnHeightInfo[minYOfColumn] floatValue]) { + minYOfColumn = key; + } + } + + CGFloat width = (self.collectionView.width - self.columnSpace * (self.columnCount - 1)) / self.columnCount; + CGFloat height = [self.delegate doricFlowLayoutItemHeightAtIndexPath:indexPath]; + CGFloat x = (width + self.columnSpace) * [minYOfColumn integerValue]; + CGFloat y = self.rowSpace + [self.columnHeightInfo[minYOfColumn] floatValue]; + + self.columnHeightInfo[minYOfColumn] = @(y + height); + + UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; + attrs.frame = CGRectMake(x, y, width, height); + return attrs; +} + +- (CGSize)collectionViewContentSize { + CGFloat width = self.collectionView.width; + CGFloat height = 0; + for (NSNumber *column in self.columnHeightInfo.allValues) { + height = MAX(height, [column floatValue]); + } + return CGSizeMake(width, height); +} +@end + @interface DoricFlowLayoutViewCell : UICollectionViewCell @property(nonatomic, strong) DoricFlowLayoutItemNode *viewNode; @end @@ -38,11 +146,14 @@ - (void)layoutSelf:(CGSize)targetSize { } @end -@interface DoricFlowLayoutNode () +@interface DoricFlowLayoutNode () @property(nonatomic, strong) NSMutableDictionary *itemViewIds; @property(nonatomic, strong) NSMutableDictionary *itemSizeInfo; @property(nonatomic, assign) NSUInteger itemCount; @property(nonatomic, assign) NSUInteger batchCount; +@property(nonatomic, assign) NSUInteger columnCount; +@property(nonatomic, assign) CGFloat columnSpace; +@property(nonatomic, assign) CGFloat rowSpace; @end @implementation DoricFlowLayoutNode @@ -51,14 +162,14 @@ - (instancetype)initWithContext:(DoricContext *)doricContext { _itemViewIds = [NSMutableDictionary new]; _itemSizeInfo = [NSMutableDictionary new]; _batchCount = 15; + _columnCount = 2; } return self; } - (UICollectionView *)build { - UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; - [flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical]; - + DoricFlowLayout *flowLayout = [[DoricFlowLayout alloc] init]; + flowLayout.delegate = self; return [[[DoricFlowLayoutView alloc] initWithFrame:CGRectZero collectionViewLayout:flowLayout] also:^(UICollectionView *it) { @@ -71,7 +182,17 @@ - (UICollectionView *)build { } - (void)blendView:(UICollectionView *)view forPropName:(NSString *)name propValue:(id)prop { - if ([@"itemCount" isEqualToString:name]) { + if ([@"columnSpace" isEqualToString:name]) { + self.columnSpace = [prop floatValue]; + [self.view.collectionViewLayout invalidateLayout]; + } else if ([@"rowSpace" isEqualToString:name]) { + self.rowSpace = [prop floatValue]; + [self.view.collectionViewLayout invalidateLayout]; + } else if ([@"columnCount" isEqualToString:name]) { + self.columnCount = [prop unsignedIntegerValue]; + [self.view reloadData]; + [self.view.collectionViewLayout invalidateLayout]; + } else if ([@"itemCount" isEqualToString:name]) { self.itemCount = [prop unsignedIntegerValue]; [self.view reloadData]; } else if ([@"renderItem" isEqualToString:name]) { @@ -146,10 +267,7 @@ - (void)callItem:(NSUInteger)position size:(CGSize)size { return; } self.itemSizeInfo[@(position)] = [NSValue valueWithCGSize:size]; - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:position inSection:0]; - [UIView performWithoutAnimation:^{ - [self.view reloadItemsAtIndexPaths:@[indexPath]]; - }]; + [self.view.collectionViewLayout invalidateLayout]; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { @@ -170,27 +288,33 @@ - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collection DoricFlowLayoutItemNode *node = cell.viewNode; node.viewId = model[@"id"]; [node blend:props]; - CGSize size = [node.view measureSize:CGSizeMake(collectionView.width, collectionView.height)]; + CGFloat width = (collectionView.width - (self.columnCount - 1) * self.columnSpace) / self.columnCount; + CGSize size = [node.view measureSize:CGSizeMake(width, collectionView.height)]; [node.view layoutSelf:size]; [self callItem:position size:size]; return cell; } -- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { +- (CGFloat)doricFlowLayoutItemHeightAtIndexPath:(NSIndexPath *)indexPath { NSUInteger position = (NSUInteger) indexPath.row; NSValue *value = self.itemSizeInfo[@(position)]; if (value) { - return [value CGSizeValue]; + return [value CGSizeValue].height; } else { - return CGSizeMake(100, 100); + return 100; } } -- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { - return 0; +- (CGFloat)doricFlowLayoutColumnSpace { + return self.columnSpace; } -- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { - return 0; +- (CGFloat)doricFlowLayoutRowSpace { + return self.rowSpace; } + +- (NSInteger)doricFlowLayoutColumnCount { + return self.columnCount; +} + @end diff --git a/js-framework/src/widget/flowlayout.ts b/js-framework/src/widget/flowlayout.ts index c339cb2a..fe0655a5 100644 --- a/js-framework/src/widget/flowlayout.ts +++ b/js-framework/src/widget/flowlayout.ts @@ -26,9 +26,16 @@ export class FlowLayoutItem extends Stack { } export interface IFlowLayout extends IView { renderItem: (index: number) => FlowLayoutItem + itemCount: number + batchCount?: number - column?: number + + columnCount?: number + + columnSpace?: number + + rowSpace?: number } export class FlowLayout extends Superview implements IFlowLayout { @@ -40,7 +47,13 @@ export class FlowLayout extends Superview implements IFlowLayout { } @Property - column = 1 + columnCount = 2 + + @Property + columnSpace?: number + + @Property + rowSpace?: number @Property itemCount = 0 From 6a34f45d44d5a602c0cfc3ccf624a2283ebdc6f8 Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Thu, 28 Nov 2019 19:02:52 +0800 Subject: [PATCH 7/7] feat:add FlowLayout for Android --- .../main/java/pub/doric/DoricRegistry.java | 4 + .../doric/shader/flowlayout/FlowAdapter.java | 137 ++++++++++++++++ .../shader/flowlayout/FlowLayoutItemNode.java | 56 +++++++ .../shader/flowlayout/FlowLayoutNode.java | 146 ++++++++++++++++++ 4 files changed, 343 insertions(+) create mode 100644 Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowAdapter.java create mode 100644 Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutItemNode.java create mode 100644 Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutNode.java diff --git a/Android/doric/src/main/java/pub/doric/DoricRegistry.java b/Android/doric/src/main/java/pub/doric/DoricRegistry.java index 9919509f..b15f70fa 100644 --- a/Android/doric/src/main/java/pub/doric/DoricRegistry.java +++ b/Android/doric/src/main/java/pub/doric/DoricRegistry.java @@ -29,6 +29,8 @@ import pub.doric.refresh.RefreshableNode; import pub.doric.shader.HLayoutNode; import pub.doric.shader.ImageNode; import pub.doric.shader.ScrollerNode; +import pub.doric.shader.flowlayout.FlowLayoutItemNode; +import pub.doric.shader.flowlayout.FlowLayoutNode; import pub.doric.shader.list.ListItemNode; import pub.doric.shader.list.ListNode; import pub.doric.shader.RootNode; @@ -98,6 +100,8 @@ public class DoricRegistry { this.registerViewNode(SliderNode.class); this.registerViewNode(SlideItemNode.class); this.registerViewNode(RefreshableNode.class); + this.registerViewNode(FlowLayoutNode.class); + this.registerViewNode(FlowLayoutItemNode.class); initRegistry(this); } diff --git a/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowAdapter.java b/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowAdapter.java new file mode 100644 index 00000000..00c2f9fa --- /dev/null +++ b/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowAdapter.java @@ -0,0 +1,137 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package pub.doric.shader.flowlayout; + +import android.text.TextUtils; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.github.pengfeizhou.jscore.JSArray; +import com.github.pengfeizhou.jscore.JSDecoder; +import com.github.pengfeizhou.jscore.JSNull; +import com.github.pengfeizhou.jscore.JSObject; +import com.github.pengfeizhou.jscore.JSValue; + +import pub.doric.async.AsyncResult; +import pub.doric.shader.ViewNode; + +/** + * @Description: com.github.penfeizhou.doric.widget + * @Author: pengfei.zhou + * @CreateDate: 2019-11-12 + */ +class FlowAdapter extends RecyclerView.Adapter { + + private final FlowLayoutNode flowLayoutNode; + String renderItemFuncId; + int itemCount = 0; + int batchCount = 15; + SparseArray itemValues = new SparseArray<>(); + + FlowAdapter(FlowLayoutNode flowLayoutNode) { + this.flowLayoutNode = flowLayoutNode; + } + + @NonNull + @Override + public DoricViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + FlowLayoutItemNode node = (FlowLayoutItemNode) ViewNode.create(flowLayoutNode.getDoricContext(), "FlowLayoutItem"); + node.init(flowLayoutNode); + return new DoricViewHolder(node, node.getNodeView()); + } + + @Override + public void onBindViewHolder(@NonNull DoricViewHolder holder, int position) { + JSValue jsValue = getItemModel(position); + if (jsValue.isObject()) { + JSObject jsObject = jsValue.asObject(); + holder.flowLayoutItemNode.setId(jsObject.getProperty("id").asString().value()); + holder.flowLayoutItemNode.blend(jsObject.getProperty("props").asObject()); + } + } + + @Override + public int getItemCount() { + return itemCount; + } + + @Override + public int getItemViewType(int position) { + JSValue value = getItemModel(position); + if (value.isObject()) { + if (value.asObject().getProperty("identifier").isString()) { + return value.asObject().getProperty("identifier").asString().value().hashCode(); + } + } + return super.getItemViewType(position); + } + + private JSValue getItemModel(final int position) { + String id = itemValues.get(position); + if (TextUtils.isEmpty(id)) { + AsyncResult asyncResult = flowLayoutNode.callJSResponse( + "renderBunchedItems", + position, + batchCount); + try { + JSDecoder jsDecoder = asyncResult.synchronous().get(); + JSValue result = jsDecoder.decode(); + if (result.isArray()) { + JSArray jsArray = result.asArray(); + for (int i = 0; i < jsArray.size(); i++) { + JSObject itemModel = jsArray.get(i).asObject(); + String itemId = itemModel.getProperty("id").asString().value(); + itemValues.put(i + position, itemId); + flowLayoutNode.setSubModel(itemId, itemModel); + } + return flowLayoutNode.getSubModel(itemValues.get(position)); + } + } catch (Exception e) { + e.printStackTrace(); + } + return new JSNull(); + } else { + JSObject childModel = flowLayoutNode.getSubModel(id); + if (childModel == null) { + return new JSNull(); + } else { + return childModel; + } + } + } + + + void blendSubNode(JSObject subProperties) { + for (int i = 0; i < itemValues.size(); i++) { + if (subProperties.getProperty("id").asString().value().equals(itemValues.valueAt(i))) { + notifyItemChanged(i); + } + } + } + + static class DoricViewHolder extends RecyclerView.ViewHolder { + FlowLayoutItemNode flowLayoutItemNode; + + DoricViewHolder(FlowLayoutItemNode node, @NonNull View itemView) { + super(itemView); + flowLayoutItemNode = node; + } + } +} diff --git a/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutItemNode.java b/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutItemNode.java new file mode 100644 index 00000000..643f9b87 --- /dev/null +++ b/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutItemNode.java @@ -0,0 +1,56 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package pub.doric.shader.flowlayout; + +import android.widget.FrameLayout; + +import com.github.pengfeizhou.jscore.JSObject; +import com.github.pengfeizhou.jscore.JSValue; + +import pub.doric.DoricContext; +import pub.doric.extension.bridge.DoricPlugin; +import pub.doric.shader.StackNode; + +/** + * @Description: com.github.penfeizhou.doric.widget + * @Author: pengfei.zhou + * @CreateDate: 2019-11-12 + */ +@DoricPlugin(name = "FlowLayoutItem") +public class FlowLayoutItemNode extends StackNode { + public String identifier = ""; + + public FlowLayoutItemNode(DoricContext doricContext) { + super(doricContext); + this.mReusable = true; + } + + @Override + protected void blend(FrameLayout view, String name, JSValue prop) { + if ("identifier".equals(name)) { + this.identifier = prop.asString().value(); + } else { + super.blend(view, name, prop); + } + } + + @Override + public void blend(JSObject jsObject) { + super.blend(jsObject); + getNodeView().getLayoutParams().width = getLayoutParams().width; + getNodeView().getLayoutParams().height = getLayoutParams().height; + } +} diff --git a/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutNode.java b/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutNode.java new file mode 100644 index 00000000..f2b46b09 --- /dev/null +++ b/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutNode.java @@ -0,0 +1,146 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package pub.doric.shader.flowlayout; + +import android.graphics.Rect; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.StaggeredGridLayoutManager; + +import com.github.pengfeizhou.jscore.JSObject; +import com.github.pengfeizhou.jscore.JSValue; + +import pub.doric.DoricContext; +import pub.doric.extension.bridge.DoricPlugin; +import pub.doric.shader.SuperNode; +import pub.doric.shader.ViewNode; +import pub.doric.utils.DoricUtils; + +/** + * @Description: pub.doric.shader.flowlayout + * @Author: pengfei.zhou + * @CreateDate: 2019-11-28 + */ +@DoricPlugin(name = "FlowLayout") +public class FlowLayoutNode extends SuperNode { + private final FlowAdapter flowAdapter; + private final StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager( + 2, + StaggeredGridLayoutManager.VERTICAL); + private int columnSpace = 0; + private int rowSpace = 0; + private final RecyclerView.ItemDecoration spacingItemDecoration = new RecyclerView.ItemDecoration() { + @Override + public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { + outRect.set(columnSpace / 2, rowSpace / 2, columnSpace / 2, rowSpace / 2); + } + }; + + public FlowLayoutNode(DoricContext doricContext) { + super(doricContext); + this.flowAdapter = new FlowAdapter(this); + } + + @Override + public ViewNode getSubNodeById(String id) { + RecyclerView.LayoutManager manager = mView.getLayoutManager(); + if (manager == null) { + return null; + } + for (int i = 0; i < manager.getChildCount(); i++) { + View view = manager.getChildAt(i); + if (view == null) { + continue; + } + FlowAdapter.DoricViewHolder viewHolder = (FlowAdapter.DoricViewHolder) mView.getChildViewHolder(view); + if (id.equals(viewHolder.flowLayoutItemNode.getId())) { + return viewHolder.flowLayoutItemNode; + } + } + return null; + } + + @Override + protected void blend(RecyclerView view, String name, JSValue prop) { + switch (name) { + case "columnSpace": + columnSpace = DoricUtils.dp2px(prop.asNumber().toFloat()); + mView.setPadding(-columnSpace / 2, mView.getPaddingTop(), -columnSpace / 2, mView.getPaddingBottom()); + break; + case "rowSpace": + rowSpace = DoricUtils.dp2px(prop.asNumber().toFloat()); + mView.setPadding(mView.getPaddingLeft(), -rowSpace / 2, mView.getPaddingRight(), -rowSpace / 2); + break; + case "columnCount": + staggeredGridLayoutManager.setSpanCount(prop.asNumber().toInt()); + break; + case "itemCount": + this.flowAdapter.itemCount = prop.asNumber().toInt(); + break; + case "renderItem": + this.flowAdapter.renderItemFuncId = prop.asString().value(); + // If reset renderItem,should reset native cache. + this.flowAdapter.itemValues.clear(); + clearSubModel(); + break; + case "batchCount": + this.flowAdapter.batchCount = prop.asNumber().toInt(); + break; + default: + super.blend(view, name, prop); + break; + } + } + + @Override + public void blend(JSObject jsObject) { + super.blend(jsObject); + if (mView != null) { + mView.post(new Runnable() { + @Override + public void run() { + flowAdapter.notifyDataSetChanged(); + } + }); + } + } + + @Override + protected void blendSubNode(JSObject subProperties) { + String viewId = subProperties.getProperty("id").asString().value(); + ViewNode node = getSubNodeById(viewId); + if (node != null) { + node.blend(subProperties.getProperty("props").asObject()); + } else { + JSObject oldModel = getSubModel(viewId); + if (oldModel != null) { + recursiveMixin(subProperties, oldModel); + } + flowAdapter.blendSubNode(subProperties); + } + } + + @Override + protected RecyclerView build() { + RecyclerView recyclerView = new RecyclerView(getContext()); + recyclerView.setLayoutManager(staggeredGridLayoutManager); + recyclerView.setAdapter(flowAdapter); + recyclerView.addItemDecoration(spacingItemDecoration); + return recyclerView; + } +}