• 内容可循环重用的ScrollView


    UIScrollView是iOS中最常用的交互控件之一,本文讨论当设定为翻页模式,内容页很多的时候,如果给每个页面都创建一个新View,会导致资源爆表。比较好的做法是参考UITableViewCell的做法,引入重用机制。

    原理非常的简单:不管有多少内容要显示,只要三个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;
    @end
    
    static 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

  • 相关阅读:
    js之面向对象
    常用功能
    html圆环(该代码非原创,具体出处已不详)
    关于jsonp的一篇文章(传播好的东西)
    当切换select时,获取select选中的值;获取选中的单选按钮的val,判断复选框是否选中
    js类型判断(数字、0、""、undefined、null)
    js获取窗口可视范围的高度、获取窗口滚动条高度、文档内容实际高度
    66
    55
    44
  • 原文地址:https://www.cnblogs.com/palance/p/4860489.html
Copyright © 2020-2023  润新知