想看UICollectionView基礎(chǔ)使用的可以先看我的另一篇文章。這篇主寫關(guān)于UIollectionViewLayout自定義布局的一些常用方法,瀑布流布局的自定義,包括頭尾試圖的添加,插入刪除動(dòng)畫,還有9.0后移動(dòng)動(dòng)態(tài)布局的使用。
UICollectionViewLayout自定義常用的幾個(gè)方法
//預(yù)布局方法 所有的布局應(yīng)該寫在這里面
- (void)prepareLayout
//此方法應(yīng)該返回當(dāng)前屏幕正在顯示的視圖(cell 頭尾視圖)的布局屬性集合(UICollectionViewLayoutAttributes 對(duì)象集合)
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
//根據(jù)indexPath去對(duì)應(yīng)的UICollectionViewLayoutAttributes 這個(gè)是取值的,要重寫,在移動(dòng)刪除的時(shí)候系統(tǒng)會(huì)調(diào)用改方法重新去UICollectionViewLayoutAttributes然后布局
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
//返回當(dāng)前的ContentSize
- (CGSize)collectionViewContentSize
//是否重新布局
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
//這4個(gè)方法用來處理插入、刪除和移動(dòng)cell時(shí)的一些動(dòng)畫 瀑布流代碼詳解
- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
- (UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
- (nullable UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
- (void)finalizeCollectionViewUpdates
//9.0之后處理移動(dòng)相關(guān)
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForInteractivelyMovingItems:(NSArray<NSIndexPath *> *)targetIndexPaths withTargetPosition:(CGPoint)targetPosition previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths previousPosition:(CGPoint)previousPosition NS_AVAILABLE_IOS(9_0)
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:(NSArray<NSIndexPath *> *)indexPaths previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths movementCancelled:(BOOL)movementCancelled NS_AVAILABLE_IOS(9_0)
瀑布流布局詳解,先貼代碼
.h文件
#import <UIKit/UIKit.h>
UIKIT_EXTERN NSString *const AC_UICollectionElementKindSectionHeader;
UIKIT_EXTERN NSString *const AC_UICollectionElementKindSectionFooter;
@class AC_WaterCollectionViewLayout;
@protocol AC_WaterCollectionViewLayoutDelegate <NSObject>
//代理取cell 的高
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(AC_WaterCollectionViewLayout *)layout heightOfItemAtIndexPath:(NSIndexPath *)indexPath itemWidth:(CGFloat)itemWidth;
//處理移動(dòng)相關(guān)的數(shù)據(jù)源
- (void)moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath;
@end
@interface AC_WaterCollectionViewLayout : UICollectionViewLayout
@property (assign, nonatomic) NSInteger numberOfColumns;//瀑布流有列
@property (assign, nonatomic) CGFloat cellDistance;//cell之間的間距
@property (assign, nonatomic) CGFloat topAndBottomDustance;//cell 到頂部 底部的間距
@property (assign, nonatomic) CGFloat headerViewHeight;//頭視圖的高度
@property (assign, nonatomic) CGFloat footViewHeight;//尾視圖的高度
@property(nonatomic, weak) id<AC_WaterCollectionViewLayoutDelegate> delegate;
@end
.h文件沒有太多東西,看注釋應(yīng)該都清楚。跟UICollectionViewFlowLayout不同的是沒有方向設(shè)置,因?yàn)槠俨剂鳈M向基本少見,所以所以頭尾視圖也由CGSize改成CGFloat,
.m文件
#import "AC_WaterCollectionViewLayout.h"
NSString *const AC_UICollectionElementKindSectionHeader = @"AC_HeadView";
NSString *const AC_UICollectionElementKindSectionFooter = @"AC_FootView";
@interface AC_WaterCollectionViewLayout()
@property (strong, nonatomic) NSMutableDictionary *cellLayoutInfo;//保存cell的布局
@property (strong, nonatomic) NSMutableDictionary *headLayoutInfo;//保存頭視圖的布局
@property (strong, nonatomic) NSMutableDictionary *footLayoutInfo;//保存尾視圖的布局
@property (assign, nonatomic) CGFloat startY;//記錄開始的Y
@property (strong, nonatomic) NSMutableDictionary *maxYForColumn;//記錄瀑布流每列最下面那個(gè)cell的底部y值
@property (strong, nonatomic) NSMutableArray *shouldanimationArr;//記錄需要添加動(dòng)畫的NSIndexPath
@end
@implementation AC_WaterCollectionViewLayout
- (instancetype)init
{
self = [super init];
if (self) {
self.numberOfColumns = 3;
self.topAndBottomDustance = 10;
self.cellDistance = 10;
_headerViewHeight = 0;
_footViewHeight = 0;
self.startY = 0;
self.maxYForColumn = [NSMutableDictionary dictionary];
self.shouldanimationArr = [NSMutableArray array];
self.cellLayoutInfo = [NSMutableDictionary dictionary];
self.headLayoutInfo = [NSMutableDictionary dictionary];
self.footLayoutInfo = [NSMutableDictionary dictionary];
}
return self;
}
- (void)prepareLayout
{
[super prepareLayout];
//重新布局需要清空
[self.cellLayoutInfo removeAllObjects];
[self.headLayoutInfo removeAllObjects];
[self.footLayoutInfo removeAllObjects];
[self.maxYForColumn removeAllObjects];
self.startY = 0;
CGFloat viewWidth = self.collectionView.frame.size.width;
//代理里面只取了高度,所以cell的寬度有列數(shù)還有cell的間距計(jì)算出來
CGFloat itemWidth = (viewWidth - self.cellDistance*(self.numberOfColumns + 1))/self.numberOfColumns;
//取有多少個(gè)section
NSInteger sectionsCount = [self.collectionView numberOfSections];
for (NSInteger section = 0; section < sectionsCount; section++) {
//存儲(chǔ)headerView屬性
NSIndexPath *supplementaryViewIndexPath = [NSIndexPath indexPathForRow:0 inSection:section];
//頭視圖的高度不為0并且根據(jù)代理方法能取到對(duì)應(yīng)的頭視圖的時(shí)候,添加對(duì)應(yīng)頭視圖的布局對(duì)象
if (_headerViewHeight>0 && [self.collectionView.dataSource respondsToSelector:@selector(collectionView: viewForSupplementaryElementOfKind: atIndexPath:)]) {
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:AC_UICollectionElementKindSectionHeader withIndexPath:supplementaryViewIndexPath];
//設(shè)置frame
attribute.frame = CGRectMake(0, self.startY, self.collectionView.frame.size.width, _headerViewHeight);
//保存布局對(duì)象
self.headLayoutInfo[supplementaryViewIndexPath] = attribute;
//設(shè)置下個(gè)布局對(duì)象的開始Y值
self.startY = self.startY + _headerViewHeight + _topAndBottomDustance;
}else{
//沒有頭視圖的時(shí)候,也要設(shè)置section的第一排cell到頂部的距離
self.startY += _topAndBottomDustance;
}
//將Section第一排cell的frame的Y值進(jìn)行設(shè)置
for (int i = 0; i < _numberOfColumns; i++) {
self.maxYForColumn[@(i)] = @(self.startY);
}
//計(jì)算cell的布局
//取出section有多少個(gè)row
NSInteger rowsCount = [self.collectionView numberOfItemsInSection:section];
//分別計(jì)算設(shè)置每個(gè)cell的布局對(duì)象
for (NSInteger row = 0; row < rowsCount; row++) {
NSIndexPath *cellIndePath =[NSIndexPath indexPathForItem:row inSection:section];
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:cellIndePath];
//計(jì)算當(dāng)前的cell加到哪一列(瀑布流是加載到最短的一列)
CGFloat y = [self.maxYForColumn[@(0)] floatValue];
NSInteger currentRow = 0;
for (int i = 1; i < _numberOfColumns; i++) {
if ([self.maxYForColumn[@(i)] floatValue] < y) {
y = [self.maxYForColumn[@(i)] floatValue];
currentRow = i;
}
}
//計(jì)算x值
CGFloat x = self.cellDistance + (self.cellDistance + itemWidth)*currentRow;
//根據(jù)代理去當(dāng)前cell的高度 因?yàn)楫?dāng)前是采用通過列數(shù)計(jì)算的寬度,高度根據(jù)圖片的原始寬高比進(jìn)行設(shè)置的
CGFloat height = [(id<AC_WaterCollectionViewLayoutDelegate>)self.delegate collectionView:self.collectionView layout:self heightOfItemAtIndexPath:cellIndePath itemWidth:itemWidth];
//設(shè)置當(dāng)前cell布局對(duì)象的frame
attribute.frame = CGRectMake(x, y, itemWidth, height);
//重新設(shè)置當(dāng)前列的Y值
y = y + self.cellDistance + height;
self.maxYForColumn[@(currentRow)] = @(y);
//保留cell的布局對(duì)象
self.cellLayoutInfo[cellIndePath] = attribute;
//當(dāng)是section的最后一個(gè)cell是,取出最后一排cell的底部Y值 設(shè)置startY 決定下個(gè)視圖對(duì)象的起始Y值
if (row == rowsCount -1) {
CGFloat maxY = [self.maxYForColumn[@(0)] floatValue];
for (int i = 1; i < _numberOfColumns; i++) {
if ([self.maxYForColumn[@(i)] floatValue] > maxY) {
NSLog(@"%f", [self.maxYForColumn[@(i)] floatValue]);
maxY = [self.maxYForColumn[@(i)] floatValue];
}
}
self.startY = maxY - self.cellDistance + self.topAndBottomDustance;
}
}
//存儲(chǔ)footView屬性
//尾視圖的高度不為0并且根據(jù)代理方法能取到對(duì)應(yīng)的尾視圖的時(shí)候,添加對(duì)應(yīng)尾視圖的布局對(duì)象
if (_footViewHeight>0 && [self.collectionView.dataSource respondsToSelector:@selector(collectionView: viewForSupplementaryElementOfKind: atIndexPath:)]) {
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:AC_UICollectionElementKindSectionFooter withIndexPath:supplementaryViewIndexPath];
attribute.frame = CGRectMake(0, self.startY, self.collectionView.frame.size.width, _footViewHeight);
self.footLayoutInfo[supplementaryViewIndexPath] = attribute;
self.startY = self.startY + _footViewHeight;
}
}
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *allAttributes = [NSMutableArray array];
//添加當(dāng)前屏幕可見的cell的布局
[self.cellLayoutInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attribute, BOOL *stop) {
if (CGRectIntersectsRect(rect, attribute.frame)) {
[allAttributes addObject:attribute];
}
}];
//添加當(dāng)前屏幕可見的頭視圖的布局
[self.headLayoutInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attribute, BOOL *stop) {
if (CGRectIntersectsRect(rect, attribute.frame)) {
[allAttributes addObject:attribute];
}
}];
//添加當(dāng)前屏幕可見的尾部的布局
[self.footLayoutInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attribute, BOOL *stop) {
if (CGRectIntersectsRect(rect, attribute.frame)) {
[allAttributes addObject:attribute];
}
}];
return allAttributes;
}
//插入cell的時(shí)候系統(tǒng)會(huì)調(diào)用改方法
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attribute = self.cellLayoutInfo[indexPath];
return attribute;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attribute = nil;
if ([elementKind isEqualToString:AC_UICollectionElementKindSectionHeader]) {
attribute = self.headLayoutInfo[indexPath];
}else if ([elementKind isEqualToString:AC_UICollectionElementKindSectionFooter]){
attribute = self.footLayoutInfo[indexPath];
}
return attribute;
}
- (CGSize)collectionViewContentSize
{
return CGSizeMake(self.collectionView.frame.size.width, MAX(self.startY, self.collectionView.frame.size.height));
}
- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
{
[super prepareForCollectionViewUpdates:updateItems];
NSMutableArray *indexPaths = [NSMutableArray array];
for (UICollectionViewUpdateItem *updateItem in updateItems) {
switch (updateItem.updateAction) {
case UICollectionUpdateActionInsert:
[indexPaths addObject:updateItem.indexPathAfterUpdate];
break;
case UICollectionUpdateActionDelete:
[indexPaths addObject:updateItem.indexPathBeforeUpdate];
break;
case UICollectionUpdateActionMove:
// [indexPaths addObject:updateItem.indexPathBeforeUpdate];
// [indexPaths addObject:updateItem.indexPathAfterUpdate];
break;
default:
NSLog(@"unhandled case: %@", updateItem);
break;
}
}
self.shouldanimationArr = indexPaths;
}
//對(duì)應(yīng)UICollectionViewUpdateItem 的indexPathBeforeUpdate 設(shè)置調(diào)用
- (UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
if ([self.shouldanimationArr containsObject:itemIndexPath]) {
UICollectionViewLayoutAttributes *attr = self.cellLayoutInfo[itemIndexPath];
attr.transform = CGAffineTransformRotate(CGAffineTransformMakeScale(0.2, 0.2), M_PI);
attr.center = CGPointMake(CGRectGetMidX(self.collectionView.bounds), CGRectGetMaxY(self.collectionView.bounds));
attr.alpha = 1;
[self.shouldanimationArr removeObject:itemIndexPath];
return attr;
}
return nil;
}
//對(duì)應(yīng)UICollectionViewUpdateItem 的indexPathAfterUpdate 設(shè)置調(diào)用
- (nullable UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
if ([self.shouldanimationArr containsObject:itemIndexPath]) {
UICollectionViewLayoutAttributes *attr = self.cellLayoutInfo[itemIndexPath];
attr.transform = CGAffineTransformRotate(CGAffineTransformMakeScale(2, 2), 0);
// attr.center = CGPointMake(CGRectGetMidX(self.collectionView.bounds), CGRectGetMaxY(self.collectionView.bounds));
attr.alpha = 0;
[self.shouldanimationArr removeObject:itemIndexPath];
return attr;
}
return nil;
}
- (void)finalizeCollectionViewUpdates
{
self.shouldanimationArr = nil;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
CGRect oldBounds = self.collectionView.bounds;
if (!CGSizeEqualToSize(oldBounds.size, newBounds.size)) {
return YES;
}
return NO;
//
// return YES;
}
//移動(dòng)相關(guān)
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForInteractivelyMovingItems:(NSArray<NSIndexPath *> *)targetIndexPaths withTargetPosition:(CGPoint)targetPosition previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths previousPosition:(CGPoint)previousPosition NS_AVAILABLE_IOS(9_0)
{
UICollectionViewLayoutInvalidationContext *context = [super invalidationContextForInteractivelyMovingItems:targetIndexPaths withTargetPosition:targetPosition previousIndexPaths:previousIndexPaths previousPosition:previousPosition];
if([self.delegate respondsToSelector:@selector(moveItemAtIndexPath: toIndexPath:)]){
[self.delegate moveItemAtIndexPath:previousIndexPaths[0] toIndexPath:targetIndexPaths[0]];
}
return context;
}
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:(NSArray<NSIndexPath *> *)indexPaths previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths movementCancelled:(BOOL)movementCancelled NS_AVAILABLE_IOS(9_0)
{
UICollectionViewLayoutInvalidationContext *context = [super invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:indexPaths previousIndexPaths:previousIndexPaths movementCancelled:movementCancelled];
if(!movementCancelled){
}
return context;
}
@end
(void)prepareLayout
方法里面的布局注釋我應(yīng)該寫的很詳細(xì)了,看不懂的多看2遍。這里我再詳細(xì)說一下startY跟maxYForColumn這兩個(gè)屬性。startY值主要處理下一個(gè)視圖對(duì)象的Y值。maxYForColumn保存當(dāng)前已經(jīng)計(jì)算了的最下一列的cell的bottom值。布局cell的時(shí)候,cell的Y值
取maxYForColumn里面的最小值。當(dāng)section里面的cell全部布局完的時(shí)候,接下來布局尾視圖的時(shí)候,startY應(yīng)該取maxYForColumn里面的最大值。(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
這個(gè)方法需要返回當(dāng)前界面可見的視圖的布局對(duì)象集合,很多線性布局的效果都是在這個(gè)方法里面處理,在下面的UIollectionViewFlowLayout會(huì)有一些常見效果的處理代碼。(void)prepareForCollectionViewUpdates:(NSArray )updateItems
當(dāng)調(diào)用插入、刪除和移動(dòng)相關(guān)的api的時(shí)候回調(diào)用該方法(對(duì)照上面的代碼看)其中的indexPathBeforeUpdate跟indexPathAfterUpdat分別對(duì)應(yīng)
(UICollectionViewLayoutAttributes)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath )itemIndexPath
(UICollectionViewLayoutAttributes)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
處理相對(duì)應(yīng)的UICollectionViewLayoutAttributes屬性變動(dòng),我的代碼中插入是添加的indexPathAfterUpdate,刪除是添加的indexPathBeforeUpdate。
關(guān)于移動(dòng)相關(guān)的,系統(tǒng)提供的只能9.0之后,如果想9.0之前使用必須的自定義,可以查看這篇文章可拖拽重排的CollectionView自己研究。添加移動(dòng)相關(guān)的代碼在ctr處理,回調(diào)也在ctr里面處理,先貼上代碼
//添加cell長(zhǎng)按手勢(shì)
UILongPressGestureRecognizer *longGest = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longGest:)];
[self.waterCollectionView addGestureRecognizer:longGest];
//對(duì)應(yīng)的action
- (void)longGest:(UILongPressGestureRecognizer *)gest
{
switch (gest.state) {
case UIGestureRecognizerStateBegan:
{
NSIndexPath *touchIndexPath = [self.waterCollectionView indexPathForItemAtPoint:[gest locationInView:self.waterCollectionView]];
if (touchIndexPath) {
[self.waterCollectionView beginInteractiveMovementForItemAtIndexPath:touchIndexPath];
}else{
break;
}
}
break;
case UIGestureRecognizerStateChanged:
{
[self.waterCollectionView updateInteractiveMovementTargetPosition:[gest locationInView:gest.view]];
}
break;
case UIGestureRecognizerStateEnded:
{
[self.waterCollectionView endInteractiveMovement];
}
break;
default:
break;
}
}
//移動(dòng)對(duì)應(yīng)的回調(diào)
//系統(tǒng)的
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS(9_0)
{
// if(sourceIndexPath.row != destinationIndexPath.row){
// NSString *value = self.imageArr[sourceIndexPath.row] ;
// [self.imageArr removeObjectAtIndex:sourceIndexPath.row];
// [self.imageArr insertObject:value atIndex:destinationIndexPath.row];
// NSLog(@"from:%ld to:%ld", sourceIndexPath.row, destinationIndexPath.row);
// }
}
//自定義的回調(diào)
- (void)moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath
{
if(sourceIndexPath.row != destinationIndexPath.row){
NSString *value = self.imageArr[sourceIndexPath.row];
[self.imageArr removeObjectAtIndex:sourceIndexPath.row];
[self.imageArr insertObject:value atIndex:destinationIndexPath.row];
NSLog(@"from:%ld to:%ld", sourceIndexPath.row, destinationIndexPath.row);
}
}
當(dāng)長(zhǎng)按后移動(dòng)手指的時(shí)候系統(tǒng)會(huì)一直調(diào)用
invalidationContextForInteractivelyMovingItems:withTargetPosition:previousIndexPaths: previousPosition:因?yàn)槠俨剂鞯拿總€(gè)cell的frame大小不相同所以要通過代理方法不斷的更新數(shù)據(jù)源的順序,然后系統(tǒng)不斷調(diào)用prepareLayout方法進(jìn)行重新布局,之前我是采用的系統(tǒng)提供的代理collectionView moveItemAtIndexPath: toIndexPath:來處理數(shù)據(jù)源的,但是發(fā)現(xiàn)只有布局的時(shí)候是正常的,然是松開手指后,從新加載數(shù)據(jù)發(fā)現(xiàn)亂了,然后打印數(shù)據(jù)源。發(fā)現(xiàn)數(shù)據(jù)源的順序并沒有改變,還是之前的順序。
后來發(fā)現(xiàn)問題出現(xiàn)在當(dāng)移動(dòng)手勢(shì)結(jié)束的時(shí)候調(diào)用的方法 [self.waterCollectionView endInteractiveMovement];
以下xcode對(duì)該方法的介紹
Ends interactive movement tracking and moves the target item to its new location.
Call this method upon the successful completion of movement tracking for a item. For example, when using a gesture recognizer to track user interactions, call this method upon the successful completion of the gesture. Calling this method lets the collection view know to end tracking and move the item to its new location permanently. The collection view responds by calling the collectionView:moveItemAtIndexPath:toIndexPath: method of its data source to ensure that your data structures are updated.
也就是說當(dāng)手勢(shì)結(jié)束的時(shí)候系統(tǒng)會(huì)掉一次collectionView:moveItemAtIndexPath:toIndexPath:,該操作導(dǎo)致移動(dòng)的時(shí)候進(jìn)行的變換的順序又變回來了,所以只好自己寫了一個(gè)代理方法處理數(shù)據(jù)源,沒管系統(tǒng)的回調(diào)。
運(yùn)行效果
相關(guān)代碼下載