iOS 輪播圖的幾種實(shí)現(xiàn)

大概的實(shí)現(xiàn)思路有這么幾種:
1 . UIScrollView + UIImageView
1.1. 使用比 圖片數(shù) 多 2 個(gè)的 UIImageView
1.2 使用三個(gè)UIImageView 實(shí)現(xiàn)
1.3 使用 兩個(gè) UIImageView實(shí)現(xiàn)
2.UICollectionView (當(dāng)然UICollectionView也是繼承自UIScrollView)
2.1 使用UICollectionView 的復(fù)用特性,復(fù)制 多份 圖片作為數(shù)據(jù)源,從中間開始顯示
2.2 在 第一張圖片前加 上最后一張圖片,在最后一張圖片前加上第一張圖片
注意:
1.記得設(shè)置 scrollView.isPagingEnabled = true
2. 控制 UIScrollView 回滾的時(shí)候 不要使用動(dòng)畫,否則會(huì)調(diào)用代理方法

無限輪播示意圖
1.1 使用比 圖片數(shù) 多 2 個(gè)的 UIImageView 實(shí)現(xiàn)

思路 : 創(chuàng)建 比圖片數(shù)多 2個(gè)UIImageView ,最一個(gè)ImageView顯示第一張圖片,第一個(gè)ImageView顯示最后一張圖片,從第二個(gè)到倒數(shù)第二個(gè)ImageView依次顯示第一張到最后一張圖片 當(dāng)滾動(dòng)到最后一張圖片, 即 :原始 圖片順序?yàn)?1,2,3,4,5,6 ,則八個(gè)ImageView 顯示圖片的順序?yàn)?code>6,1,2,3,4,5,6,1,依次把ImageView添加到UIScrollView上,最開始顯現(xiàn)第二個(gè)ImageView,當(dāng)滾動(dòng)到第一個(gè) ImageView的時(shí)候,控制UIScrollView滾動(dòng)到倒數(shù)第二個(gè) ImageView,滾動(dòng)到最后一個(gè) ImageView的時(shí)候,控制UIScrollView滾到第二個(gè)ImageView ,只有 中間的 是正式用來顯示的.兩頭的用來實(shí)現(xiàn)一個(gè)視差效果
核心代碼如下

 private lazy var mainScrollView : UIScrollView = {
        let scrollView = UIScrollView(frame: bounds)
        let scrollVWidth = bounds.width * CGFloat(images.count + 2)
        scrollView.contentSize = CGSize(width: scrollVWidth, height: bounds.height)
        scrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
        scrollView.delegate = self
        scrollView.bounces = true
        scrollView.isPagingEnabled = true
        for (index, imageV) in imageVs.enumerated() {
            imageV.frame =  CGRect(x: bounds.width  * CGFloat(index), y: 0, width: bounds.width, height: bounds.height)
            scrollView.addSubview(imageV)
        }
        return scrollView
    }()
// images 為根據(jù)傳 過來的圖片再在頭尾分別加上最后一張和第一張圖片創(chuàng)建的新數(shù)組
 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        if scrollView.contentOffset.x >= (bounds.width  *  CGFloat(images.count - 1)) {
            mainScrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
        }
        if scrollView.contentOffset.x <= 0 {
            mainScrollView.setContentOffset(CGPoint(x: bounds.width * CGFloat(images.count - 2), y: 0), animated: false)
        }
    }
1.2 使用三個(gè)UIImageView 實(shí)現(xiàn)

思路: 方法1.1 的進(jìn)化版,方法1.1 在有大量圖片的情況下,需要?jiǎng)?chuàng)建大量的ImageView 明顯不合適,所以我們 把中間的一個(gè)ImageView片來代替 1.1中的 大量中間圖片,即只有中間一張圖片是用來正式顯示圖片的,每次滾動(dòng)結(jié)束都需要滾回到 中間圖片位置,兩邊的ImageView 用來實(shí)現(xiàn)視差效果
核心代碼如下

