实现自定义延迟加载的滚动视图
数据量大,单个数据处理耗时的情况下,一个延迟加载的自定义视图就必不可少了。我们希望这样的视图可以
在需要用到某个数据源的时候再去装载这个数据并处理之。而不是视图已启动就把全部的数据都加载上去。如果
全部加载再加上服务器请求(如果有的话)的时间绝对超过用户忍耐的极限。随之应用必然遭弃!
UITableView也有一定的复用机制。大概的机理是:每次到一个Cell出现在可视区域的时候,它会从已经用过的Cell中
取出来一个, 如果还没有用过的Cell(也就是说这个返回值是空)就创建一个。当一个Cell划出可视范围的时候就把这个
Cell放到备用数组中供以后使用。如下代码中就有所体现:
1 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{ 2 static NSString *CellIdentifier = @"HomeCellIdentifier"; 3 4 MyHomeCell *cell = (MyHomeCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 5 MyDataSource *content = (MyDataSource *)self.items[indexPath.row]; 6 if (!cell) { 7 cell = [[[MyHomeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; 8 } 9 [cell setContent:content]; 10 11 12 return cell; 13 }
这里没有的就是回收不在显示范围的Cell的步骤,其代码应该是这样的:
1 - (void)recycleView:(UITableViewCell *)cell 2 { 3 if (!cell) 4 { 5 return; 6 } 7 8 [cell retain]; 9 [cell removeFromSuperview]; //从主界面中删除 10 [_visibleViews removeObject:cell]; //从可视的数组中删除 11 [_reuseableViews addObject:cell]; //放到回收数组中 12 [cell release]; 13 }
dequeueReusableCellWithIdentifier这个方法都做了些神马呢,这里有一个模拟:
1 - (UITableViewCell *)dequeueReusableView 2 { 3 UITableViewCell *reuseableCell = [_reuseableViews anyObject]; 4 if (reuseableCell) 5 { 6 [[reuseableCell retain] autorelease]; 7 [_reuseableViews removeObject:reuseableCell]; 8 } 9 return reuseableCell; 10 }
从中可以看出。UITableView的复用机制包括两个数组:一个存放显示区域中的Cell,一个存放不在显示区域中的Cell。
当然UITableView实际的服用机制要比这个复杂。比如我们没有提到的CellIdentifier。这个与本文的中心有所偏离,各位可以自行研究。
UITableView虽然好用,但是不能满足所有的需求。所以很多人集成UIScrollView实现滚动视图。但是机制都和UITableView差不多。
实现出来的效果也是手指快速从屏幕上划过,一堆的Cell一样的视图轱辘个没完。
那么如何能够一次滑动只滚动一个“Cell”,或者说是翻一页。而且同时加载这一页对应的数据呢?
各位应该都看过杂技的一个最简单的节目,几个球在空中扔来扔去的。这个可以简单到只用两个视图交替展示一个旧的和一个新的视图,
从一个方向依次滚动,然后复位,装填一旧一新的数据,然后再滚动,再复位。在用户的眼前看到的可以是一个无穷循环播放的视图。
在这个视图初始化的时候:
1 _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)]; 2 [self addGestureRecognizer:_panGesture]; 3 _panGesture.delegate = self; 4 [_panGesture release]; 5 6 _leftView = [[UIView alloc] initWithFrame:CGRectMake(0 7 , 0 8 , UISCREEN_MAINSCREEN_WIDTH 9 , UISCREEN_MAINSCREEN_HEIGHT)]; 10 _leftView.backgroundColor = [UIColor whiteColor]; 11 _rightView = [[UIView alloc] initWithFrame:CGRectMake(UISCREEN_MAINSCREEN_WIDTH 12 , 0 13 , UISCREEN_MAINSCREEN_WIDTH 14 , UISCREEN_MAINSCREEN_HEIGHT)]; 15 _rightView.backgroundColor = [UIColor whiteColor]; 16 17 [self addSubview:_leftView]; 18 [self addSubview:_rightView];
UIPanGestureRecognizer是必须的,没有这个的话无法扑捉用户的手势操作。整个视图的结构是一个视图在最下面扑捉用户手势并作为其他附属视图的container。
另外还有两个轮换的子视图响应用户操作。
当用户操作的时候如此处理:
1 - (void)panAction:(id)sender{ 2 switch (_panGesture.state) { 3 case UIGestureRecognizerStateBegan: 4 _startPoint = self.center; 5 case UIGestureRecognizerStateChanged:{ 6 // CGPoint translatedPoint = [panGesture translationInView:self]; 7 // _leftView.center = CGPointMake(_startPoint.x + translatedPoint.x, 8 // _startPoint.y); 9 // _rightView.center = CGPointMake(_startPoint.x + translatedPoint.x, 10 // _startPoint.y); 11 break; 12 } 13 case UIGestureRecognizerStateCancelled: 14 case UIGestureRecognizerStateEnded:{ 15 CGPoint translation = [_panGesture translationInView:self]; 16 if (abs(translation.x) < kPanGestureDistance) { 17 return; 18 } 19 20 HBRRotationViewDirection moveDirection; 21 22 if (translation.x > _startPoint.x) { 23 moveDirection = HBRRotationViewDirectionRight; 24 _dataSourceIndex = 0; 25 } 26 else{ 27 moveDirection = HBRRotationViewDirectionLeft; 28 // if (_dataSourceIndex-- == 0) { 29 _dataSourceIndex++; 30 // } 31 } 32 33 if (_isAnimationExecuting) { 34 return; 35 } 36 37 _isAnimationExecuting = YES; 38 39 [UIView animateWithDuration:kRotationAnimationTimeInterval 40 animations:^{ 41 if (_dataSourceIndex == 0 && moveDirection == HBRRotationViewDirectionRight) { 42 return; 43 } 44 45 if (_dataSourceIndex + 1 == self.dataSourceCount 46 && moveDirection == HBRRotationViewDirectionLeft) { 47 return; 48 } 49 50 _leftView.frame = CGRectMake(-320.f, 0, 320, UISCREEN_MAINSCREEN_HEIGHT); 51 _rightView.frame = CGRectMake(0, 0, 320, UISCREEN_MAINSCREEN_HEIGHT); 52 } 53 completion:^(BOOL finished){ 54 UIView *tempView = _leftView; 55 _leftView = _rightView; 56 _rightView = tempView; 57 _leftView.frame = CGRectMake(0, 0, UISCREEN_MAINSCREEN_WIDTH, UISCREEN_MAINSCREEN_HEIGHT); 58 _rightView.frame = CGRectMake(UISCREEN_MAINSCREEN_WIDTH, 0, UISCREEN_MAINSCREEN_WIDTH 59 , UISCREEN_MAINSCREEN_HEIGHT); 60 [_rightView removeAllSubViews]; 61 HBRMaskView *maskView = [[HBRMaskView alloc] initWithFrame:CGRectMake(0, 0 62 , UISCREEN_MAINSCREEN_WIDTH 63 , UISCREEN_MAINSCREEN_HEIGHT)]; 64 [_rightView addSubview:maskView]; 65 [maskView release]; 66 67 if (self.RotationAnimationCompleteBlock) { 68 self.RotationAnimationCompleteBlock(_leftView, _rightView, moveDirection, _dataSourceIndex); 69 } 70 71 _isAnimationExecuting = NO; 72 }]; 73 break; 74 } 75 76 default: 77 break; 78 } 79 }
当用户手指在屏幕上作出pan手势,并且移动距离达到触发视图行为的时候动画移动左视图出可视区域,同时右视图随着左视图移入可视区域。
之后交换左右视图指针值,然后清空右视图的全部子视图。
滚动视图和其superview通过一个block交互。
1 @property(nonatomic, copy) void(^RotationAnimationCompleteBlock)(UIView*, UIView*, HBRRotationViewDirection, NSInteger);
这个block会把当前滚动视图的左视图、右视图、移动方向和当前需要用到的数据index一起发送给superview。superView可以根据这些
参数加载所需的视图并把处理之后的数据按照需要的方式展现在滚动视图中。
调用滚动视图的代码:
1 //初始化滚动视图 2 _rotationView.RotationAnimationCompleteBlock = ^(UIView *leftView, UIView *rightView 3 , HBRRotationViewDirection moveDirection 4 , NSInteger index){ 5 [self rotationAction:leftView rightView:rightView direction:moveDirection index:index]; 6 };
调用滚动视图代码:
1 - (void)rotationAction:(UIView *)leftView rightView:(UIView *)rightView 2 direction:(HBRRotationViewDirection)moveDirection index:(NSInteger)dataSourceIndex{ 3 DLog(@"Rotation Block"); 4 HBRWebView *webView; 5 switch (moveDirection) { 6 case HBRRotationViewDirectionStartPoint:{ 7 //开始运行 8 9 break; 10 } 11 case HBRRotationViewDirectionLeft:{ 12 //手指左移 13 DLog(@"move left"); 14 break; 15 } 16 case HBRRotationViewDirectionRight:{ 17 DLog(@"move right"); 18 [self.navigationController popViewControllerAnimated:YES]; 19 20 break; 21 } 22 default: 23 break; 24 } 25 }
是指移动方向枚举:
typedef NS_ENUM(NSInteger, HBRRotationViewDirection) {
HBRRotationViewDirectionLeft,
HBRRotationViewDirectionRight,
HBRRotationViewDirectionStartPoint,
};
枚举中第三项StartPoint是说在视图第一次在superView中加载上来的时候的状态。
同时开发这个滚动视图时还需要考虑的到如果到了数据源的最后一项如何处理。在这里用户手指向右滑动的时候跳转到上一层controller。
先写到这里了。谢谢 :)