動畫二

隔了好多天,因為在忙別的事。。。
那我們現在要做什么,上一篇講到一個滾動大屏圖,巨丑。
趁著回憶的時間,先添兩行代碼

 //隱藏滾動條
 self.collectionView.showsVerticalScrollIndicator = NO;
 self.collectionView.showsHorizontalScrollIndicator = NO;

開始正題

讓item變小

毫無疑問,這是一個很簡單的事情。我們首先應該去設置itemSize,然后設置行(這里是縱向)間距,其次可以做一個組邊距(sectionInset)使其兩邊留白。
當然簡單的事情就要考慮的稍微多一點,這個size要怎么設置(200,300),這樣可能在不同大小的屏幕上會有很明顯的差別,那有人會說可以像Masonry(一個layout的框架)設置與其他UI的關系位置,這是一種策略,但是不見得好,因為它不是一個純色的東西。根據其他位置拉伸變化(即便可以保持圖片不拉伸)也不見得好,所以可以看很多代碼會有一堆屏幕大小的宏,然后根據一個(200,300)針對某一尺寸的屏幕,對于不同的屏幕,按屏幕比等比擴大的代碼。這個可以自行實現,我做簡單易懂一點

#define ItemHeight (ScreenHeight * (5.0 / 9))
#define ItemWidth (ScreenWidth * (3.0 / 5))

然后相應的代碼變成

self.itemSize = CGSizeMake(ItemWidth, ItemHeight);
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;   //水平方向
self.sectionInset = UIEdgeInsetsMake(0, ScreenWidth/2 - ItemWidth/2, 0, ScreenWidth/2 - ItemWidth/2);   //設置組邊距
self.minimumLineSpacing = (ScreenWidth - ItemWidth)/4;

這里忘了提一個東西,我在這次加了一個pch(全局不需要自己導入的.h - -),當然里面放太多的東西會影響效率,至于添加方法自行百度,也可以做個.h自行導入,差不多

cell居中設置

現在我們的跑一下我們的代碼,當你停止滑動的時候,它會停在一個當前的位置,但是這個位置可能是兩個卡片(cell)的中間,或者偏一點的地方,我們需要的是對卡片進行操作,所以需要讓它居中。所以引入一個問題,如果只是不小心的觸碰到屏幕,稍稍的滑了一下,需要切換嗎?顯然是不需要的,把這個一下量化一下(設置為distance)以后,變成<distance的距離不需要切換,而>distance的距離就切換卡片。在這樣的思路確定以后,我們開始實現這個邏輯
代理也是繼承的,所以這樣的效果應該在scrollView的代理里實現

//開始拖拽collectionView(scrollView的子類)
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
//停止拖拽collectionView
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate

在這兩個方法里面可以拿到起始的位置,和結束的位置,他們到底是怎么樣的,可以在里面打印輸出一下

//向左滑
2018-01-29 19:37:03.265 卡片轉場1[31481:1202165] 開始拖動0.000000
2018-01-29 19:37:04.624 卡片轉場1[31481:1202165] 停止拖動105.000000
2018-01-29 19:37:06.905 卡片轉場1[31481:1202165] 開始拖動105.000000
2018-01-29 19:37:07.729 卡片轉場1[31481:1202165] 停止拖動124.000000
2018-01-29 19:37:10.943 卡片轉場1[31481:1202165] 開始拖動124.000000

//向右滑
2018-01-29 19:38:26.746 卡片轉場1[31481:1202165] 開始拖動1449.000000
2018-01-29 19:38:28.164 卡片轉場1[31481:1202165] 停止拖動1404.000000
2018-01-29 19:38:30.113 卡片轉場1[31481:1202165] 開始拖動1404.000000
2018-01-29 19:38:30.547 卡片轉場1[31481:1202165] 停止拖動1371.333333
2018-01-29 19:38:32.806 卡片轉場1[31481:1202165] 開始拖動1371.333333

明白這樣的變化以后,我們聲明幾個類內的變量

   CGFloat     _startDragX;        //開始移動的位置
   CGFloat     _endDragX;          //停止移動的位置
   CGFloat     _dragMiniDistance;  //最小移動的臨界值
   NSInteger   _currentIndex;      //當前切換到的卡片索引位置
   NSInteger   _maxIndex;          //最大的索引位置
//在開始拖動的方法里獲取到 _startDragX
_startDragX = scrollView.contentOffset.x;
//在停止拖動的方法里獲取到_endDragX
_endDragX = scrollView.contentOffset.x;
然后根據上面的邏輯進行一系列的簡單運算判斷:
CGFloat delta = _startDragX - _endDragX;

if (delta >= _dragMiniDistance) {
    //向右滑動
    _currentIndex -= 1;  
 } else if (delta * -1 >= _dragMiniDistance) {
     //向左滑動
     _currentIndex += 1;
 }
    
 _currentIndex = _currentIndex <= 0 ? 0 : _currentIndex;
 _currentIndex = _currentIndex >= _maxIndex ? _maxIndex : _currentIndex;

這樣我們就可以調用scrollToItemAtIndexPath: atScrollPosition這個方法實現滑動了,但是這里要注意一點,就是所有tableView,collectionView這些數據源方法的特殊性,如果你同步執(zhí)行的話,他們的數據源方法可能還沒有在內部算清,所以就會出Bug,所以對于這個方法,我們需要用主隊列異步去執(zhí)行,很多代碼里面就把類似上面的一堆運算也扔在異步隊列里,其實不用,我們只是要把UI更新放進去.

