人人可实现的滑动菜单一

突然感觉,上班也跟上学的时候很像,有业务精的,也有懒散的,有聪明的,有踏实肯干的。不同的也许是,不再靠一张试卷决定成绩,而是夹杂了很多人情在里面。作为程序员,写代码就像写作业,多思考,多练习,才能有所助益。对于我这种资质愚笨的“学生”而言,就更应该勤加练习。最近写了一个简单的滑动切换菜单的小组件,目前只支持最简单的平分屏幕,底部下划线的样式。这里记录一下实现思路以及思考过程,一是留作日后翻看,二是拿出来大家讨论,一起进步。
github地址:
https://github.com/weiman152/ScrollMenusView

先看实现效果:

QQ20181210-111055.gif
一、实现思路

我这里希望这个组件作为一个View进行使用,内部使用的也都是View实现的。

  1. 结构
    我把这个组件分成了三部分,第一部分是顶部的按钮部分。第二部分是中间的滑动小细线。第三部分是下面的内容部分。


    image.png

    menus: 顶部按钮
    line:中间细线
    collectionView:底部内容部分

为什么这样分?
我是根据这个组件各自不同的功能进行封装拆分的。
顶部按钮负责切换菜单,它是独立的部分,至于内容如何排列,如何实现切换,是顶部按钮自己的事,可以封装在一个View中。中间的小横线只负责滑动,滑动到哪里它自己不知道,需外界告知,但是自己的大小颜色需要自己管理。底部的内容负责响应左右扫的手势,告知外界自己滑动了多少。

  1. 顶部按钮
    我的顶部实现就是用了View + button。传进来按钮的名字以及图片,然后使用for循环创建按钮,依次排列。按钮的大小是根据屏幕变化,平分整个屏幕宽度。
    部分代码:


    image.png

3.中间细线
整个细线就是一个View,设置了颜色和大小,没有什么特殊操作,并没有单独拿出来。

4.底部内容
底部内容考虑到要滑动,就使用了UICollectionView。每一个cell对应一个顶部的菜单。

5.如何同步

这个问题应该是实现这个组件的重点了吧。
首先是按钮与细线和内容的同步。
在创建按钮的时候,记录按钮的index,我这里使用的是button的属性tag。点击按钮的时候,如果点击的按钮tag与当前正在显示的按钮不是同一个,则发出按钮切换的信号。使用代理,告知上层View,按钮切换了。


image.png

上层View收到这个消息之后,把细线和内容切换到对应的位置。当然了,底部collectionView是通过contentOffset进行滑动的。


image.png

其次是底部滑动内容与顶部的按钮和细线的同步。
底部的滑动是通过监听collectionView的滑动代理,确定滑动的位置,再通过位置计算出index,把index通过代理传给上层,最后实现同步。


image.png

这里解释下,为什么使用scrollViewDidEndDecelerating而不是scrollViewDidScroll。因为scrollViewDidEndDecelerating是滑动停止的时候调用的,而scrollViewDidScroll只要滑动都会调用,不停地计算会消耗性能,我们关心的也只是滑动最后停止的位置而不是过程,所以我这里选择使用scrollViewDidEndDecelerating来确定最后停止的index。上层View收到消息后,设置顶部按钮以及细线的位置。


image.png
二、难点

说是难点,其实算不上难点,只是计算稍微花点时间,用到的知识也只是小学的等比运算而已。

1.细线的滑动

这里的细线是根据底部内容滑动的多少来同步滑动的。细线的滑动其实就是细线的X在不断地变化。向右滑动,X变大;向左滑动,X变小。
示意图(凑合看吧)


111.jpg

所以,我们就可以这么计算了:

image.png
image.png
2.暴露给外部的接口设计

这个组件的作用就是实现菜单的滑动,菜单内容和底部内容部分是用户自己要写的部分。我希望这个组件像tableview那样使用,当然了,自己写的与tableView差了一个银河系呢。

image.png

初始化的时候,传入顶部菜单的内容。支持纯文字和文字+图片的样式。


image.png
三、缺点

设计上也许不够完善,期待高人指点。

四、使用:
private func setup() {
        
        let menu1 = MenuModel(title: "购买记录",
                              imageNormal: nil,
                              imageSelected: nil)
        let menu2 = MenuModel(title: "680人",
                              imageNormal: #imageLiteral(resourceName: "personNumber_normal"),
                              imageSelected: #imageLiteral(resourceName: "personNumber_selected"))
        let menu3 = MenuModel(title: "商品详情",
                              imageNormal: nil,
                              imageSelected: nil)
        menus = [menu1, menu2, menu3]
        let menu = ScrollMenus(titles: menus,
                               frame: menuView.bounds,
                               menuHeight: 44)
        menu.dataSource = self
        menu.delegate = self
        menu.set(selected: 1)
        menu.lineColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
        menu.textColor = #colorLiteral(red: 0.6000000238, green: 0.6000000238, blue: 0.6000000238, alpha: 1)
        menu.textSeletedColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
        menuView.addSubview(menu)
        scrollMenu = menu
    }

extension ViewController: ScrollMenusDataSource {
    
    func menuViewNumberOfItems() -> Int {
        return menus.count
    }
    
    func menuViewViewForItems(atIndex: Int) -> UIView {
        guard atIndex < menus.count, atIndex < childs.count else {
            return UIView()
        }
        return childs[atIndex].view
    }
}

extension ViewController: ScrollMenusDelegate {
    
    func menuDidChange(currentIndex: Int) {
        print("------------  \(currentIndex)")
        
    }
}
五、集成
  1. 直接导入项目中,导入方法就不多啰嗦了。


    image.png

2.使用cocoa pods继承

platform :ios, '9.0' inhibit_all_warnings!

target '你的项目名字' do use_frameworks!

pod 'ScrollMenusView'

end

最后,希望自己继续静心学习,希望大家天天开心,更期待给出批评和建议。

推荐阅读更多精彩内容