内容可循环重用的ScrollView
来源:互联网 发布:华为穆斯林0 知乎 编辑:程序博客网 时间:2024/06/03 09:25
原理非常的简单:不管有多少内容要显示,只要三个View就足够了,假设为A、B、C。为了后面方便操作,我把三个view放进一个大的容器视图containerView中,再把containerView作为scrollView的子视图。containerView的尺寸以及scrollView的contentSize均设为3倍屏幕宽度大小。
假设当前状态为A(a)、[B(b)]、C(c),表示三个View中存放的内容分别为a、b、c,当前显示的是B。为了在左右滑屏时能流畅地看到相邻的内容,需要将相邻视图内容都准备好,放在B的左右位置,如图1所示:
当向左滑屏翻页后,当前显示页变为C(c),如图2:
如果c之后还有内容d要显示,为了让接下来的左滑可以进行,需要在C的右边把内容d加载进来,由于只有三个view,需要把视图A腾出来加载内容d,然后把containerView中的子视图顺序更改为B、C、A,如图3:
以上便完成了重用翻页的一半工作,到目前为止,ScrollView中显示内容还是containerView的A(d)部分,我们需要把当前显示的区间改为C(c):
ScrollView有一个属性叫做contentOffset,用来表示当前显示内容的左上角坐标距离scrollview左上角的偏移,修改contentOffset可以改变当前显示的区间。令屏幕宽度为w,只需要将contentOffset属性从(2w, 0)修改为(w, 0)即可。
原理就这么多,在具体操作的时候,需要把业务逻辑和重用机制分离开。我用ReusableScrollView封装可重用的翻页scrollview,它管理containerView以及A、B、C三个bufferViews,但它不应该知道具体要显示什么内容,这些由业务层负责,业务层只需要遵守ReusableScrollViewDelegate协议,并将自身传给ReusableScrollView即可。
ReusableScrollViewDelegate的定义如下:
@protocol ReusableScrollViewDelegate// 需要delegate在view中填充第toPage的数据;如果传入的view为nil,需要delegate创建view-(nonnull UIView*)setupView:(nullable UIView*)view toPage:(NSUInteger)toPage;-(NSUInteger)numOfPages;@end
ReusableScrollView声明如下:
@interface ReusableScrollView : UIScrollView@property (nonatomic,assign, nonnull) id<ReusableScrollViewDelegate> delegateForReuseableScrollView;// 完成bufferViews的初始化,并放入containerView,再把containerView放入scrollView-(void)setupViews;@end
ReusableScrollView的定义,重点在layoutSubview方法中,每次view中内容变化时(包括addSubView、设置view的frame、滑动、旋转屏幕),都会调用该方法,前面讲的原理部分主要在此方法中体现:
#import "ReusableScrollView.h"@interface ReusableScrollView()@property (nonatomic, strong) UIView* containerView;@property (nonatomic, strong) NSMutableArray* bufferViews;@property int currentPage;@property int toPage;@endstatic const int nBufferViews = 3;@implementation ReusableScrollView-(instancetype)initWithCoder:(NSCoder *)aDecoder{ self = [super initWithCoder:aDecoder]; if (self) { _currentPage = 0; _containerView = [[UIView alloc]init]; _bufferViews = [[NSMutableArray alloc]init]; _delegateForReuseableScrollView = nil; } return self;}-(void)setupViews{ CGRect rtContent = self.frame; rtContent.size.width = rtContent.size.width * nBufferViews; self.containerView.frame = rtContent; self.contentSize = rtContent.size; [self addSubview:self.containerView]; self.pagingEnabled = YES; [self setShowsHorizontalScrollIndicator:NO]; for (int i=0; i<nBufferViews; i++) { UIView *view = [self.delegateForReuseableScrollView setupView:nil toPage:i]; CGRect rect = self.frame; rect.origin.x = rect.size.width * i; view.frame = rect; [self.bufferViews addObject:view]; [self.containerView addSubview:view]; if (i == 0) { view.backgroundColor = [UIColor redColor]; }else if(i == 1){ view.backgroundColor = [UIColor yellowColor]; }else{ view.backgroundColor = [UIColor blueColor]; } }}-(void)layoutSubviews{ [super layoutSubviews]; if (self.delegateForReuseableScrollView == nil) { NSLog(@"不执行reusable策略:self.delegateForReusableScrollView == nill"); return; } // 如果总页数小于buffer页数,则没必要执行reusable策略 if ([self.delegateForReuseableScrollView numOfPages] <= nBufferViews) { NSLog(@"不执行reusable策略:总页数小于buffer数"); return; } UIView *currentView = nil; NSUInteger maxPage = [self.delegateForReuseableScrollView numOfPages] - 1; if (self.currentPage == 0) { currentView = self.bufferViews[0]; }else if (self.currentPage == maxPage){ currentView = self.bufferViews[2]; }else{ currentView = self.bufferViews[1]; } CGFloat offsetDiff = self.contentOffset.x - currentView.frame.origin.x; // 如果滑动幅度没有达到翻页,则不执行reusable策略 if (fabs(offsetDiff) < self.frame.size.width) {// NSLog(@"不执行reusable策略:未达到翻页X(%d - %d)", (int)self.contentOffset.x, (int)currentView.frame.origin.x); return; } int toPage = self.currentPage; if (offsetDiff > 0) { toPage++; }else{ toPage--; } NSLog(@"Page %d => Page %d", self.currentPage, toPage); // 如果是 第0页<=>第1页 或者 最后一页<=>倒数第二页,则仅更新currentPage if (self.currentPage == 0 || toPage == 0 || self.currentPage == maxPage || toPage == maxPage) { self.currentPage = toPage; }else{ if (toPage > self.currentPage) { UIView *view = self.bufferViews[0]; self.bufferViews[0] = self.bufferViews[1]; self.bufferViews[1] = self.bufferViews[2]; self.bufferViews[2] = view; [self.delegateForReuseableScrollView setupView:self.bufferViews[2] toPage:toPage + 1]; }else{ UIView *view = self.bufferViews[2]; self.bufferViews[2] = self.bufferViews[1]; self.bufferViews[1] = self.bufferViews[0]; self.bufferViews[0] = view; [self.delegateForReuseableScrollView setupView:self.bufferViews[0] toPage:toPage - 1]; } self.currentPage = toPage; for (int i=0; i<nBufferViews; i++) { UIView *view = self.bufferViews[i]; CGRect rect = self.frame; rect.origin.x = rect.size.width * i; view.frame = rect; } CGPoint contentOffset = ((UIView*)self.bufferViews[1]).frame.origin; self.contentOffset = contentOffset; }}@end
在ReusableScrollView上层的ViewController,只需要遵守ReusableScrollViewDelegate协议即可。
完整的代码可参见:https://github.com/palanceli/ReusableScrollViewSample
- 内容可循环重用的ScrollView
- 可重用的SVG
- 可重用性的克服
- 可重用的数据库连接框架
- 开发可重用的数据库连接
- 可重用代码的艺术
- MENU可重用的子系统
- 创建可重用的Layout
- 可重用的代码块
- 子系统的可重用设计
- 简单的重用 ios(效果类似图片无限循环(可支持多种图片))
- scrollview 内容重用小demo(懒人专用哈)
- Django官方教程(十)【进阶内容:编写可重用的应用】
- 如何把握可重用与不可重用的人才
- 简单的可重用的输入验证
- 软件可重用性的一点思考
- 软件可重用性的一点思考
- 软件可重用性的一点思考
- iOS模拟器的应用沙盒在MAC中的位置
- iOS Programming GitHub
- Git入门操作
- 关于Logger
- EL表达式调用错误及获取List长度
- 内容可循环重用的ScrollView
- iOS海哥开发笔记(MapKit的使用)
- UINavigationController + UIScrollView组合,视图尺寸的设置探秘(一)
- UINavigationController + UIScrollView组合,视图尺寸的设置探秘(二)
- UINavigationController + UIScrollView组合,视图尺寸的设置探秘(三)
- iOS模拟器录屏转gif神器
- sqlite3使用备忘
- JSPatch在MAC下的使用
- 以证书的方式登录ssh