dispatch_async(dispatch_get_main_queue(), ^{
 [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:_currentIndex inSection:0]
 atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
 });

//順帶利用這個_currentIndex的索引,可以把背后的蒙版也換一下
self.BgView.image = [UIImage imageNamed:self.imageArray[_currentIndex].cardPicName];

調整布局屬性

現在有個問題需要我們考慮,就是每個卡片的大小都是一樣,看示例gif明顯當前屏幕中間的大,兩邊的小。所以我們也要這樣做,調整cell的大小,在UICollectionView里面,他們的大小(還有透明度這些屬性)是歸于布局屬性負責的,所以我們要調整布局屬性,在哪里調整,當然是代理方法了,一個UICollectionViewFlowLayout的父類代理UICollectionViewLayout里面有l(wèi)ayoutAttributesForElementsInRect:(CGRect)rect這樣一個方法,是這樣介紹的
//Implement -layoutAttributesForElementsInRect:
//to return layout attributes for for supplementary or decoration views, or to perform layout in an as-needed-on-screen fashion.
翻譯過來就是:返回 追加的 或者 裝飾視圖 布局屬性 或者去適應屏幕上的布局。
還有一個這樣的方法:
-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
很明顯是對每個具體cell的布局進行調整
這里我們的需求其實是對兩邊cell的大小進行統(tǒng)一的調整,其實也不用具體到張三,李四cell這個具體的cell上,只要是在兩邊的就進行縮放就好了,所以選擇第一個方法去實現

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
    //這樣就拿到了所有的布局屬性
    NSArray<UICollectionViewLayoutAttributes *> *arr = [super layoutAttributesForElementsInRect:rect].copy;
    
    return arr;
}

這里同樣有個問題需要說明,就是蘋果規(guī)定,我們不能直接去拿系統(tǒng)生成這些原有的布局屬性,對這些原有的布局進行更改,直接更改會報錯(我們不展開去解釋這個問題),所以我用了.copy,其實這樣用也不好,在你運行的時候,還是會給你一堆警告

Logging only once for UICollectionViewFlowLayout cache mismatched frame
UICollectionViewFlowLayout has cached frame mismatch for index path <NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0} - cached value: {{82.666666666666686, 163.66667320702987}, {248.39999999999998, 408.88887580816248}}; expected value: {{82.666666666666671, 163.66666666666666}, {248.39999999999998, 408.88888888888891}}
This is likely occurring because the flow layout subclass CardFlowLayout is modifying attributes returned by UICollectionViewFlowLayout without copying them

就是告訴你這樣不好,但是通常好像也不會出錯,但是為了避免這樣,我們可以干脆做一個拷貝的方法

- (NSArray *)getCopyOfAttributes:(NSArray *)attributes {
    NSMutableArray *copyArr = [NSMutableArray new];
    for (UICollectionViewLayoutAttributes *attribute in attributes) {
        [copyArr addObject:[attribute copy]];
    }
    return copyArr;
}

這樣就ok了。
接著寫我們的代碼,這里單獨說一點collectionView的位置不是屏幕大小的位置,它是內部展開的位置

    //獲取屏幕中線
    CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.bounds.size.width / 2.0;
    //拿到每個屬性
    for (UICollectionViewLayoutAttributes *attr in arr) {
       
        //布局屬性與中線的距離
        CGFloat distance = fabs(attr.center.x - centerX);
        //距離與屏幕寬的比例(為了計算縮放的比例)
        CGFloat disScale = distance / self.collectionView.bounds.size.width;
        //確定縮放的大小
        CGFloat scale = fabs(cos(disScale * M_PI / 4));
        
        //對布局屬性進行縮放變換
        //如果想要3D傾斜的變化,也可以在這里指定為3D變化
        attr.transform = CGAffineTransformMakeScale(1.0, scale);
        
        //同時也利用這個比例對透明度進行一下更改,顯得自然
        attr.alpha = scale;
    
    }

但是這樣運行以后我們又發(fā)現一個問題,就是他們好像是一順縮小的,越來越小,一看我們的代碼,是啊,我們設置的就是把屬性縮小了,縮小的屬性的后面的屬性就變得更小了。所以我們要調整代碼,讓他們下次恢復之前的約束,然后再進行變化,剛好有這樣一個方法:- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
//判定為布局需要被無效化并重新計算的時候,布局對象會被詢問以提供新的布局
所以我們只需要在這里返回YES就好了。
這樣這篇文章就可以完了

3月-12-2018 17-00-40.gif

再下面就要對cell內部進行復雜操作了。。。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽默認的外補...
    _Yfling閱讀 14,095評論 1 92
  • 在上篇文章 屬性動畫(一) 中已經對屬性動畫有了基本的介紹,本篇文章將對屬性動畫中稍微高級點的內容進行介紹,主要介...
    lijiankun24閱讀 458評論 0 0
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,812評論 25 709
  • 1 懶加載 懶加載與OC中的懶加載的區(qū)別:懶加載的類一旦 設置為nil 后, 懶加載就不會再次執(zhí)行,與OC中不同,...
    JunShine閱讀 245評論 0 0
  • 2011年9月,作為大一懵懂的小學妹,由著自己想作為志愿者的情愫,過關斬將成為紅十字協(xié)會的一員,便遇上了此生...
    傻子薛閱讀 227評論 0 1

友情鏈接更多精彩內容