【IOS】自訂可點擊的多文本跑馬燈YFRollingLabel,iosyfrollinglabel
需求
項目中需要用到跑馬燈來僅展示一條訊息,長度合適則不滾動,過長則迴圈滾動。
雖然不是我寫的,但看了看代碼,是在一個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的位置Realtime Compute出來。每次前一個消失後,如此迴圈的更換。
但是這樣只更改了位置,文本以及大小卻沒有變換,見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 萬歲!!!