【IOS】自定義可點擊的多文本跑馬燈YFRollingLabel


需求

項目中需要用到跑馬燈來僅展示一條消息,長度合適則不滾動,過長則循環滾動。

雖然不是我寫的,但看了看代碼,是在一個UIView里面放入兩個UILabel,

在前一個快結束的時候,另一個顯示。然而點擊處理的 確是UIView的點擊事件。    

 

然而看到比如地鐵、公交里面的跑馬燈是分了很多段顯示的。雖然說可以將多段合並為一段來顯示,

但是如果各個需要點擊事件又該如何處理呢?於是我來自己實現可點擊的多段跑馬燈。

所以這篇隨筆我要實現的跑馬燈包含下面這種效果:(圖中有5段   點擊不同文本可觸發相應的事件)

 

彎路

還記得上一篇隨筆【IOS】將字體大小不同的文字底部對齊 么?

雖然不能夠做到多個UILabel的底部對齊,但是我們可以通過繼承UILabel來改變文本豎直方向的位置。

所以呢,我最初的想法是繼承UILabel,可以保持其繼承性, 通過NSTimer來直接慢慢移動UILable里面的文本。

這里出現了兩個問題:(以@"這是自定義跑馬燈里面要移動的文本"為例)

1.移動是可以移動,但是在文本左移至快要看不見(只剩下"移動的文本")的時候, 如何讓@"這是.."開始從右側出現呢?

2.文本過長的時候,看不見的部分將被截斷,所以在移動的時候,只有部分文本了。

第一種好像沒有辦法,UILabel只存在一個文本的bounds, 不可能讓他一部分在左邊, 一部分在右邊。 

第二種就因為存在默認的屬性NSLineBreakMode:NSLineBreakByWordWrapping,就算不截斷文本也只會變為省略號。

所以這種方法作罷。。。。

 

實現

首先要明確的是本跑馬燈繼承了UIView且需要兩個UILabel、定時器NSTimer。

在初始化時,傳入字符串數組,並計算各個字符串的自適應大小

CGRect textRect = [((NSString *)_textArray[i]) boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:kFont} context:nil];
[_textRectArray addObject:[NSValue valueWithCGRect:textRect]];

如果傳入的字符串數組個數為1且自適應寬度<UIView寬度,則不會滾動。重新寫一個UILabel用於顯示就行了

其他情況下,就是可以滾動的, 在此實例化兩個UILabel,並打開定時器了:

定時器相關:

_timer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
//為什么要將定時器起放入LOOP中呢?
//如果此RunLoop正在執行一個連續性的運算,timer就會被延時出發。
//也就是說,如果你將跑馬燈放入scrollview上,當滑動scrollview的時候,定時器就不會動了

//相關方法:
//[_timer setFireDate:[NSDate date]];   開始 //[_timer setFireDate:[NSDate distantFuture]]; 暫停
//取消定時器
//[_timer invalidate]; //_timer = nil;  //防止野指針

//定時器執行事件:

-(void)timerAction:(NSTimer *)timer{}

現在我們要做的  就是在每次進入該方法的時候來設置兩個UILabel。

好了,現在假設傳入了4個字符串@[@"這個是第0個字符串",@"這個是第1個字符串",@"這個是第2個字符串",@"這個是第3個字符串"];

只有兩個Label, 不管向右滾動還是向左滾動,我們將最初顯示的定為Labels[0], 后來顯示的定位Labels[1]

定義一個變量,實時的存放前一個UILabel的origin.X值,從0開始

1.每次前一個UILabel暫未完全隱藏前,后一個UIlabel就已經出現 (兩者間有一個固定的距離  internalWidth)

還需要根據speed的值更改Labels[0]的大小的增減來控制Labels[0]的位置(更改offsetX值)。

通過這個距離和前一個的位置則可以實時的計算后一個UILabel的位置(origin.X值)。

2.每次前一個UILabel完全隱藏時就需要重新設置一個值, 此時刻在每次前一個UILabel完全看不到后只進入一次

同時將左右兩個UILabel變換一下位置。 往左滾動: A<--B,A消失后,A跑到B右邊去了;   成為B<--A,B消失后,又要到A右邊去。

所以只需要設置offsetX = _labels[1].frame.origin.x;//A消失后,將后面的B位置作為下一個將消失的label的位置,A變為后面一個,

                               //其位置根據B的位置實時計算出來。每次前一個消失后,如此循環的更換。

