zoukankan      html  css  js  c++  java
  • UIScrollView相关

    UIScrollView的一些特点

    是个麻烦的控件,这里先问几个问题:

    1.  如果我触摸一下屏幕,哪个view会最先收到触摸消息呢(touchesBegan)?

    答案是,如果所有view都是UIView的话,那么最外层的子view会先收到该消息。这个机制就是通过 UIView中的hitTest:withEvent:  实现的。也就是说底层的父view的hitTest函数先被调用,在这里遍历调用subView的hitTest,直到找到最外层的subView,然后返回该view,系统再把touch事件传给这个view。

    2. tableView的cell我点击一下,打开一个新页面,但如果飞快的swipe一下,tableView会是上下滚动,而cell即不会highlight,也不会打开新页面,这是如何实现的呢?

    tableView继承自UIScrollView,这个功能实际上是UIScrollView来实现的。它是如何做的呢?根据问题 一,UIView的默认行为是将touch事件传递给最外层的subView。实际上UIScrollView重载了 hitTest:withEvent:,他永远只返回自己。

    这里把UIScrollView的几个要点总结下:

    从你的手指touch屏幕开始,scrollView开始一个timer,如果:

    1.  150ms内如果你的手指没有任何动作,消息就会传给subView。

    2.  150ms内手指有明显的滑动(一个swipe动作),scrollView就会滚动,消息不会传给subView,这里就是产生问题二的原因。

    3. 150ms内手指没有滑动,scrollView将消息传给subView,但是之后手指开始滑动,scrollView传送touchesCancelled消息给subView,然后开始滚动。

    观察下tableView的情况,你先按住一个cell,cell开始高亮,手不要放开,开始滑动,tableView开始滚动,高亮取消。

    delaysContentTouches的作用:

    这个标志默认是YES,使用上面的150ms的timer,如果设置为NO,touch事件立即传递给subView,不会有150ms的等待。

    cancelsTouches的作用:

    这个标准默认为YES,如果设置为NO,这消息一旦传递给subView,这scroll事件不会再发生。

    UIScrollView的一些属性

    1. @property(nonatomic) BOOL bounces //当滚动到内容边缘是否发生反弹,default is YES.
    2. @property(nonatomic) BOOL alwaysBounceHorizontal; //是否只在水平发生反弹,当内容到达
    边缘。。default is NO,
    如果要只在水平反弹那么bounces必须为YES.
    3. @property(nonatomic) BOOL alwaysBounceVertical   //当滚动到达边缘时,是否只有垂直边缘才发生
    反弹。default is no.
    4. @property(nonatomic) BOOL bouncesZoom;   //当在缩放时,到达图片最大缩放倍数(maximumZoomScale)
    或者是最小缩放倍数( minimumZoomScale)时,为了告诉用户缩放倍数已达极限,是否发生动态反弹的效果来
    告诉用户。defaults is YES.
    5. @property(nonatomic) BOOL canCancelContentTouches; //当手指触摸屏幕后,并没有开始拖动,而隔一段时间后
    再开始拖动,这个属性决定是否scorllView里的图片是否会再继续随着手指的滑动,而图片跟着滑动。defualt is
    NO,图片会跟着手指滑动而滑动。
    6. @property(nonatomic) CGPoint contentOffset; //scrollView里的内容(如里面存的图片)的原点,距离scrollView的
    frame属性里的原点(origin)的距离。按照一般思维来说,如scrollView的frame为(0,0,320,480),而scrollView里的
    图片坐标为:(-320,0,320*2,480);那么contentOffset应该为(-320,0),但实际上是(320,0),可能是为了方便设置,取为
    正吧,反正contentOffset的x,y是不可能为负的,那样代表滚动已到边缘,发生反弹或者不能再往边缘外拖动。
    7. @property(nonatomic) CGSize contentSize; //scrollView里能存储图片最大size。比如scrollView为屏幕
    大小,而要在里面存放几张屏幕大小的图片,那么一定不能忘了在放图片之前设置contentSize.
    8. @property(nonatomic, assign) id<UIScrollViewDelegate> delegate;  //scrollView的代理。如:
    当要实现缩放图片时,必须实现UIScrollViewDelegate里的两个方法,且最大、最小缩放倍数必须不一样maximumZoomScale
    ,minimumZoomScale:
    - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;  //返回要缩放的图片(必须在代理类里实现)
    - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale;
    //重新确定缩放完后的缩放倍数.
    常用来缩放方法:- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated,把从scrollView里截取的矩形
    区域缩放到整个scrollView当前可视的frame里面。所以如果截取的区域大于scrollView的frame时,图片缩小,
    如果截取区域小于frame,会看到图片放大。一般情况下rect需要自己计算出来。
    比如,要把scrollView原来坐标点为(40,40)的内容周围内容在scrollView里放大一倍,可以求出需要从scrollView里
    截取图片的frame,当然主要是求截取图片坐标原点,可以想象,内容放大一倍,那么截取图片的大小宽度肯定是
    scrollView的frame大小一半。如下列方法:
    - (CGRect) getRectWithScale:(float)scale andCenter:(float)center
    {
     CGRect newRect;
     newRect.size.width=scrollView.frame.size.width/scale;
     newRect.size.height=scrollView.frame.size.height.scale;
     newRect.origin.x=center.x-newRect.size.width/2;
     newRect.origin.y=center.y-newRect.size.height/2;
     return newRect;
    }

    1. 
    @property(nonatomic, getter=isDirectionalLockEnabled) BOOL directionalLockEnabled; //滚动方向的锁定。
    如果一开始拖动方向是水平或者垂直,且该属性设置为YES,那么另外一个方向将会被锁定,不能在那个方向拖动了。如果
    开始拖动方向为斜的,那么不会禁止任何一个方向的拖动。
    2. @property(nonatomic) UIScrollViewIndicatorStyle indicatorStyle; //拖动图片时,下面或者右侧的那个滚动进度条
    显示的风格,当然也可以把这个滚动条取消。可以用下面属性:
    @property(nonatomic) BOOL showsHorizontalScrollIndicator; //是否显示水平滚动条
    @property(nonatomic) BOOL showsVerticalScrollIndicator; //是否显示垂直滚动条,default is YES
    3. @property(nonatomic) float maximumZoomScale; //最大缩放倍数
     @property(nonatomic) float minimumZoomScale; //最大缩小倍数
    通常情况下,最小倍数比scrollView的frame要小,而最大缩放倍数可能与contentSize有关,需要自己算出
    最大缩放倍数,如:如果想最大缩放倍数为5倍,那么contentSize也应该设置为5倍scrollView的frame大小。
    假如想要双击scrollView里的图片放大,或者支持两只手指在屏幕捏放实现图片缩放,必须重写覆盖继承自
    UIResponder的几个交互方法:
    ﹣(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
    ﹣(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
    ﹣(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; 
    4.@property(nonatomic, getter=isPagingEnabled) BOOL pagingEnabled; //是否在拖动图片后,图片翻到
    scrollView的下一个子视图开始边界. default is NO
    @property(nonatomic, getter=isScrollEnabled) BOOL scrollEnabled; //是否可以滚动。default is YES,
    如果设置为NO,那么将scrollView将不会接受任何触摸事件。
    - (void)setZoomScale:(float)scale animated:(BOOL)animated

    UIScrollView的用法

     UIScrollView是iphone中的一个重要的视图,它提供了一个方法,让你在一个界面中看到所有的内容,从而不必担心因为屏幕的大小有 限,必须翻到下一页进行阅览。确实对于用户来说是一个很好的体验。但是又是如何把所有的内容都加入到scrollview,是简单的 addsubView。假如是这样,岂不是scrollView界面上要放置很多的图形,图片。移动设备的显示设备肯定不如PC,怎么可能放得下如此多的 视图。所以在使用scrollView中一定要考虑这个问题,当某些视图滚动出可见范围的时候,应该怎么处理,是不管它那,还是进行内存回收或者重利用。 苹果公司的UITableView就很好的展示了在UIScrollView中如何重用可视的空间,减少内存的开销。

        首先还是看官方API如何解释UIScrollView,一下是翻译官方UIScrollView的帮助文档。

        UIScrollView类支持显示比屏幕更大的应用窗口的内容。它通过挥动手势,能够使用户滚动内容,并且通过捏合手势缩放部分内容。

        UIScrollView是UITableView和UITextView的超类。

        UIScrollView的核心理念是,它是一个可以在内容视图之上,调整自己原点位置的视图。它根据自身框架的大小,剪切视图中的内容, 通常框架是和应用程序窗口一样大。一个滚动的视图可以根据手指的移动,调整原点的位置。展示内容的视图,根据滚动视图的原点位置,开始绘制视图的内容,这 个原点位置就是滚动视图的偏移量。ScrollView本身不能绘制,除非显示水平和竖直的指示器。滚动视图必须知道内容视图的大小,以便于知道什么时候 停止;一般而言,当滚动出内容的边界时,它就返回了。

        某些对象是用来管理内容显示如何绘制的,这些对象应该是管理如何平铺显示内容的子视图,以便于没有子视图可以超过屏幕的尺寸。就是当用户滚动时,这些对象应该恰当的增加或者移除子视图。

        因为滚动视图没有滚动条,它必须知道一个触摸信号是打算滚动还是打算跟踪里面的子视图。为了达到这个目的,它临时中断了一个touch- down的事件,通过建立一个定时器,在定时器开始行动之前,看是否触摸的手指做了任何的移动。假如定时器行动时,没有任何的大的位置改变,滚动视图就发 送一个跟踪事件给触摸的子视图。如果在定时器消失前,用户拖动他们的手指足够的远,滚动视图取消子视图的任何跟踪事件,滚动它自己。子类可以重载 touchesShouldBegin:withEvent:inContentView:,pagingEnabled,和 touchesShouldCancelInContentView:方法,从而影响滚动视图的滚动手势。

        一个滚动视图也可以控制一个视图的缩放和平铺。当用户做捏合手势时,滚动视图调整偏移量和视图的比例。当手势结束的时候,管理视图内容显示的对象,就应该恰当的升级子视图的显示。当手势在处理的过程中,滚动视图不能够给子视图,发送任何跟踪的调用。

        UIScrollView类有一个delegate,需要适配的协议是UIScrollViewDelegate。为了缩放和平铺工作,代 理必须实现viewForZoomingInScrollView:和 scrollViewDidEndZooming:withView:atScale:方法。另外,最大和最小缩放比例应该是不同的。

        重要的提示:在UIScrollView对象中,你不应该嵌入任何UIWebView和UITableView。假如这样做,会出现一些异常情况,因为2个对象的触摸事件可能被混合,从而错误的处理。

        这些都是官方API的解释,重点是理解UIScrollView怎么来控制手势的。可以由canCancelContentTouches这个方法的运用来解释UIScrollView如何控制手势的。

        假如你设置canCancelContentTouches为YES,那么当你在UIScrollView上面放置任何子视图的时候,当你 在子视图上移动手指的时候,UIScrollView会给子视图发送touchCancel的消息。而如果该属性设置为NO,ScrollView本身不 处理这个消息,全部交给子视图处理。

        那么这里就有疑问了,既然该属性设置未来NO了,那么岂不是UIScrollView不能处理任何事件了,那么为何在子视图上快速滚动的时 候,UIScrollView还能移动那。这个一定要区分前面所说的UIScrollView中断touch-Down事件,开启一个定时器。我们设置的 这个cancancelContentTouches属性为NO时,只是让UIScrollView不能发送cancel事件给子视图。而前面所说的时, 中断touch-down事件,和取消touch事件是俩码事,所以当快速在子视图上移动的时候,当然可以滚动。但是如果你慢速的移动的话,就可以区分这 个属性了,假如设定为YES,在子视图上慢速移动也可以滚动视图,但是如果为NO 。因为UIScrollView,发送了cancel事件给子视图处理 了,自己当然滚动不了了。

        事件处理看过了,就要考虑scrollView如何重用内存的,下面写了一个例子模仿UITableView的重用的思想,这里只是模仿,至于苹果公司怎么实现这种重用的,他们应该有更好的方法。

         这里的例子是在scrollView上放置4个2排2列的视图,但是内存中只占用6个视图的内存空间。当scrollView滚动的时候,通过不停的重用之前视图的内存空间,从而达到节省内存的效果。重用的方法如下:

    1.如果scrollView向下面滚动,一旦一排视图滚出了可视范围,就改变滚动出去的那个view在scrollView中的frame,也就是改变位置到达末尾,达到重用的效果。

    2.如果scrollView向上面滚动,一旦最末排的视图view滚出了可视范围,就改变滚动出去的那个view在scrollView中的frame,移动到最前面。

       下面就需要在你创建的视图控制器中,创建一个重用的视图数组,用来把这些要显示的视图放入内存中,这里虽然界面上显示的是2排2列的四个视 图,但是当拖动的时候,可能出现前面一排的视图显示一部分,末尾一排的视图显示一部分的情况,所以重用的数组中要放置6个视图。下面是定义的一些宏:

    1. #define sMyViewTotal 6  
    2.   
    3. #define sMyViewWidth 150  
    4.   
    5. #define sMyViewHeight 220  
    6.   
    7. #define sMyViewGap 10  


    具体实现代码如下:

    1. _aryViews = [[NSMutableArray alloc] init];  
    2.   
    3. for (int i = 0; i < sMyViewTotal; i++) {  
    4.   
    5.     CGFloat x;  
    6.   
    7.     if (i%2) {  
    8.   
    9.         x = sMyViewWidth + sMyViewGap + sMyViewGap/2;  
    10.   
    11.     }  
    12.   
    13.     else{  
    14.   
    15.         x = sMyViewGap/2;  
    16.   
    17.     }  
    18.   
    19.     CGFloat y = (sMyViewHeight + sMyViewGap)*(i/2);  
    20.   
    21.     MyView *myView = [[MyView alloc] initWithFrame:CGRectMake(x, y, sMyViewWidth, sMyViewHeight)];  
    22.   
    23.     myView.showNumber = i;  
    24.   
    25. [myScrollView addSubview:myView];  
    26.   
    27.   [_aryViews addObject:myView];  
    28.   
    29.     [myView release];  
    30.   
    31. }  


    所以这里的核心方法是,首先要判断是向上滚动还是向下滚动方法如下:

    1. - (void)scrollViewDidScroll:(UIScrollView *)scrollView{  
    2.   
    3.     BOOL directDown;  
    4.   
    5.     if (previousOffSet.y < scrollView.contentOffset.y) {  
    6.   
    7.         directDown = YES;  
    8.   
    9.     }  
    10.   
    11.     else{  
    12.   
    13.         directDown = NO;  
    14.   
    15.     }  
    16.   
    17.     previousOffSet.y = scrollView.contentOffset.y;  
    18.   
    19.     //防止最开始就向上面拖动的时候,改变数组视图树的位置。  
    20.   
    21.     if (scrollView.contentOffset.y < 0) {  
    22.   
    23.         return;  
    24.   
    25.     }  
    26.   
    27.     if (directDown) {  
    28.   
    29.         NSLog(@"down");  
    30.   
    31.         MyView * subView = [_aryViews objectAtIndex:firstViewIndex];  
    32.   
    33.         CGFloat firstViewYOffset = subView.frame.origin.y + subView.frame.size.height + sMyViewGap;  
    34.   
    35.         //寻找第一个视图是否滚动出去  
    36.   
    37.         if (firstViewYOffset < scrollView.contentOffset.y) {  
    38.   
    39.             //改变数组中第一排可见视图的位置。  
    40.   
    41.             [self moveIndexInViewsWithDirect:YES];  
    42.   
    43.         }  
    44.   
    45.     }  
    46.   
    47.     else{  
    48.   
    49.         NSLog(@"up");  
    50.   
    51.         MyView * subView = [_aryViews objectAtIndex:(firstViewIndex + sMyViewTotal - 2)%sMyViewTotal];  
    52.   
    53.         CGFloat lastViewYOffset = subView.frame.origin.y - scrollView.bounds.size.height;  
    54.   
    55.         if (lastViewYOffset > scrollView.contentOffset.y) {  
    56.   
    57.                            [self moveIndexInViewsWithDirect:NO];  
    58.   
    59.         }  
    60.   
    61.     }  
    62.   
    63. }  


    每次滚动的时候先判断滚动位置即offset,和先前的比较。如果先前的大就是向下滚动,否则就是向上滚动。

        找到了向下滚动了,就该判断是否子视图已经离开了可视范围。方法就是判断当前offset和视图的位置进行比较。如果判断滚到离开了可视范 围,然后就是要改变重用视图数组中第一个视图的位置了。这里用了firstViewIndex来记录scrollView中第一个可见视图的位置, 循环 使用这6个视图达到重用的目的。自然firstViewIndex上面的一个视图就是最后一个视图的位置 (firstViewIndex + sMyViewTotal - 1)%sMyViewTotal。所以这里需要改变重用视图中 firstViewIndex即第一个可见视图的位置。代码如下:

    1. - (void)moveIndexInViewsWithDirect:(BOOL)forward{  
    2.   
    3.     [UIView setAnimationsEnabled:NO];  
    4.   
    5.     if (forward) {  
    6.   
    7.         for (int i = firstViewIndex; i < (firstViewIndex + 2); i++) {  
    8.   
    9.             MyView *subView = [_aryViews objectAtIndex:i%sMyViewTotal];  
    10.   
    11.             subView.showNumber = subView.showNumber + sMyViewTotal;  
    12.   
    13. subView.frame = CGRectMake(subView.frame.origin.x, subView.frame.origin.y + (sMyViewTotal/2) * (sMyViewHeight + sMyViewGap), subView.frame.size.width, subView.frame.size.height);  
    14.   
    15.         }  
    16.   
    17.         firstViewIndex = (firstViewIndex + 2)%sMyViewTotal;  
    18.   
    19.     }  
    20.   
    21.     else{  
    22.   
    23.         int lastViewIndex = firstViewIndex + sMyViewTotal - 1;  
    24.   
    25.         for (int i = lastViewIndex; i > (lastViewIndex - 2); i--) {            MyView *subView = [_aryViews objectAtIndex:(firstViewIndex + sMyViewTotal - i)%sMyViewTotal];  
    26.   
    27.             subView.showNumber = subView.showNumber - sMyViewTotal;  
    28.   
    29. subView.frame = CGRectMake(subView.frame.origin.x, subView.frame.origin.y - (sMyViewTotal/2) * (sMyViewHeight + sMyViewGap), subView.frame.size.width, subView.frame.size.height);  
    30.   
    31.         }  
    32.   
    33.         firstViewIndex = (firstViewIndex + sMyViewTotal - 2)%sMyViewTotal;  
    34.   
    35.     }  
    36.   
    37.     [UIView setAnimationsEnabled:YES];  
    38.   
    39. }  


      这里创建的子视图数字属性,是用来在视图上画数字的,这样就可以看到视图重用的效果了,应该是从0开始到无穷多,但是实际上内存中就创建了6个视图。 

      1. - (void)drawRect:(CGRect)rect  
      2.   
      3. {  
      4.   
      5.     // Drawing code  
      6.   
      7.     NSString *text = [NSString stringWithFormat:@"%d",showNumber];  
      8.   
      9.     [[UIColor redColor] set];  
      10.   
      11.     [text drawInRect:CGRectMake(rect.origin.x, rect.origin.y + rect.size.height/2 - 30, rect.size.width, 30) withFont:[UIFont      fontWithName:@"Helvetica" size:20] lineBreakMode:UILineBreakModeWordWrap alignment:UITextAlignmentCenter];  
      12.   
      13. }  
      14.   
      15. }
  • 相关阅读:
    Tomcat解压版Windows配置(运行环境非开发环境)
    idea排除要编译的文件
    DispatcherServlet继承体系
    nested exception is java.lang.IllegalArgumentException: warning no match for this type name: res [Xlint:invalidAbsoluteTypeName]
    Spring框架之一 读取配置文件
    初识MyBatis-Generator
    jmeter源代码开发环境构建
    tail
    tps抖动
    Centos系统更改yum源为163
  • 原文地址:https://www.cnblogs.com/hbf369/p/2506647.html
Copyright © 2011-2022 走看看