// 這里為每個(gè) ImageView 添加一個(gè) tag ,用來標(biāo)識(shí)當(dāng)前顯示的圖片和后面計(jì)算將要顯示的圖片
  private lazy  var  leftImageV :  UIImageView = {
        let imageV = UIImageView(frame: bounds)
        imageV.frame = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height)
        imageV.tag = images.count - 1
        imageV.image = images[images.count - 1]
        return imageV
    }()
    private lazy  var  MidleImageV :  UIImageView = {
        let imageV = UIImageView(frame: bounds)
         imageV.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
          imageV.tag = 0
          imageV.image = images[0]
        return imageV
    }()
    private lazy  var  RightImageV :  UIImageView = {
        let imageV = UIImageView(frame: bounds)
         imageV.frame = CGRect(x: bounds.width * 2, y: 0, width: bounds.width, height: bounds.height)
          imageV.tag = 1
          imageV.image = images[1]
        return imageV
    }()
    private lazy var imageVs : Array<UIImageView> = {
        return [leftImageV,RightImageV,MidleImageV];
    }()
    
    private lazy var mainScrollView : UIScrollView = {
        let scrollView = UIScrollView(frame: bounds)
        let scrollVWidth = images.count >= 3 ? bounds.width * 3 : bounds.width * CGFloat(images.count)
        scrollView.contentSize = CGSize(width: scrollVWidth, height: bounds.height)
        scrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
        scrollView.delegate = self
        scrollView.bounces = true
        scrollView.isPagingEnabled = true
        scrollView.addSubview(leftImageV)
        scrollView.addSubview(MidleImageV)
        scrollView.addSubview(RightImageV)
        return scrollView
    }()

 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        //判斷 滾動(dòng)方向,右滾下標(biāo) +1 ,左滾下標(biāo) -1
        var  direction  : Int = 0
        if scrollView.contentOffset.x > bounds.width {
            direction = 1
        }else if  scrollView.contentOffset.x < bounds.width{
            direction = -1
        }
      let imageVs = [leftImageV,MidleImageV,RightImageV]
        // 根據(jù)圖片tag 和滾動(dòng)方向 計(jì)算 要顯示的 圖片,更新 tag 值
        for  imageV in  imageVs {
            var  index  = imageV.tag + direction
            if index == images.count {
                index = 0
            }else if index == -1{
                index = images.count - 1
            }
            imageV.image = images[index]
            imageV.tag = index
        }
        mainScrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
    }
1.3 使用 兩個(gè) UIImageView實(shí)現(xiàn)

思路: UIScrollViewcontentSize 依然 為 三倍 屏幕寬度,創(chuàng)建兩個(gè) ImageView 添加到 UIScrollView 上, 沒有滾動(dòng)時(shí),兩個(gè)imageView重疊,上面的ImageView顯示當(dāng)前圖片,發(fā)生滾動(dòng)的時(shí)候,判斷滾動(dòng)方向,把底下的ImageView移到 左邊或者右邊,滾動(dòng)結(jié)束,UIScrollView 滾動(dòng) 回 中間位置
核心代碼如下:

     private  var isChange : Bool = true
    let images : Array<UIImage>
    lazy var  bottomImageView : UIImageView = {
        let imageView = UIImageView(image: UIImage(named: "2.png"))
        imageView.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
        imageView.tag = 0
        return imageView
    }()
    
    lazy var  topImageView : UIImageView = {
        let imageView = UIImageView(image: UIImage(named: "1.png"))
        imageView.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
         imageView.tag = 0
        return imageView
    }()
    
    lazy var  scrollView : UIScrollView = {
        let scroView = UIScrollView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height))
        scroView.contentSize = CGSize(width: bounds.width * 3, height: bounds.height)
        scroView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
        scroView.isPagingEnabled = true
        scroView.bounces = false
        scroView.delegate = self
        scroView.addSubview(bottomImageView)
        scroView.addSubview(topImageView)
        return scroView
    }()
 
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
         imageOffset = scrollView.contentOffset.x
    }
   // 每次滾動(dòng)的時(shí)候 判斷 滾動(dòng)方向,調(diào)整底下的 imageview  的位置和顯示的圖片
   
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if isChange{
        if scrollView.contentOffset.x < bounds.width {
            imageOffset =  scrollView.contentOffset.x
            var tag = bottomImageView.tag - 1
            if tag < 0{
                tag = images.count - 1
            }
            bottomImageView.image = images[tag]
            bottomImageView.frame = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height)
            bottomImageView.tag = tag
        }else if  scrollView.contentOffset.x > bounds.width {
            imageOffset = scrollView.contentOffset.x
            var tag = bottomImageView.tag + 1
            if tag  == images.count{
                tag = 0
            }
            bottomImageView.image = images[tag]
            bottomImageView.frame = CGRect(x: bounds.width * 2, y: 0, width: bounds.width, height: bounds.height)
            bottomImageView.tag = tag
        }
        }
        isChange = false
    }
    
   //滾動(dòng)結(jié)束, scrollView 回滾到中間位置,更新 上面的 ImageView 的圖片
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
                bottomImageView.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
                topImageView.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
                print(bottomImageView.tag)
                topImageView.image = images[bottomImageView.tag]
        scrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
        isChange = true
    }

這里 因?yàn)?scrollViewDidScroll 代理滾動(dòng)過程中會(huì)一直調(diào)用,而我這里只需要判斷 一下滾動(dòng)方向然后調(diào)整 底下的ImageView, 所以用的 一個(gè)BOOL值來限制執(zhí)行,當(dāng)時(shí)沒想到一個(gè)比較好的方案,所以實(shí)現(xiàn)看起來比較low

2.1 使用UICollectionView 的復(fù)用特性,復(fù)制 多份 圖片作為數(shù)據(jù)源,從中間開始顯示

思路: 復(fù)制 多份 需要顯示的圖片 作為UICollectionView數(shù)據(jù)源,開始的時(shí)候 就把scrollView 滾動(dòng)到中間那份 數(shù)據(jù)的開頭位置,因?yàn)閺?fù)制的多份數(shù)據(jù),所以滾動(dòng)的時(shí)候就會(huì)無限輪播的效果,這里就不貼具體代碼,大家可以自行 嘗試下

