feat:iOS FlowLayout

This commit is contained in:
pengfei.zhou 2019-11-28 17:41:12 +08:00
parent 0031d7a17d
commit 8a15297ebd
10 changed files with 274 additions and 22 deletions

View File

@ -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"; import { colors, label } from "./utils";
const imageUrls = [ const imageUrls = [
@ -18,11 +18,21 @@ class FlowDemo extends Panel {
flowlayout({ flowlayout({
layoutConfig: layoutConfig().atmost(), layoutConfig: layoutConfig().atmost(),
itemCount: 500, itemCount: 500,
columnCount: 3,
columnSpace: 10,
rowSpace: 10,
renderItem: (idx) => { renderItem: (idx) => {
return new FlowLayoutItem().apply({ return new FlowLayoutItem().apply({
bgColor: colors[idx % colors.length], bgColor: colors[idx % colors.length],
width: 200, height: 50 + (idx % 3) * 20,
height: 50 + idx * 10, layoutConfig: layoutConfig().w(LayoutSpec.AT_MOST),
}).also(it => {
it.addChild(text({
text: `${idx}`,
textColor: Color.WHITE,
textSize: 20,
layoutConfig: layoutConfig().wrap().a(Gravity.Center)
}))
}) })
}, },
}) })

View File

@ -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. // Created by pengfei.zhou on 2019/11/26.
// //

View File

@ -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. // Created by pengfei.zhou on 2019/11/26.
// //

View File

@ -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. // Created by pengfei.zhou on 2019/11/26.
// //

View File

@ -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. // Created by pengfei.zhou on 2019/11/26.
// //

View File

@ -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. // Created by pengfei.zhou on 2019/11/28.
// //

View File

@ -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. // Created by pengfei.zhou on 2019/11/28.
// //

View File

@ -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. // Created by pengfei.zhou on 2019/11/28.
// //

View File

@ -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. // Created by pengfei.zhou on 2019/11/28.
// //
@ -7,6 +22,99 @@
#import "DoricExtensions.h" #import "DoricExtensions.h"
#import <JavaScriptCore/JavaScriptCore.h> #import <JavaScriptCore/JavaScriptCore.h>
@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 <NSNumber *, NSNumber *> *columnHeightInfo;
@property(nonatomic, weak) id <DoricFlowLayoutDelegate> 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 @interface DoricFlowLayoutViewCell : UICollectionViewCell
@property(nonatomic, strong) DoricFlowLayoutItemNode *viewNode; @property(nonatomic, strong) DoricFlowLayoutItemNode *viewNode;
@end @end
@ -38,11 +146,14 @@ - (void)layoutSelf:(CGSize)targetSize {
} }
@end @end
@interface DoricFlowLayoutNode () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout> @interface DoricFlowLayoutNode () <UICollectionViewDataSource, UICollectionViewDelegate, DoricFlowLayoutDelegate>
@property(nonatomic, strong) NSMutableDictionary <NSNumber *, NSString *> *itemViewIds; @property(nonatomic, strong) NSMutableDictionary <NSNumber *, NSString *> *itemViewIds;
@property(nonatomic, strong) NSMutableDictionary <NSNumber *, NSValue *> *itemSizeInfo; @property(nonatomic, strong) NSMutableDictionary <NSNumber *, NSValue *> *itemSizeInfo;
@property(nonatomic, assign) NSUInteger itemCount; @property(nonatomic, assign) NSUInteger itemCount;
@property(nonatomic, assign) NSUInteger batchCount; @property(nonatomic, assign) NSUInteger batchCount;
@property(nonatomic, assign) NSUInteger columnCount;
@property(nonatomic, assign) CGFloat columnSpace;
@property(nonatomic, assign) CGFloat rowSpace;
@end @end
@implementation DoricFlowLayoutNode @implementation DoricFlowLayoutNode
@ -51,14 +162,14 @@ - (instancetype)initWithContext:(DoricContext *)doricContext {
_itemViewIds = [NSMutableDictionary new]; _itemViewIds = [NSMutableDictionary new];
_itemSizeInfo = [NSMutableDictionary new]; _itemSizeInfo = [NSMutableDictionary new];
_batchCount = 15; _batchCount = 15;
_columnCount = 2;
} }
return self; return self;
} }
- (UICollectionView *)build { - (UICollectionView *)build {
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; DoricFlowLayout *flowLayout = [[DoricFlowLayout alloc] init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical]; flowLayout.delegate = self;
return [[[DoricFlowLayoutView alloc] initWithFrame:CGRectZero return [[[DoricFlowLayoutView alloc] initWithFrame:CGRectZero
collectionViewLayout:flowLayout] collectionViewLayout:flowLayout]
also:^(UICollectionView *it) { also:^(UICollectionView *it) {
@ -71,7 +182,17 @@ - (UICollectionView *)build {
} }
- (void)blendView:(UICollectionView *)view forPropName:(NSString *)name propValue:(id)prop { - (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.itemCount = [prop unsignedIntegerValue];
[self.view reloadData]; [self.view reloadData];
} else if ([@"renderItem" isEqualToString:name]) { } else if ([@"renderItem" isEqualToString:name]) {
@ -146,10 +267,7 @@ - (void)callItem:(NSUInteger)position size:(CGSize)size {
return; return;
} }
self.itemSizeInfo[@(position)] = [NSValue valueWithCGSize:size]; self.itemSizeInfo[@(position)] = [NSValue valueWithCGSize:size];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:position inSection:0]; [self.view.collectionViewLayout invalidateLayout];
[UIView performWithoutAnimation:^{
[self.view reloadItemsAtIndexPaths:@[indexPath]];
}];
} }
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
@ -170,27 +288,33 @@ - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collection
DoricFlowLayoutItemNode *node = cell.viewNode; DoricFlowLayoutItemNode *node = cell.viewNode;
node.viewId = model[@"id"]; node.viewId = model[@"id"];
[node blend:props]; [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]; [node.view layoutSelf:size];
[self callItem:position size:size]; [self callItem:position size:size];
return cell; return cell;
} }
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { - (CGFloat)doricFlowLayoutItemHeightAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger position = (NSUInteger) indexPath.row; NSUInteger position = (NSUInteger) indexPath.row;
NSValue *value = self.itemSizeInfo[@(position)]; NSValue *value = self.itemSizeInfo[@(position)];
if (value) { if (value) {
return [value CGSizeValue]; return [value CGSizeValue].height;
} else { } else {
return CGSizeMake(100, 100); return 100;
} }
} }
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { - (CGFloat)doricFlowLayoutColumnSpace {
return 0; return self.columnSpace;
} }
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { - (CGFloat)doricFlowLayoutRowSpace {
return 0; return self.rowSpace;
} }
- (NSInteger)doricFlowLayoutColumnCount {
return self.columnCount;
}
@end @end

View File

@ -26,9 +26,16 @@ export class FlowLayoutItem extends Stack {
} }
export interface IFlowLayout extends IView { export interface IFlowLayout extends IView {
renderItem: (index: number) => FlowLayoutItem renderItem: (index: number) => FlowLayoutItem
itemCount: number itemCount: number
batchCount?: number batchCount?: number
column?: number
columnCount?: number
columnSpace?: number
rowSpace?: number
} }
export class FlowLayout extends Superview implements IFlowLayout { export class FlowLayout extends Superview implements IFlowLayout {
@ -40,7 +47,13 @@ export class FlowLayout extends Superview implements IFlowLayout {
} }
@Property @Property
column = 1 columnCount = 2
@Property
columnSpace?: number
@Property
rowSpace?: number
@Property @Property
itemCount = 0 itemCount = 0