但是這樣只更改了位置,文本以及大小卻沒有變換,見3.

3.對於只有一個文本來說,AB的內容都是一樣的。但是對於傳入的四個字符串而言,每次重新設置值的時候,需要更改AB內容。

同時,對於長度不等的字符串,需要根據不同的文本大小來設置相應AB的Frame。

所以需將四個字符串文本大小,文本內容在之前保存為一個數組。定義一個始終記錄當前正准備消失的(前一個)UIlabel的位置:_currentIndex

在步驟1中: 從兩個數組中分別獲取用於顯示在A.B里的文本數組:labelTextArray  frame數組:labelArray(從中取得寬和高)

每次AB位置交換的時候,需將currentIndex+1 : 即_currentIndex = (_currentIndex + 1)  % _textArray.count;以供交換后使用。

 

之后分別取得當前以及下一個的Text和frame   分別保存到長度為2的數組   以便使用

 

上面太多、太亂。。。。。。我不想看我不想看我不想看。。。。。。

這里有圖: 看完上面還完全不懂的請看這個吧。

 

再次解釋

<===============左移==================      

   

 

 

================右移=================>

現在只看顏色 從圖中看可以到 無論左滾右滾   綠色始終是Labels[0](將要消失的Label)  紅色始終是Label[1]

正常滾動情況下:  

  綠色的offsetX值隨着speed而變 : self.offsetX =  self.offsetX - sign * self.speed;

  紅色的X值會隨着綠色的offsetX和固定間距的關系而變 : CGFloat nextOffX = self.offsetX + sign * (((self.orientation == RollingOrientationLeft)? firstRect.size.width : lastRect.size.width) +   self.internalWidth);

  通過_currentIndex值從保存到的數據中獲取到紅色、綠色的內容和大小后賦值:

當綠色消失的一瞬間:

  本該是在右邊的紅色一下子嚇綠了 : self.offsetX = _labels[1].frame.origin.x;  

  消失的綠色又將會按照正常滾動的情況下變為紅色

   _currentIndex指得始終是綠色內容的索引: _currentIndex = (_currentIndex + 1)  % _textArray.count;

  通過這個值又將會獲取按照正常滾動的情況下  紅色、綠色的大小和文本內容

  (兩個又將會進行的流程將會在"正常滾動情況下"的藍色部分操作)

    }

 

好了  解釋到此結束   看着好累。。。。   慢着   還有點擊事件沒寫完

點擊事件:在給UILabel添加了Tap手勢后進行處理

-(void)labelTap:(UITapGestureRecognizer *)gesture{
    NSInteger tag = ((UILabel *)[gesture view]).tag - 100;
    NSInteger index;
    if(tag == 0){   //如果是(Labels[0])綠色
        index = _currentIndex;
    }else if (tag == 1){  //如果是(Labels[1])紅色  就是當前點擊的后一個
        index = (_currentIndex + 1) % _textArray.count;
    }else{  
        index = _currentIndex;
    }   
if(self.labelClickBlock){ self.labelClickBlock(index); } }

 

終點

代碼見GitHub: ====>   YFRollingLabel       PS:源碼以及GitHub文檔都是用蹩腳的英語寫的,也不知道會不會有人看。。

另外說明記錄存在的問題:

對於放入的文本數組  長度不能太短   因為里面只有兩個UILabel  如果長度太短的話   並且間距也小的情況下

在綠色剛消失后, 又會立馬變為紅色,出現在目前的綠色右邊,而不是慢慢的移動出現。

  

文本太長太長的話(幾百個中文,正常情況下不會設置這么多吧), 會導致獲取的文本寬度過長,UILabel寬度過長,文本直接就不顯示了,但點擊事件還是有的  說明了還存在。。。這就搞不懂。。。

好了,終於寫完了,Windows 10 Mobile 萬歲!!!

 

 

 

PS:新的知識點:CADisplayLink

CADisplayLink是一個能讓我們以和屏幕刷新率相同的頻率(每秒60次)將內容畫到屏幕上的定時器。

但如果調用的方法比較耗時,超過了屏幕刷新周期,就會導致跳過若干次回調調用機會。

//創建
CADisplayLink displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doSomeThing:)];    
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

//設置是否停止
displayLink.Pause = NO/YES;

//釋放
[displayLink invalidate];  
displayLink = nil;

 


免責聲明!

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



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