2.2 在 第一張圖片前加 上最后一張圖片,在最后一張圖片前加上第一張圖片

思路:1.1 方法類似,只是使用 UICollectionView 的話因?yàn)?code>UICollectionView自身的復(fù)用機(jī)制,所以沒有 1.1方法 創(chuàng)建大量 ImageView的性能問題,同樣的,在首尾 分別加上最后和第一張圖片,當(dāng) 滾動(dòng)到這兩個(gè)位置的時(shí)候,控制 scrollView滾動(dòng)到中間顯示區(qū)的對(duì)應(yīng)位置
核心代碼如下:

   let images : Array<UIImage>
//創(chuàng)建  UICollectionView 
    lazy var collectionV : UICollectionView = {
        let layout  = UICollectionViewFlowLayout()
        layout.minimumInteritemSpacing = 0
        layout.minimumLineSpacing = 0
        layout.itemSize = CGSize(width: bounds.width, height: bounds.height)
        layout.scrollDirection = .horizontal
        let collectionV  = UICollectionView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height), collectionViewLayout: layout)
        collectionV.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        collectionV.dataSource = self
        collectionV.delegate = self
        collectionV.isPagingEnabled = true
        return collectionV
    }()
    //根據(jù)傳進(jìn)來的 圖片數(shù)組構(gòu)建 數(shù)據(jù)源數(shù)組
    init(frame: CGRect,images :Array<UIImage> ) {
        var tempImages : Array<UIImage> = [images[images.count - 1]]
        for  image in images {
            tempImages.append(image)
        }
        tempImages.append(images[0])
        self.images = tempImages
        super.init(frame: frame)
        addSubview(collectionV)
        collectionV.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
    }

extension XlXBannerCollectionV : 
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        cell.imageV = UIImageView(image: images[indexPath.row])
        return cell
    }
    
// 滾動(dòng)到 首尾的時(shí)候控制回滾
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        if scrollView.contentOffset.x >= (bounds.width  *  CGFloat(images.count - 1)) {
            collectionV.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
        }
        if scrollView.contentOffset.x <= 0 {
             collectionV.setContentOffset(CGPoint(x: bounds.width * CGFloat(images.count - 2), y: 0), animated: false)
        }
    }
}

無限輪播 整體 上就是 中間部分 是用來正式顯示的,首尾的是 在滾動(dòng)的時(shí)候讓用戶有種 后面還有內(nèi)容的錯(cuò)覺,當(dāng)滾動(dòng)停止,立馬回滾到中間顯示區(qū)的對(duì)應(yīng)位置,來達(dá)到無限輪播的視覺效果

自動(dòng)輪播的話在 scrollView 代理方法里面合適的位置開啟或暫停定時(shí)器來實(shí)現(xiàn),代碼中剔除了其他干擾只保留了核心部分,因?yàn)楣δ鼙容^簡(jiǎn)單,所以也沒注意代碼衛(wèi)生,實(shí)際開發(fā)中,各位小伙伴們還是要多多注意下代碼習(xí)慣,抽取封裝,配置工具類什么的來實(shí)現(xiàn)的漂亮一點(diǎn)

最后:寫完了才想起來兩張圖片的情況下沒做處理,可能還是會(huì)有些問題吧,道理都是一樣的,實(shí)際開發(fā)中多多注意下

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盔然,一起剝皮案震驚了整個(gè)濱河市之剧,隨后出現(xiàn)的幾起案子胖齐,更是在濱河造成了極大的恐慌豌拙,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件峡钓,死亡現(xiàn)場(chǎng)離奇詭異考婴,居然都是意外死亡剔蹋,警方通過查閱死者的電腦和手機(jī)赤嚼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顺又,“玉大人更卒,你說我怎么就攤上這事≈烧眨” “怎么了蹂空?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)果录。 經(jīng)常有香客問我上枕,道長(zhǎng),這世上最難降的妖魔是什么弱恒? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任辨萍,我火速辦了婚禮,結(jié)果婚禮上返弹,老公的妹妹穿的比我還像新娘锈玉。我一直安慰自己,他們只是感情好义起,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布拉背。 她就那樣靜靜地躺著,像睡著了一般默终。 火紅的嫁衣襯著肌膚如雪椅棺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天齐蔽,我揣著相機(jī)與錄音两疚,去河邊找鬼。 笑死含滴,一個(gè)胖子當(dāng)著我的面吹牛鬼雀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛙吏,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼源哩,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼鞋吉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起励烦,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤谓着,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后坛掠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赊锚,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年屉栓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了舷蒲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡友多,死狀恐怖牲平,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情域滥,我是刑警寧澤纵柿,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站启绰,受9級(jí)特大地震影響昂儒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜委可,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一渊跋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧着倾,春花似錦刹枉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至虎眨,卻和暖如春蟋软,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嗽桩。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工岳守, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人碌冶。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓湿痢,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子譬重,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359