iOS JXBanner - (多功能無限輪播圖框架)

(JXBanner 支持多種動畫變換, 支持純代碼布局和Xib布局)

JXBanner依賴于JXPageControl,并包含許多自定義接口呀舔,如轉(zhuǎn)換動畫特碳、視圖結(jié)構(gòu)和設(shè)置


    1. 開發(fā)環(huán)境: Xcode 7
    1. 運行條件: iOS(8.0+)
  • 開源框架:github地址

?

(如果使用有什么問題蹋岩,可以留言外恕,歡迎一起學(xué)習(xí)杆逗,歡迎star)


Installation [安裝]

安裝乡翅,只需將以下面代碼添加到您的Podfile:


platform :ios, '8.0'

target 'TargetName' do
pod 'JXBanner'
end


UI效果

  • default

不需要設(shè)置JXBanner -> JXBannerLayoutParams

default.gif

  • JXBannerTransformLinear
linear.gif

  • JXBannerTransformCoverflow
coverflow.gif

  • custom

需要實現(xiàn)JXBannerTransformable協(xié)議, 修改 UICollectionViewLayoutAttributes -> transform3D 或 transform 屬性

custom.gif

Frame set [框架集合]

?

Banner 輪播圖框架公用類文件
  • API ---> 開發(fā)者可以調(diào)用的所有接口
  • Cell ---> 框架提供cell基類 (如果想自定義cell內(nèi)容, 可以新建cell繼承于JXBannerBaseCell)
  • Common ---> 框架公用類文件
  • Transform ---> 動畫效果類文件 ( 如果框架提供的動畫效果不能滿足開發(fā)者需求, 可以新建實現(xiàn)JXBannerTransformable協(xié)議的struct/class, 修改 UICollectionViewLayoutAttributes -> transform3D 或 transform 屬性)
PageControl 指示器類文件
  • JXBannerPageControlBuilder ---> pageControl的構(gòu)建者類
  • JXBannerPageControlDefault ---> 框架默認的pageControl樣式 (可以通過實現(xiàn)JXBannerDataSource -> 【jxBanner(pageControl banner: numberOfPages: coverView: builder:) -> JXBannerPageControlBuilder】協(xié)議方法修改樣式)

JXBanner 重要文件介紹

?

JXBannerParams 【banner 屬性】
  • isAutoPlay ---> 自動播放
  • isBounces ---> 邊界能否越界滑動
  • timeInterval ---> 播放調(diào)度間隔
  • isShowPageControl ---> 是否加載內(nèi)部指示器(JXPageControl(框架特色)
  • cycleWay ---> 輪播方式(框架特色) (forward:無線向右播放, skipEnd:首尾自定義動畫跳轉(zhuǎn)罪郊, rollingBack:左右回滾模式)
  • edgeTransitionType ---> cycleWay 使用 skipEnd 中 可以選取動畫方式
  • edgeTransitionSubtype ---> cycleWay 使用 skipEnd 中 可以選取動畫方式

JXBannerLayoutParams 【banner布局蠕蚜、動畫屬性】
  • itemSize ---> cell大小。
  • itemSpacing --->cell左右邊距悔橄。
  • layoutType ---> 動畫效果JXBannerTransformable(框架特色)
  • minimumScale ---> cell 縮放系數(shù)靶累。
  • minimumAlpha ---> cell 透明度系數(shù)。
  • maximumAngle ---> cell 旋轉(zhuǎn)系數(shù)癣疟。
  • rateOfChange ---> cell 變化系數(shù)尺铣。
  • rateHorisonMargin ---> cell 水平間距調(diào)整系數(shù)。

JXBannerCellRegister 【cell注冊構(gòu)建者】
  • type ---> 注冊cell的類型争舞,必須是JXBannerBaseCell的子類

  • reuseIdentifier ---> cell重用標(biāo)識

    var type: JXBannerBaseCell.Type
    var reuseIdentifier: String


JXBanner 使用

?

Example 1
  • 默認實現(xiàn)示例

?


import SnapKit
import JXBanner

class JXDefaultVC: UIViewController {
    
    var pageCount = 5
    
    lazy var banner: JXBanner = {
        let banner = JXBanner()
        banner.backgroundColor = UIColor.black
        banner.placeholderImgView.image = UIImage(named: "banner_placeholder")
        banner.delegate = self
        banner.dataSource = self
        return banner
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(banner)
        banner.snp.makeConstraints { (maker) in
            maker.left.right.equalTo(view)
            maker.height.equalTo(250)
            maker.top.equalTo(view.snp_top).offset(100)
        }
        self.automaticallyAdjustsScrollViewInsets = false
    }
    
    deinit {
        print("\(#function) ----------> \(#file.components(separatedBy: "/").last?.components(separatedBy: ".").first ?? #file)")
    }
}

//MARK:- JXBannerDataSource
extension JXDefaultVC: JXBannerDataSource {
    
    // 注冊重用Cell標(biāo)識
    func jxBanner(_ banner: JXBannerType)
        -> (JXBannerCellRegister) {
            return JXBannerCellRegister(type: JXBannerCell.self,
                                        reuseIdentifier: "JXDefaultVCCell")
    }
    
    // 輪播總數(shù)
    func jxBanner(numberOfItems banner: JXBannerType)
        -> Int { return pageCount }
    
    // 輪播cell內(nèi)容設(shè)置
    func jxBanner(_ banner: JXBannerType,
                  cellForItemAt index: Int,
                  cell: JXBannerBaseCell)
        -> JXBannerBaseCell {
            let tempCell: JXBannerCell = cell as! JXBannerCell
            tempCell.layer.cornerRadius = 8
            tempCell.layer.masksToBounds = true
            tempCell.imageView.image = UIImage(named: "banner_placeholder")
            tempCell.msgLabel.text = String(index) + "---來嘍來嘍,他真的來嘍~"
            return tempCell
    }
    
    // banner基本設(shè)置(可選)
    func jxBanner(_ banner: JXBannerType,
                  layoutParams: JXBannerLayoutParams)
        -> JXBannerLayoutParams {
        return layoutParams
            .itemSize(CGSize(width: UIScreen.main.bounds.width - 40, height: 200))
            .itemSpacing(20)
    }
}

//MARK:- JXBannerDelegate
extension JXDefaultVC: JXBannerDelegate {
    
    // 點擊cell回調(diào)
    public func jxBanner(_ banner: JXBannerType,
                         didSelectItemAt index: Int) {
        print(index)
    }
    
}



Example 2

?

  • 個性化設(shè)置

import SnapKit
import JXBanner
import JXPageControl

class JXCustomVC: UIViewController {
    
    var pageCount = 5
    
    lazy var linearBanner: JXBanner = {[weak self] in
        let banner = JXBanner()
        banner.placeholderImgView.image = UIImage(named: "banner_placeholder")
        banner.backgroundColor = UIColor.black
        banner.indentify = "linearBanner"
        banner.delegate = self
        banner.dataSource = self
        return banner
    }()
    
    lazy var converflowBanner: JXBanner = {
        let banner = JXBanner()
        banner.placeholderImgView.image = UIImage(named: "banner_placeholder")
        banner.backgroundColor = UIColor.black
        banner.indentify = "converflowBanner"
        banner.delegate = self
        banner.dataSource = self
        return banner
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(linearBanner)
        view.addSubview(converflowBanner)
        linearBanner.snp.makeConstraints {(maker) in
            maker.left.right.equalTo(view)
            maker.height.equalTo(200)
            maker.top.equalTo(view.snp_top).offset(100)
        }
        
        converflowBanner.snp.makeConstraints {(maker) in
            maker.left.right.height.equalTo(linearBanner)
            maker.top.equalTo(linearBanner.snp_bottom).offset(100)
        }
        
        self.automaticallyAdjustsScrollViewInsets = false
    }
    
    deinit {
        print("\(#function) ----------> \(#file.components(separatedBy: "/").last?.components(separatedBy: ".").first ?? #file)")
    }
}

//MARK:- JXBannerDataSource
extension JXCustomVC: JXBannerDataSource {
    
    // 注冊重用Cell標(biāo)識
    func jxBanner(_ banner: JXBannerType)
        -> (JXBannerCellRegister) {

            if banner.indentify == "linearBanner" {
                return JXBannerCellRegister(type: JXBannerCell.self,
                                            reuseIdentifier: "LinearBannerCell")
            }else {
                return JXBannerCellRegister(type: JXBannerCell.self,
                                            reuseIdentifier: "ConverflowBannerCell")
            }
    }
    
    // 輪播總數(shù)
    func jxBanner(numberOfItems banner: JXBannerType)
        -> Int { return pageCount }
    
    // 輪播cell內(nèi)容設(shè)置
    func jxBanner(_ banner: JXBannerType,
                  cellForItemAt index: Int,
                  cell: JXBannerBaseCell)
        -> JXBannerBaseCell {
            let tempCell: JXBannerCell = cell as! JXBannerCell
            tempCell.layer.cornerRadius = 8
            tempCell.layer.masksToBounds = true
            tempCell.imageView.image = UIImage(named: "banner_placeholder")
            tempCell.msgLabel.text = String(index) + "---來嘍來嘍,他真的來嘍~"
            return tempCell
    }
    
    // banner基本設(shè)置(可選)
    func jxBanner(_ banner: JXBannerType,
                  params: JXBannerParams)
        -> JXBannerParams {
        
            if banner.indentify == "linearBanner" {
                return params
                    .timeInterval(2)
                    .cycleWay(.forward)
            }else {
                return params
                    .timeInterval(3)
                    .cycleWay(.forward)
            }
    }
    
    // banner布局、動畫設(shè)置
    func jxBanner(_ banner: JXBannerType,
                  layoutParams: JXBannerLayoutParams)
        -> JXBannerLayoutParams {
            
            if banner.indentify == "linearBanner" {
                return layoutParams
                    .layoutType(JXBannerTransformLinear())
                    .itemSize(CGSize(width: 250, height: 190))
                    .itemSpacing(10)
                    .rateOfChange(0.8)
                    .minimumScale(0.7)
                    .rateHorisonMargin(0.5)
                    .minimumAlpha(0.8)
            }else {
                return layoutParams
                    .layoutType(JXBannerTransformCoverflow())
                    .itemSize(CGSize(width: 300, height: 190))
                    .itemSpacing(0)
                    .maximumAngle(0.25)
                    .rateHorisonMargin(0.3)
                    .minimumAlpha(0.8)
            }
    }
    
    // 自定義pageControl樣式澈灼、布局
    //(基于jxPageControl, 如果不適用JXPageControl, 設(shè)置isShowPageControl = false竞川, 內(nèi)部pageControl將不會再次加載 ) 
    func jxBanner(pageControl banner: JXBannerType,
                  numberOfPages: Int,
                  coverView: UIView,
                  builder: JXBannerPageControlBuilder) -> JXBannerPageControlBuilder {

        if banner.indentify == "linearBanner" {
            let pageControl = JXPageControlScale()
            pageControl.contentMode = .bottom
            pageControl.activeSize = CGSize(width: 15, height: 6)
            pageControl.inactiveSize = CGSize(width: 6, height: 6)
            pageControl.activeColor = UIColor.red
            pageControl.inactiveColor = UIColor.lightGray
            pageControl.columnSpacing = 0
            pageControl.isAnimation = true
            builder.pageControl = pageControl
            builder.layout = {
                pageControl.snp.makeConstraints { (maker) in
                    maker.left.right.equalTo(coverView)
                    maker.top.equalTo(coverView.snp_bottom).offset(10)
                    maker.height.equalTo(20)
                }
            }
            return builder

        }else {
            let pageControl = JXPageControlExchange()
            pageControl.contentMode = .bottom
            pageControl.activeSize = CGSize(width: 15, height: 6)
            pageControl.inactiveSize = CGSize(width: 6, height: 6)
            pageControl.activeColor = UIColor.red
            pageControl.inactiveColor = UIColor.lightGray
            pageControl.columnSpacing = 0
            builder.pageControl = pageControl
            builder.layout = {
                pageControl.snp.makeConstraints { (maker) in
                    maker.left.right.equalTo(coverView)
                    maker.top.equalTo(coverView.snp_bottom).offset(10)
                    maker.height.equalTo(20)
                }
            }
            return builder
        }

    }
    
}

//MARK:- JXBannerDelegate
extension JXCustomVC: JXBannerDelegate {
    
    // 點擊cell回調(diào)
    public func jxBanner(_ banner: JXBannerType,
                         didSelectItemAt index: Int) {
        print(index)
    }
    
    // 設(shè)置自定義覆蓋View, 比如添加自定義外部pageControl和布局
    func jxBanner(_ banner: JXBannerType, coverView: UIView) {
        let title = UILabel()
        title.frame = CGRect(x: 0, y: 0, width: 100, height: 30)
        title.text = "JXBanner"
        title.textColor = UIColor.red
        title.font = UIFont.systemFont(ofSize: 16)
        coverView.addSubview(title)
    }
    
    // 最中心顯示cell 索引
    func jxBanner(_ banner: JXBannerType, center index: Int) {
        print(index)
    }
}

Example 3

如果框架提供的動畫效果不能滿足開發(fā)者需求:

    1. 輪播圖動畫樣式開發(fā)者可以自定義實現(xiàn), 只要是新建實現(xiàn)JXBannerTransformable協(xié)議的struct/class, 修改 UICollectionViewLayoutAttributes -> transform3D 或 transform 屬性)

//
//  JXCustomTransform.swift
//  JXBanner_Example
//
//  Created by 譚家祥 on 2019/7/30.
//  Copyright ? 2019 CocoaPods. All rights reserved.
//

import UIKit
import JXBanner

struct JXCustomTransform: JXBannerTransformable {
    
    public func transformToAttributes(collectionView: UICollectionView,
                                      params: JXBannerLayoutParams,
                                      attributes: UICollectionViewLayoutAttributes) {
        
        let collectionViewWidth = collectionView.frame.width
        if collectionViewWidth <= 0 { return }
        
        let centetX = collectionView.contentOffset.x + collectionViewWidth * 0.5;
        let delta = abs(attributes.center.x - centetX)
        let calculateRate = 1 - delta / collectionViewWidth
        let angle = min(delta / collectionViewWidth * (1 - params.rateOfChange), params.maximumAngle)
        let alpha = max(calculateRate, params.minimumAlpha)
        
        
        applyCoverflowTransformToAttributes(viewCentetX: centetX,
                                            attributes: attributes,
                                            params: params,
                                            angle: angle,
                                            alpha: alpha,
                                            calculateRate: calculateRate)
    }
    
    func applyCoverflowTransformToAttributes(viewCentetX: CGFloat,
                                             attributes: UICollectionViewLayoutAttributes,
                                             params: JXBannerLayoutParams,
                                             angle: CGFloat,
                                             alpha: CGFloat,
                                             calculateRate: CGFloat) -> Void {
        var transform3D: CATransform3D = CATransform3DIdentity
        
        
        let location = JXBannerTransfrom.itemLocation(viewCentetX: viewCentetX,
                                                      itemCenterX: attributes.center.x)

        var _angle = angle
        var _alpha = alpha
        var _translateX: CGFloat = 0
        var _translateY: CGFloat = 0
        attributes.zIndex = 0
        
        switch location {
        case .left:
            _angle = angle
            _translateX = 0.2 * attributes.size.width * (1 - calculateRate) / 4
            _translateY = 0.4 * attributes.size.height * (1 - calculateRate)
            
            
        case .right:
            _angle = -angle
            _translateX = -0.2 * attributes.size.width * (1 - calculateRate) / 4
            _translateY = 0.4 * attributes.size.height * (1 - calculateRate)
            
        case .center:
            _angle = 0
            _alpha = 1
            _translateY = 0
            attributes.zIndex = 10000
        }
        
        transform3D = CATransform3DTranslate(transform3D, _translateX, _translateY, 0)
        transform3D = CATransform3DRotate(transform3D, -CGFloat.pi * _angle, 0, 0, 1)
        attributes.alpha = _alpha
        attributes.transform3D = transform3D
    }

}


    1. 設(shè)置自定義實現(xiàn)動畫

JXBannerDataSource -> 【jxBanner(_ banner: layoutParams: ) -> JXBannerLayoutParams】


    // JXCustomTransform()

    func jxBanner(_ banner: JXBannerType,
                  layoutParams: JXBannerLayoutParams)
        -> JXBannerLayoutParams {
            
            return layoutParams
                .layoutType(JXCustomTransform())
    }

更多設(shè)置可以參考示例 Demo地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叁熔,一起剝皮案震驚了整個濱河市委乌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荣回,老刑警劉巖遭贸,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異心软,居然都是意外死亡壕吹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門删铃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耳贬,“玉大人,你說我怎么就攤上這事猎唁≈渚ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵诫隅,是天一觀的道長腐魂。 經(jīng)常有香客問我,道長逐纬,這世上最難降的妖魔是什么蛔屹? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮豁生,結(jié)果婚禮上判导,老公的妹妹穿的比我還像新娘嫉父。我一直安慰自己,他們只是感情好眼刃,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布绕辖。 她就那樣靜靜地躺著,像睡著了一般擂红。 火紅的嫁衣襯著肌膚如雪仪际。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天昵骤,我揣著相機與錄音树碱,去河邊找鬼。 笑死变秦,一個胖子當(dāng)著我的面吹牛成榜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蹦玫,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赎婚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了樱溉?” 一聲冷哼從身側(cè)響起挣输,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎福贞,沒想到半個月后撩嚼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡挖帘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年完丽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拇舀。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡舰涌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出你稚,到底是詐尸還是另有隱情瓷耙,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布刁赖,位于F島的核電站搁痛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏宇弛。R本人自食惡果不足惜鸡典,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望枪芒。 院中可真熱鬧彻况,春花似錦谁尸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悍赢,卻和暖如春决瞳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背左权。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工皮胡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赏迟。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓屡贺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锌杀。 傳聞我的和親對象是個殘疾皇子甩栈,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353