• iOS 3D卡片式轮播

    2018-07-24 14:37:26
    支持五险轮播,可以加载本地图片,也可以加载网络图片,可以根据自己的需求自定义 UITableViewDelegate /** * 当前显示cell的Size(中间页显示大小) * * @param flowView <#flowView de...

    效果:
    这里写图片描述

    参考UITableView的UITableViewDataSource和UITableViewDelegate两个方法实现;支持五险轮播,可以加载本地图片,也可以加载网络图片,可以根据自己的需求自定义
    Demo地址

    UITableViewDelegate

    /**
     *  当前显示cell的Size(中间页显示大小)
     *
     *  @param flowView <#flowView description#>
     *
     *  @return <#return value description#>
     */
    - (CGSize)sizeForPageInFlowView:(HQFlowView *)flowView;
    
    /**
     *  滚动到了某一列
     *
     *  @param pageNumber <#pageNumber description#>
     *  @param flowView   <#flowView description#>
     */
    - (void)didScrollToPage:(NSInteger)pageNumber inFlowView:(HQFlowView *)flowView;
    
    /**
     *  点击了第几个cell
     *
     *  @param subView 点击的控件
     *  @param subIndex    点击控件的index
     *
     *  @return <#return value description#>
     */
    - (void)didSelectCell:(HQIndexBannerSubview *)subView withSubViewIndex:(NSInteger)subIndex;

    UITableViewDataSource

    /**
     *  返回显示View的个数
     *
     *  @param flowView <#flowView description#>
     *
     *  @return <#return value description#>
     */
    - (NSInteger)numberOfPagesInFlowView:(HQFlowView *)flowView;
    
    /**
     *  给某一列设置属性
     *
     *  @param flowView <#flowView description#>
     *  @param index    <#index description#>
     *
     *  @return <#return value description#>
     */
    - (HQIndexBannerSubview *)flowView:(HQFlowView *)flowView cellForPageAtIndex:(NSInteger)index;
    展开全文
  • 概述CoverFlow效果是IOS上自带的控件,类似3d轮播图效果。 在Android中也有很多的相关实现,大体都是使用Gallery和Camera来实现的。相关开源库:ImageCoverFlow效果图:但是Gallery已经被标记过时,而且ViewPager更...

    概述

    CoverFlow效果是IOS上自带的控件,类似3d轮播图效果。
    在Android中也有很多的相关实现,大体都是使用Gallery和Camera来实现的。

    相关开源库:ImageCoverFlow

    效果图:

    这里写图片描述

    但是Gallery已经被标记过时,而且ViewPager更方便使用。而且ViewPager中有个接口ViewPagerTransformer,专门用于给ViewPager设置翻页动画的。

    ViewPagerTransformer使用

    自定义类实现ViewPagerTransformer接口,viewpager调用setPageTransformer即可。

    //参数含义:page绘制顺序;transformer实例对象
    ViewPager.setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer);

    每次界面切换,transformPage()都会为viewpager容器下的缓存页面调用该方法。如,第三页可见且用户向第四页拖动,在操作的各个阶段为第二,三,四页分别调用(ViewPager默认缓存3页)。

    //其中view即viewpager的当前item,
    transformPage(View view, float position)

    如果是3页的 情况下,当前也position的取值范围是[-1,1]
    前面的position:[-Infinity,-1)
    后面的position:(1,+Infinity]

    clipChildren

    clipChildren是指子控件是否超过padding区域,这个属性默认是true,利用此属性即可设置viewpager一屏展示多个item,如果将page 沿着y轴旋转即可实现类似coverflow效果了。

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:id="@+id/vp_container"
        android:layout_height="match_parent"
        android:clipChildren="false">
    
        <com.bobomee.android.scrollloopviewpager.autoscrollviewpager.AutoScrollViewPager
            android:id="@+id/picslooper3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="100dp"
            android:layout_marginRight="100dp"
            android:clipChildren="false" />
    
        <com.bobomee.android.drawableindicator.widget.DrawableIndicator
            android:id="@+id/pageIndexor3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            app:indicator_height="15dp"
            app:indicator_margin="15dp"
            app:indicator_select_src="@drawable/select_drawable"
            app:indicator_unselect_src="@drawable/unselect_drawable"
            app:indicator_width="15dp" />
    </RelativeLayout>

    java code:

      PagerAdapter adapter;
            final AutoScrollViewPager viewPager = (AutoScrollViewPager) findViewById(R.id.picslooper3);
            viewPager.setAdapter(adapter = new FragmentStateAdapter(getSupportFragmentManager()));
            viewPager.setDirection(AutoScrollViewPager.LEFT);
            viewPager.setPageTransformer(true, new RotateTransformer());
    
            BaseIndicator pageIndex = (BaseIndicator) findViewById(R.id.pageIndexor3);
            pageIndex.setViewPager(viewPager);
    
            viewPager.startAutoScroll();
    
            //设置幕后item的缓存数目
            viewPager.setOffscreenPageLimit(adapter.getCount());
    
            //设置页与页之间的间距
            int margin = ((ViewGroup.MarginLayoutParams) viewPager.getLayoutParams()).leftMargin;
            viewPager.setPageMargin(-(px2dip(this, margin)) / 2);
    
            //将父类的touch事件分发至viewPgaer,否则只能滑动中间的一个view对象
            RelativeLayout vpContainer = (RelativeLayout) findViewById(R.id.vp_container);
            vpContainer.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    return viewPager.dispatchTouchEvent(event);
                }
            });

    transformer:

    public class RotateTransformer implements ViewPager.PageTransformer {
    
        public static final float MAX_SCALE = 1.2f;
        public static final float MIN_SCALE = 0.6f;
    
        @Override
        public void transformPage(View page, float position) {
            page.setRotationY(position * -45);
    
            if (position < -1) {
                position = -1;
            } else if (position > 1) {
                position = 1;
            }
    
            float tempScale = position < 0 ? 1 + position : 1 - position;
    
            float slope = (MAX_SCALE - MIN_SCALE) / 1;
            //一个公式
            float scaleValue = MIN_SCALE + tempScale * slope;
            page.setScaleX(scaleValue);
            page.setScaleY(scaleValue);
    
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
                page.getParent().requestLayout();
            }
    
        }
    }

    效果图:
    3dbanner

    实例代码:autoscrollloopviewpager

    展开全文
  • ios-3D轮播图.zip

    2020-07-12 23:31:31
    使用iCarousel开源项目进行3D轮播图转换效果
  • ios实现超炫3d界面切换。。。。。。。。。。。。。。。。。。。。。。
  • ios-3d轮播.zip

    2020-05-06 23:33:53
    简单的实现3d轮播
  • (JXBanner 支持多种动画变换, 支持纯代码布局和Xib布局) ...运行条件: iOS(8.0+) 开源框架:github地址   (如果使用有什么问题,可以留言,欢迎一起学习,欢迎star) Installation [安装] 安...
    (JXBanner 支持多种动画变换, 支持纯代码布局和Xib布局)

    JXBanner依赖于JXPageControl,并包含许多自定义接口,如转换动画、视图结构和设置


      1. 开发环境: Xcode 7
      1. 运行条件: iOS(8.0+)
    • 开源框架:github地址

     

    (如果使用有什么问题,可以留言,欢迎一起学习,欢迎star)


    Installation [安装]

    安装,只需将以下面代码添加到您的Podfile:

    
    platform :ios, '8.0'
    
    target 'TargetName' do
    pod 'JXBanner'
    end
    
    

    UI效果

    • default

    不需要设置JXBanner -> JXBannerLayoutParams

    在这里插入图片描述


    • JXBannerTransformLinear

    在这里插入图片描述


    • JXBannerTransformCoverflow

    在这里插入图片描述


    • custom

    需要实现JXBannerTransformable协议, 修改 UICollectionViewLayoutAttributes -> transform3D 或 transform 属性

    在这里插入图片描述


    Frame set [框架集合]

     

    Banner 轮播图框架公用类文件
    • API —> 开发者可以调用的所有接口
    • Cell —> 框架提供cell基类 (如果想自定义cell内容, 可以新建cell继承于JXBannerBaseCell)
    • Common —> 框架公用类文件
    • Transform —> 动画效果类文件 ( 如果框架提供的动画效果不能满足开发者需求, 可以新建实现JXBannerTransformable协议的struct/class, 修改 UICollectionViewLayoutAttributes -> transform3D 或 transform 属性)
    PageControl 指示器类文件
    • JXBannerPageControlBuilder —> pageControl的构建者类
    • JXBannerPageControlDefault —> 框架默认的pageControl样式 (可以通过实现JXBannerDataSource -> 【jxBanner(pageControl banner: numberOfPages: coverView: builder:) -> JXBannerPageControlBuilder】协议方法修改样式)

    JXBanner 重要文件介绍

     

    JXBannerParams 【banner 属性】
    • isAutoPlay —> 自动播放
    • isBounces —> 边界能否越界滑动
    • timeInterval —> 播放调度间隔
    • isShowPageControl —> 是否加载内部指示器(JXPageControl(框架特色)
    • cycleWay —> 轮播方式(框架特色) (forward:无线向右播放, skipEnd:首尾自定义动画跳转, rollingBack:左右回滚模式)
    • edgeTransitionType —> cycleWay 使用 skipEnd 中 可以选取动画方式
    • edgeTransitionSubtype —> cycleWay 使用 skipEnd 中 可以选取动画方式

    JXBannerLayoutParams 【banner布局、动画属性】
    • itemSize —> cell大小。
    • itemSpacing —>cell左右边距。
    • layoutType —> 动画效果JXBannerTransformable(框架特色)
    • minimumScale —> cell 缩放系数。
    • minimumAlpha —> cell 透明度系数。
    • maximumAngle —> cell 旋转系数。
    • rateOfChange —> cell 变化系数。
    • rateHorisonMargin —> cell 水平间距调整系数。

    JXBannerCellRegister 【cell注册构建者】
    • type —> 注册cell的类型,必须是JXBannerBaseCell的子类

    • reuseIdentifier —> cell重用标识

      var type: JXBannerBaseCell.Type
      var reuseIdentifier: String


    JXBanner 使用

     

    Example 1
    • 默认实现示例

     

    
    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标识
        func jxBanner(_ banner: JXBannerType)
            -> (JXBannerCellRegister) {
                return JXBannerCellRegister(type: JXBannerCell.self,
                                            reuseIdentifier: "JXDefaultVCCell")
        }
        
        // 轮播总数
        func jxBanner(numberOfItems banner: JXBannerType)
            -> Int { return pageCount }
        
        // 轮播cell内容设置
        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基本设置(可选)
        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回调
        public func jxBanner(_ banner: JXBannerType,
                             didSelectItemAt index: Int) {
            print(index)
        }
        
    }
    
    
    

    Example 2

     

    • 个性化设置
    
    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标识
        func jxBanner(_ banner: JXBannerType)
            -> (JXBannerCellRegister) {
    
                if banner.indentify == "linearBanner" {
                    return JXBannerCellRegister(type: JXBannerCell.self,
                                                reuseIdentifier: "LinearBannerCell")
                }else {
                    return JXBannerCellRegister(type: JXBannerCell.self,
                                                reuseIdentifier: "ConverflowBannerCell")
                }
        }
        
        // 轮播总数
        func jxBanner(numberOfItems banner: JXBannerType)
            -> Int { return pageCount }
        
        // 轮播cell内容设置
        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基本设置(可选)
        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布局、动画设置
        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, 设置isShowPageControl = false, 内部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回调
        public func jxBanner(_ banner: JXBannerType,
                             didSelectItemAt index: Int) {
            print(index)
        }
        
        // 设置自定义覆盖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

    如果框架提供的动画效果不能满足开发者需求:

      1. 轮播图动画样式开发者可以自定义实现, 只要是新建实现JXBannerTransformable协议的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. 设置自定义实现动画

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

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

    更多设置可以参考示例 Demo地址

    展开全文
  • 个人感觉CollectionView过于强大,基本什么界面都能用他来完成需求,只是如果自定义Layout的时候可能性能开 销大。如果是普通的需求,他和tableView并没有多大的区别,同样都是通过datasource和delegate两个代理来...

    第二次更新:更加丰富的相册图片展示


    1.由于不在继承与FlowLayout,只是单纯继承与UIColletionLayout

    2.关键方法 layoutAttributesForItemAtIndexPath 在返回的对应index返回需要布局的UICollectionViewLayoutAttributes属性即可

    3.还是直接下载代码看自定义的Layout吧,有问题请留言,大过年的写的比较仓促

    传送门


    个人感觉CollectionView过于强大,基本什么界面都能用他来完成需求,只是如果自定义Layout的时候可能性能开销大。如果是普通的需求,他和tableView并没有多大的区别,同样都是通过datasource和delegate两个代理来进行用户交互稍微回顾下CollectionView的构成

    1.Cells

    2.Supplementary Views 追加视图 (sectionHeader or footer)

    3.Decoration Views 装饰视图 (用作背景展示) 这货貌似还没怎么用过

    另一方面,对于cell的组织方式和样式,确实由于collectionView比tableView要复杂的多,因此没有按照tableView方式来定义,而是专门启用了一个布局类UICollectionViewlayout来对collectionView进行布局。废话不多说,系统自带的Layout已经是很常见了,今天写个自定义Layout来做些看起来还是蛮不错的Demo

    正常人的Demo





    先理论BB以下,不想看的请跳过

    实现一个自定义Layout的正常做法就是继承与UICollectionViewlayout,然后重载以下方法,听我一一道来

    三个必须手动重载的方法

    1.-(CGSize)collectionViewContentSize
    
    返回可见区域内的大小
    
    
    
    2.-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
    
    返回的是包含UICollectionViewlayoutAttributes对象的数组,该对象可以是cell,supplementary或装饰视图
    
    
    
    
    返回对应indexPath下cell的布局属性
    
    -(UICollectionViewLayoutAttributes _)layoutAttributesForItemAtIndexPath:(NSIndexPath _)indexPath
    
    
    
    返回对应indexpath下对应的追加视图属性 (没有可不重载)
    
    
    -(UICollectionViewLayoutAttributes _)layoutAttributesForSupplementaryViewOfKind:(NSString _)kind atIndexPath:(NSIndexPath *)indexPath
    
    
    返回对应indexpath下对应的追加视图属性 (没有可不重载)
    
    
    -(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString_)decorationViewKind atIndexPath:(NSIndexPath _)indexPath
    
    
    3.-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
    
    当边界发生变化时,也就是滚动的时候,如果返回Yes,就是会一直调用,而且不断刷新重新计算布局信息
    一个自动重载的方法
    
    -(void)prepareLayout
    
    一般在该方法中设定一些必要的layout的结构和初始需要的参数等。


    注意:在需要更新layout时,需要给当前layout发送 -invalidateLayout,该消息会立即返回,并且预约在下一个loop的时候刷新当前layout,这一点和UIView的setNeedsLayout方法十分类似。在-invalidateLayout后的下一个collectionView的刷新loop中,又会从prepareLayout开始,依次再调用-collectionViewContentSize和-layoutAttributesForElementsInRect来生成更新后的布局。



    Demo分析

    1.简单的做个弹出的动画容器View 以下的self就是指弹出来的View,View上面给了一层underBackView,最终是在改View上面布局

    // self是继承于UIView的,给上面的第一个View容器加个动画
    - (void)showInSuperView:(UIView *)superView
    {
        CAKeyframeAnimation *popAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
        popAnimation.duration = 0.25;
        popAnimation.values = @[[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1f, 0.1f, 1.0f)],
                                [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0f, 1.0f, 1.0f)]];
        popAnimation.keyTimes = @[@0.2f, @1.0f];
        popAnimation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                                         [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                                         [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
        // 在点击按钮的时候在改方法里面add到父视图上面去
        [superView addSubview:self];
        // 给View上面的容器View加动画
        [self.underBackView.layer addAnimation:popAnimation forKey:nil];
    }


    2.简单的懒加载布局视图以及一些回调delegate的设置就没必要多说了,大家都知道

    #pragma mark - 懒加载
    - (UIView *)underBackView
    {
        if (_underBackView == nil) {
            _underBackView = [[UIView alloc] init];
            _underBackView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.8];
            _underBackView.originX = 30;
            _underBackView.originY = 60;
            _underBackView.width = SCREEN_WIDTH - 2 * _underBackView.originX;
            _underBackView.height = SCREEN_HEIGHT - 2 * _underBackView.originY;
            _underBackView.layer.cornerRadius = 5;
            _underBackView.layer.borderColor = [UIColor redColor].CGColor;
            _underBackView.layer.borderWidth = 2.0f;
        }
        return _underBackView;
    }
    
    - (UILabel *)nameLabel
    {
        if (_nameLabel == nil) {
            _nameLabel = [[UILabel alloc] init];
            _nameLabel.textAlignment = NSTextAlignmentCenter;
            _nameLabel.backgroundColor = [UIColor whiteColor];
            _nameLabel.font = [UIFont boldSystemFontOfSize:20];
            _nameLabel.textColor = [UIColor blueColor];
            _nameLabel.layer.cornerRadius = 5.0f;
            _nameLabel.layer.borderColor = [UIColor blackColor].CGColor;
            _nameLabel.layer.borderWidth = 2.0f;
        }
        return _nameLabel;
    }
    
    - (UIButton *)selectedButton
    {
        if (_selectedButton == nil) {
            _selectedButton = [UIButton buttonWithType:UIButtonTypeCustom];
            _selectedButton.backgroundColor = [UIColor blackColor];
            [_selectedButton setTitle:@"选这个" forState:UIControlStateNormal];
            [_selectedButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
            [_selectedButton addTarget:self action:@selector(chooseDone:) forControlEvents:UIControlEventTouchUpInside];
            _selectedButton.layer.cornerRadius = 20.0f;
            _selectedButton.layer.borderWidth = 2.0f;
            _selectedButton.layer.borderColor = [UIColor whiteColor].CGColor;
        }
        return _selectedButton;
    }
    - (void)chooseDone:(UIButton *)button
    {
        if (self.delegate && [self.delegate respondsToSelector:@selector(selectedHero:)]) {
            [self.delegate selectedHero:self.dataSource[_selectedIndex]];
        }
    }
    
    - (UIButton *)closeButton
    {
        if (_closeButton == nil) {
            _closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
            _closeButton.backgroundColor = [UIColor redColor];
            [_closeButton setImage:[UIImage imageNamed:@"close"] forState:UIControlStateNormal];
            [_closeButton addTarget:self action:@selector(close:) forControlEvents:UIControlEventTouchUpInside];
        }
        return _closeButton;
    }
    
    - (void)close:(UIButton *)button
    {
        if (self.delegate && [self.delegate respondsToSelector:@selector(closePopView)]) {
            [self.delegate closePopView];
        }
    }
    - (UICollectionView *)collectionView
    {
        if (_collectionView == nil) {
            MKJCollectionViewFlowLayout *flow = [[MKJCollectionViewFlowLayout alloc] init];
            flow.scrollDirection = UICollectionViewScrollDirectionHorizontal;
            flow.itemSize = CGSizeMake(self.underBackView.width / 2, self.underBackView.width - 100);
            flow.minimumLineSpacing = 30;
            flow.minimumInteritemSpacing = 30;
            flow.needAlpha = YES;
            flow.delegate = self;
            CGFloat oneX =self.underBackView.width / 4;
            flow.sectionInset = UIEdgeInsetsMake(0, oneX, 0, oneX);
            
            _collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 30, self.underBackView.bounds.size.width, self.underBackView.bounds.size.height * 0.65) collectionViewLayout:flow];
            _collectionView.backgroundColor = [UIColor whiteColor];
            _collectionView.delegate = self;
            _collectionView.dataSource = self;
            _collectionView.showsHorizontalScrollIndicator = NO;
            [_collectionView registerNib:[UINib nibWithNibName:indentify bundle:nil] forCellWithReuseIdentifier:indentify];
        }
        return _collectionView;
    }
    

    3.关键是还是要看最终自定义的FlowLayout一些小东西的实现

    重载的方法和方式在上面都已经理论了一番了,需要的自己去上面看看

    第一个效果:需要一个放大的效果和透明的效果 

    // 重载第一个方法
    // 返回可见区域的的内容尺寸
    - (CGSize)collectionViewContentSize
    {
        return [super collectionViewContentSize];
    }
    
    // 重载方法第二个
    // 返回rect中所有元素的布局属性
    // 返回的是包含UICollectionViewLayoutAttributes的NSArray
    // UICollectionViewAttributes可以是cell,追加视图以及装饰视图的信息,通过以下三个不同的方法可以获取到不同类型的UICollectionViewLayoutAttributes属性
    // layoutAttributesForCellWithIndexPath:  返回对应cell的UICollectionViewAttributes布局属性
    // layoutAtttibutesForSupplementaryViewOfKind:withIndexPath: 返回装饰的布局属性 如果没有追加视图可不重载
    // layoutAttributesForDecorationViewOfKind:withIndexPath: 返回装饰的布局属性  如果没有可以不重载
    - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
    {
        //1. 获取可见区域
        CGRect visibleRect = CGRectMake(self.collectionView.contentOffset.x, 0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
        //2. 获得这个区域的item
        NSArray *visibleItemArray = [super layoutAttributesForElementsInRect:visibleRect];
        
        //3. 遍历,让靠近中心线的item方法,离开的缩小
        for (UICollectionViewLayoutAttributes *attributes in visibleItemArray)
        {
            //1. 获取每个item距离可见区域左侧边框的距离 有正负
            CGFloat leftMargin = attributes.center.x - self.collectionView.contentOffset.x;
            //2. 获取边框距离屏幕中心的距离(固定的)
            CGFloat halfCenterX = self.collectionView.frame.size.width / 2;
            //3. 获取距离中心的的偏移量,需要绝对值
            CGFloat absOffset = fabs(halfCenterX - leftMargin);
            //4. 获取的实际的缩放比例 距离中心越多,这个值就越小,也就是item的scale越小 中心是方法最大的
            CGFloat scale = 1 - absOffset / halfCenterX;
            //5. 缩放
            attributes.transform3D = CATransform3DMakeScale(1 + scale * MKJMinZoomScale, 1 + scale * MKJMinZoomScale, 1);
            
            
            // 是否需要透明
            if (self.needAlpha)
            {
                if (scale < 0.6)
                {
                    attributes.alpha = 0.6;
                }
                else if (scale > 0.99)
                {
                    attributes.alpha = 1.0;
                }
                else
                {
                    attributes.alpha = scale;
                }
            }
        }
        NSArray *attributesArr = [[NSArray alloc] initWithArray:visibleItemArray copyItems:YES];
        return attributesArr;
    }

    第二个效果:如何让滚动的的item根据中心距离,自动居中对齐,避免滚动到哪停到哪

    这里做了第一个和最后一个的判断,不然在头和尾会出现对不齐的情况

    // 重载第四个属性,item自动中心对齐
    // 该方法可写可不写,主要是让滚动的item根据距离中心的值,确定哪个必须展示在中心,不会像普通的那样滚动到哪里就停到哪里
    - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
    {
        // ProposeContentOffset是本来应该停下的位子
        // 1. 先给一个字段存储最小的偏移量 那么默认就是无限大
        CGFloat minOffset = CGFLOAT_MAX;
        // 2. 获取到可见区域的centerX
        CGFloat horizontalCenter = proposedContentOffset.x + self.collectionView.bounds.size.width / 2;
        // 3. 拿到可见区域的rect
        CGRect visibleRec = CGRectMake(proposedContentOffset.x, 0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
        // 4. 获取到所有可见区域内的item数组
        NSArray *visibleAttributes = [super layoutAttributesForElementsInRect:visibleRec];
        
        // 遍历数组,找到距离中心最近偏移量是多少
        for (UICollectionViewLayoutAttributes *atts in visibleAttributes)
        {
            // 可见区域内每个item对应的中心X坐标
            CGFloat itemCenterX = atts.center.x;
            // 比较是否有更小的,有的话赋值给minOffset
            if (fabs(itemCenterX - horizontalCenter) <= fabs(minOffset)) {
                minOffset = itemCenterX - horizontalCenter;
            }
            
        }
        // 这里需要注意的是  上面获取到的minOffset有可能是负数,那么代表左边的item还没到中心,如果确定这种情况下左边的item是距离最近的,那么需要左边的item居中,意思就是collectionView的偏移量需要比原本更小才是,例如原先是1000的偏移,但是需要展示前一个item,所以需要1000减去某个偏移量,因此不需要更改偏移的正负
        
        // 但是当propose小于0的时候或者大于contentSize(除掉左侧和右侧偏移以及单个cell宽度)  、
        // 防止当第一个或者最后一个的时候不会有居中(偏移量超过了本身的宽度),直接卡在推荐的停留位置
        CGFloat centerOffsetX = proposedContentOffset.x + minOffset;
        if (centerOffsetX < 0) {
            centerOffsetX = 0;
        }
        
        if (centerOffsetX > self.collectionView.contentSize.width -(self.sectionInset.left + self.sectionInset.right + self.itemSize.width)) {
            centerOffsetX = floor(centerOffsetX);
        }
        return CGPointMake(centerOffsetX, proposedContentOffset.y);
    }

    第三个效果:如何让视图滚动的时候(不触发点击事件),自动把滚动到第几个的图片展示出来(delegate)
    CGPoint pInView = [self.collectionView.superviewconvertPoint:self.collectionView.centertoView:self.collectionView];
    同学如果你无法理解上面的坐标转换可以点击以下传送门,帮你快速理解

    点击打开链接我要学习

    // 重载第三个属性
    // 滚动的时候会一直调用
    // 当边界发生变化的时候,是否应该刷新布局。如果YES那么就是边界发生变化的时候,重新计算布局信息  这里的newBounds变化的只有x值的变化,也就是偏移量的变化
    - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
    {
        // 把collectionView的父视图(underBackView)上的中心坐标,转换到整个CollectionView ContentSize中的坐标 根据point来计算对应的indexpath
        CGPoint pInView = [self.collectionView.superview convertPoint:self.collectionView.center toView:self.collectionView];
        
        // 通过坐标获取对应的indexpath
        NSIndexPath *indexPathNow = [self.collectionView indexPathForItemAtPoint:pInView];
        
        if (indexPathNow.row == 0)
        {
            if (newBounds.origin.x < SCREEN_WIDTH / 2)
            {
                if (_index != indexPathNow.row)
                {
                    _index = 0;
                    if (self.delegate && [self.delegate respondsToSelector:@selector(collectioViewScrollToIndex:)])
                    {
                        [self.delegate collectioViewScrollToIndex:_index];
                    }
                    
                }
            }
        }
        else
        {
            if (_index != indexPathNow.row)
            {
                _index = indexPathNow.row;
                if (self.delegate && [self.delegate respondsToSelector:@selector(collectioViewScrollToIndex:)])
                {
                    [self.delegate collectioViewScrollToIndex:_index];
                }
            }
        }
        [super shouldInvalidateLayoutForBoundsChange:newBounds];
        return YES;
    }

    第四个效果:点击Item,自动中心对齐 

    // 点击item的时候
    - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
    {
        // 这里的坐标相互转换 可看我写的另一个小博客
        CGPoint pInUnderView = [self.underBackView convertPoint:collectionView.center toView:collectionView];
        
        // 获取中间的indexpath
        NSIndexPath *indexpathNew = [collectionView indexPathForItemAtPoint:pInUnderView];
        
        if (indexPath.row == indexpathNew.row)
        {
            NSLog(@"点击了同一个");
            return;
        }
        else
        {
            // 点击不同就把点击的滚动到中心居中
            [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
        }
    }
    主要的一些方法和实现思路都已经介绍完了,小白写的Demo,大神勿喷,理解的同学可以自己试试



    Demo地址:点击打开链接

    Demo写的很仓促,有些细节还没做的很好,只是玩英雄联盟选择皮肤的时候突然想到要写一个试试,各位有什么问题可以留言,我会慢慢更正能阅读完博主的文章也是不容易,谢谢捧场,好人一生平安,博主会不断进步的,谢谢


     


    展开全文
  • 压缩包分为左右轮播和上下轮播两个工程,集成简单,实现逻辑清楚,可以学习思想并尝试自己实现
  •  前段时间写下了之前那篇 3D图片轮播效果,后来发现了 Pedro Botelho 写的 jquery.gallery.js ,于是重新修改了自己的这个图片轮播,使之可以成为一个插件来使用。 基于jquery.gallery.js 添加了 自适应图片...

    前言:

        前段时间写下了之前那篇 3D图片轮播效果,后来发现了 Pedro Botelho 写的 jquery.gallery.js ,于是重新修改了自己的这个图片轮播,使之可以成为一个插件来使用。

    基于jquery.gallery.js 添加了 自适应图片数量,以及添加了 swipe-indicators 切换按钮

     

    源代码:here

    demo: here

    具体使用:

     html结构:

        <div id="swipe">
            <div class="swipe-wrapper">
                <div class="swipe-list">
                    <a href="#">
                        <img src="https://y.gtimg.cn/music/common/upload/t_focus_info_iphone/67011.jpg" alt="" class="swipe_list_pic">
                    </a> 
                </div>
                <ol class='swipe-indicator'>
                    <li data-index="0"></li>
                    <li data-index="1"></li>
                   <!-- .... -->
                </ol>
                <nav class="swipe-action">
                    <a href="#" class="prev"><span>&laquo;</span></a>
                    <a href="#" class='next'><span>&raquo;</span></a>
                </nav>
            </div>
        </div>

    通过javascript使用

    $("#swipe").Swipe()

    选项 Options:

    Name Type Default
    interval number   3000
    autoplay boolean false
    current number 0

    初始化方法 :Swipe(options)

    使用可选选项初始化轮播,然后开始循环播放项目。
    
    $('#swipe').Swipe({
      interval: 2000,
    autoplay:true
    })

    原理分析:

      Swipe插件的几个主要私有方法:

      _setItems():用于更新图片位置,五个主要元素,按照先后顺序分别为:this.$prevItem,this.$leftItem.this.$currentItem,this.$rightItem.this.$nextItem,顾名思义,大家很容易懂得

        // 更新图片位置
        _setItems: function () {
          this.$items.removeClass('current');
    
          this.$currentItem = this.$items.eq(this.current)
            .addClass('current');
          this.$leftItem = (this.current === 0) ? this.$items.eq(this.itemsCount -
            1) : this.$items.eq(this.current - 1);
    
          this.$rightItem = (this.current === this.itemsCount - 1) ? this
            .$items
            .eq(0) : this.$items.eq(this.current + 1);
    
          //next & previous items
          if (this.itemsCount > 3) {
            // next item
            this.$nextItem = (this.$rightItem.index() === this.itemsCount -
                1) ?
              this.$items.eq(0) : this.$rightItem.next();
    
            // previous item
            this.$prevItem = (this.$leftItem.index() === 0) ? this.$items
              .eq(
                this.itemsCount - 1) : this.$leftItem.prev();
          }
        },

    根据this.current找到这五个元素,其他元素通过 opacity:0; 进行隐藏。

     _layout():定义样式 

        _layout: function () {
          // current, left and right items
          this._setItems();
    
          // current item is not changed
          // left and right one are rotated and translated
          this.$leftItem.css(this._getCoordinates('left'));
          this.$rightItem.css(this._getCoordinates('right'));
          this.$currentItem.css(this._getCoordinates('center'));
    
          this.$nextItem.css(this._getCoordinates('outright'));
          this.$prevItem
            .css(this._getCoordinates('outleft'));
    
          // 定义indicators样式,当前索引 高亮背景
          this.$indicators.eq(this.current)
            .css('background', 'rgb(244, 67, 54)')
            .siblings()
            .css("background", "rgba(0, 0, 0, 0.2)");
        },

     _getCoordinates(position):接受一个position参数,获取位置值,返回_layout()所需要的样式;【可通过修改此处的样式,自定义自己所需要的轮播效果】

      具体看源代码

    _loadEvent():初始化绑定各种事件

    _slide(dir):接受一个滑动方向,用于图片滑动。根据滑动方向,调整this.current的索引,然后调用this._layout()进行图片位置更新和样式变化

      _slide: function (dir) {
          if (this.isAnim)
            return false;
          this.isAnim = true;
          this.$items.addClass("swipe-transition");
          switch (dir) {
            case 'next':
              this.current = this.$rightItem.index();
              this._layout();
              break;
            case 'prev':
              this.current = this.$leftItem.index();
              this._layout();
              break;
          };
        }

    _switchItems():主要是用于indicators的切换图片

    _cycle(): 定义一个定时器,用于图片循环

    _cycle: function () {
      var _self = this;
    
      this.$cycle = setTimeout(function () {
      _self._slide('next');
      if (_self.options.autoplay) {
          _self._cycle();
       }
     }, this.options.interval);
    }

     

    利用$.fn实现Swipe方法,看Jquery源码便可知:$.fn=$.prototype

    $.fn.Swipe = function (options) {
        if (options === undefined) options = {};
        if (typeof options === 'object') {
          this.each(function () {
            // jQuery.data( element, key, value )
            var instance = $.data(this, 'Swipe');
            if (!instance) {
              $.data(this, 'Swipe', new $.Swipe(options, this));
            }
          });
        } else {
          this.each(function () {
            var instance = $.data(this, 'Swipe');
            if (instance) {
              switch (options) {
                case 'cycle':
                  instance._cycle();
                  instance.options.autoplay = true;
                  break;
                case 'stop':
                  instance._stopCycle();
                  instance.options.autoplay = false;
                  break;
                case 'next':
                  instance._slide('next');
                  break;
                case 'prev':
                  instance._slide('prev');
                  break;
                default:
                  logError("no such method '" + options +
                    "' for Swipe instance");
                  break;
              }
            } else {
              logError(
                "cannot call methods on Swipe prior to initialization; " +
                "attempted to call method '" + options + "'");
              return;
            }
          });
        }
        return this;
      };

    此处给jQuery对象添加了一个Swipe()方法,接受一个可选选项,通过  $("#id").Swipe();  可声明一个Swipe轮播对象,当Swipe对象初始化成功后,即可通过传入 string类型,调用API

    // 可选方法
    $("#id").Swipe('cycle')
    循环通过旋转木马项目从左到右。
    
    $("#id").Swipe('stop')
    停止旋转木马循环播放项目。
    
    $("#id").Swipe('prev')
    循环到上一个项目。
    
    $("#id").Swipe('next')
    循环到下一个项目。

     

     结束语

          刚才在爱脚本网,发现了自己的这篇博文,因此重新附下此句版权声明

      版权声明:本文为博主原创文章,未经博主允许不得转载。

    展开全文
  • 一:关于图片轮播器 以前都是自己写图片轮播器,可以使UIiscrollerView也可以使用UIcollectionView,但是不管是使用UIiscrollerView还是使用UIcollectionView实现过程都略显繁琐,今天给大家介绍一个简单好用的第...
  • Swift 写一个3D轮播图,支持循环滚动和点击
  • iOS--轮播视图

    2016-06-04 17:22:24
    原文  ... ... ...平时APP中的广告位或者滚动的新闻图片等用到的就是图片轮播这种效果,实现方式主要有两种,一种是ScrollView+ImageView,另一种则是通过CollectionView,今天总结的
  • 好久没有写博客了,前段时间换了家公司,做了两个多星期,完成了一款app的开发, 公司没有app的需求了,我们几个移动端Android和IOS都转岗开发微信小程序,由于刚接触小程序不到两周,技术还是基于复制粘贴,h5以前...
  • 思路:使用Section来实现,每一组的数据都一样,默认设置N组(正常情况下50-100即可,除非有人很无聊),然后添加定时器,使CollectionView滚动到...https://github.com/shijinliang/KSDemo/tree/CollectionView轮播图
  • ios ScrollerView之图片轮播器 关于如何用scrollView来建立一个图片轮播的功能 #import "JYHCarouselController.h"@interface JYHCarouselController () @property (weak, nonatomic) IBOutlet UIScrollVi
  • iOS开发,3D旋转效果
  • 整天逛淘宝,偶尔有一天看到其中的展示页有个看起来很炫的效果,闲来无事就试着写一个出来,先来看效果:简单记一下思路,这里我选择使用UICollectionView控件,先根据其复用和滚动的特性做出无限轮播的效果,关键...
  • 代码地址:l轮播图代码地址 实现功能: 定时切换(3s) 鼠标放上去停止切换 点击左右图片进行图片切换 鼠标放上去显示向左向右按钮,鼠标移下来再次隐藏 点击向左向右按钮进行相应切换 鼠标放到下方的线上,...
  • 一:关于图片轮播器以前都是自己写图片轮播器,可以使UIiscrollerView也可以使用UIcollectionView,但是不管是使用UIiscrollerView还是使用UIcollectionView实现过程都略显繁琐,今天给大家介绍一个简单好用的第三方...
1 2 3 4 5 ... 20
收藏数 8,036
精华内容 3,214