UIScrollView的重用機制的理解


UIScrollView的重用機制的理解。大家都知道這個UIScrollView。UItableVIew是繼承UIScrollView的UItableVIew默認里面有自己的重用機制可以節省內存。UIScrollView是可以用來加載很多圖片,有利用顯示,但是沒有重用機制會在真機上crash。

網上找了些資料:主要有兩種解決的方案

1.圖片內存托管

將scrollview上所有的圖片指針收集起來,當圖片占用內存到達某個量后用setImage:nil將可視區域之外的圖片全部刪除,這個方法基本上能夠解決圖片加載過多的crash問題,但是由於承載image的view沒有被刪除,長時間滑動也會出現抖動的現象。

2.可重用的UIScrollView

經過了這些分析之后,我們很容易就能找到更進階的解決方案,就是模仿UITableView的實現機制,將scrollview進行重用,當scrollview上的view滑出可視范圍是,就將滑出的view重新設置rect添加到scrollview的最后,這樣的實現方式,保證在scrollview從開始到結束只有渲染一屏,多屏只是重新reload資源,從而讓海量圖片瀏覽也能保持高性能。

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個視圖。下面是定義的一些宏:

#define sMyViewTotal 6

#define sMyViewWidth 150

#define sMyViewHeight 220

#define sMyViewGap 10

 

 

_aryViews = [[NSMutableArray alloc] init];

for (int i = 0; i < sMyViewTotal; i++) {

    CGFloat x;

    if (i%2) {

        x = sMyViewWidth + sMyViewGap + sMyViewGap/2;

    }

    else{

        x = sMyViewGap/2;

    }

    CGFloat y = (sMyViewHeight + sMyViewGap)*(i/2);

    MyView *myView = [[MyView alloc] initWithFrame:CGRectMake(x, y, sMyViewWidth, sMyViewHeight)];

    myView.showNumber = i;

[myScrollView addSubview:myView];

  [_aryViews addObject:myView];

    [myView release];

}

 

所以這里的核心方法是,首先要判斷是向上滾動還是向下滾動方法如下:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{

    BOOL directDown;

    if (previousOffSet.y < scrollView.contentOffset.y) {

        directDown = YES;

    }

    else{

        directDown = NO;

    }

    previousOffSet.y = scrollView.contentOffset.y;

    //防止最開始就向上面拖動的時候,改變數組視圖樹的位置。

    if (scrollView.contentOffset.y < 0) {

        return;

    }

    if (directDown) {

        NSLog(@"down");

        MyView * subView = [_aryViews objectAtIndex:firstViewIndex];

        CGFloat firstViewYOffset = subView.frame.origin.y + subView.frame.size.height + sMyViewGap;

        //尋找第一個視圖是否滾動出去

        if (firstViewYOffset < scrollView.contentOffset.y) {

            //改變數組中第一排可見視圖的位置。

            [self moveIndexInViewsWithDirect:YES];

        }

    }

    else{

        NSLog(@"up");

        MyView * subView = [_aryViews objectAtIndex:(firstViewIndex + sMyViewTotal - 2)%sMyViewTotal];

        CGFloat lastViewYOffset = subView.frame.origin.y - scrollView.bounds.size.height;

        if (lastViewYOffset > scrollView.contentOffset.y) {

                           [self moveIndexInViewsWithDirect:NO];

        }

    }

}

每次滾動的時候先判斷滾動位置即offset,和先前的比較。如果先前的大就是向下滾動,否則就是向上滾動。

找到了向下滾動了,就該判斷是否子視圖已經離開了可視范圍。方法就是判斷當前offset和視圖的位置進行比較。如果判斷滾到離開了可視范圍,然后就是要改變重用視圖數組中第一個視圖的位置了。這里用了firstViewIndex來記錄scrollView中第一個可見視圖的位置, 循環使用這6個視圖達到重用的目的。自然firstViewIndex上面的一個視圖就是最后一個視圖的位置(firstViewIndex + sMyViewTotal - 1)%sMyViewTotal。所以這里需要改變重用視圖中firstViewIndex即第一個可見視圖的位置。代碼如下:

- (void)moveIndexInViewsWithDirect:(BOOL)forward{

    [UIView setAnimationsEnabled:NO];

    if (forward) {

        for (int i = firstViewIndex; i < (firstViewIndex + 2); i++) {

            MyView *subView = [_aryViews objectAtIndex:i%sMyViewTotal];

            subView.showNumber = subView.showNumber + sMyViewTotal;

subView.frame = CGRectMake(subView.frame.origin.x, subView.frame.origin.y + (sMyViewTotal/2) * (sMyViewHeight + sMyViewGap), subView.frame.size.width, subView.frame.size.height);

        }

        firstViewIndex = (firstViewIndex + 2)%sMyViewTotal;

    }

    else{

        int lastViewIndex = firstViewIndex + sMyViewTotal - 1;

        for (int i = lastViewIndex; i > (lastViewIndex - 2); i--) {            MyView *subView = [_aryViews objectAtIndex:(firstViewIndex + sMyViewTotal - i)%sMyViewTotal];

            subView.showNumber = subView.showNumber - sMyViewTotal;

subView.frame = CGRectMake(subView.frame.origin.x, subView.frame.origin.y - (sMyViewTotal/2) * (sMyViewHeight + sMyViewGap), subView.frame.size.width, subView.frame.size.height);

        }

        firstViewIndex = (firstViewIndex + sMyViewTotal - 2)%sMyViewTotal;

    }

    [UIView setAnimationsEnabled:YES];

}

這里創建的子視圖數字屬性,是用來在視圖上畫數字的,這樣就可以看到視圖重用的效果了,應該是從0開始到無窮多,但是實際上內存中就創建了6個視圖。

- (void)drawRect:(CGRect)rect

{

    // Drawing code

    NSString *text = [NSString stringWithFormat:@"%d",showNumber];

    [[UIColor redColor] set];

    [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];

}

}
可參考http://blog.csdn.net/mengtnt/article/details/6723245資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM