亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > iOS > 正文

你知道Tab Bar圖標原來還可以這樣玩嗎

2020-07-26 02:20:47
字體:
來源:轉載
供稿:網友

示例代碼下載 (本地下載)

背景

框架自帶的 Tab Bar 相信大家已經熟悉得不能再熟悉了,一般使用的時候不過是設置兩個圖標代表選中和未選中兩種狀態,難免有一些平淡。后來很多控件就在標簽選中時進行一些比較抓眼球的動畫,不過我覺得大部分都是為了動畫而動畫。直到后來我看到Outlook客戶端的動畫時,我才意識到原來還可以跟用戶的交互結合在一起。

圖1 標簽圖標跟隨手勢進行不同的動畫

有意思吧,不過本文并不是要仿制個一模一樣的出來,會有稍微變化:


圖2 本文完成的最終效果

實現分析

寫代碼之前,咱先討論下實現的方法,相信你已經猜到標簽頁的圖標顯然已經不是圖片,而是一個自定義的UIView。將一個視圖掛載到原本圖標的位置并不是一件難事,稍微有些復雜的是數字滾輪效果的實現,別看它數字不停地在滾動,仔細看其實最多顯示2種數字,也就說只要2個Label就夠了。

基于篇幅,文章不會涉及右側的時鐘效果,感興趣請直接參考源碼。

數字滾輪

打開項目TabBarInteraction,新建文件WheelView.swift,它是UIView的子類。首先設置好初始化函數:

class WheelView: UIView { required init?(coder aDecoder: NSCoder) {  super.init(coder: aDecoder)  setupView() } override init(frame: CGRect) {  super.init(frame: frame)  setupView() }}

接著創建兩個Label實例,代表滾輪中的上下兩個Label:

private lazy var toplabel: UILabel = { return createDefaultLabel()}()private lazy var bottomLabel: UILabel = { return createDefaultLabel()}()private func createDefaultLabel() -> UILabel { let label = UILabel()  label.textAlignment = NSTextAlignment.center label.adjustsFontSizeToFitWidth = true label.translatesAutoresizingMaskIntoConstraints = false return label}

現在來完成setupView()方法,在這方法中將上述兩個Label添加到視圖中,然后設置約束將它們的四邊都與layoutMarginsGuide對齊。

private func setupView() { translatesAutoresizingMaskIntoConstraints = false for label in [toplabel, bottomLabel] {  addSubview(label)  NSLayoutConstraint.activate([   label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),   label.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor),   label.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor),   label.rightAnchor.constraint(equalTo: layoutMarginsGuide.rightAnchor)  ]) }}

有人可能會問現在這樣兩個Label不是重疊的狀態嗎?不著急,接下來我們會根據參數動態地調整它們的大小和位置。

添加兩個實例變量progress和contents,分別表示滾動的總體進度和顯示的全部內容。

var progress: Float = 0.0var contents = [String]()

我們接下來要根據這兩個變量計算出當前兩個Label顯示的內容以及它們的縮放位置。這些計算都在progress的didSet里完成:

var progress: Float = 0.0 { didSet {  progress = min(max(progress, 0.0), 1.0)   guard contents.count > 0 else { return }    /** 根據 progress 和 contents 計算出上下兩個 label 顯示的內容以及 label 的壓縮程度和位置   *   * Example:    * progress = 0.4, contents = ["A","B","C","D"]   *   * 1)計算兩個label顯示的內容   * topIndex = 4 * 0.4 = 1.6, topLabel.text = contents[1] = "B"   * bottomIndex = 1.6 + 1 = 2.6, bottomLabel.text = contents[2] = "C"    *    * 2) 計算兩個label如何壓縮和位置調整,這是實現滾輪效果的原理   * indexOffset = 1.6 % 1 = 0.6   * halfHeight = bounds.height / 2   * ┌─────────────┐    ┌─────────────┐            * |┌───────────┐| scaleY |    |          * ||   || 1-0.6=0.4 |    | translationY     * || topLabel || ----------> |┌─ topLabel─┐| ------------------    * ||   ||    |└───────────┘| -halfHeight * 0.6 ⎞ ┌─────────────┐   * |└───────────┘|    |    |     ⎥ |┌─ toplabel─┐|   * └─────────────┘    └─────────────┘     ⎟ |└───────────┘|   *                 ❯ |┌───────────┐|   * ┌─────────────┐    ┌─────────────┐     ⎟ ||bottomLabel||   * |┌───────────┐| scaleY |    |     ⎟ |└───────────┘|   * ||   || 0.6  |┌───────────┐| translationY  ⎠ └─────────────┘   * ||bottomLabel|| ----------> ||bottomLabel|| -----------------    * ||   ||    |└───────────┘| halfHeight * 0.4     * |└───────────┘|    |    |         * └─────────────┘    └─────────────┘         *   * 可以想象出,當 indexOffset 從 0.0 遞增到 0.999 過程中,   * topLabel 從滿視圖越縮越小至0,而 bottomLabel剛好相反越變越大至滿視圖,即形成一次完整的滾動   */  let topIndex = min(max(0.0, Float(contents.count) * progress), Float(contents.count - 1))  let bottomIndex = min(topIndex + 1, Float(contents.count - 1))  let indexOffset = topIndex.truncatingRemainder(dividingBy: 1)    toplabel.text = contents[Int(topIndex)]  toplabel.transform = CGAffineTransform(scaleX: 1.0, y: CGFloat(1 - indexOffset))   .concatenating(CGAffineTransform(translationX: 0, y: -(toplabel.bounds.height / 2) * CGFloat(indexOffset)))     bottomLabel.text = contents[Int(bottomIndex)]  bottomLabel.transform = CGAffineTransform(scaleX: 1.0, y: CGFloat(indexOffset))   .concatenating(CGAffineTransform(translationX: 0, y: (bottomLabel.bounds.height / 2) * (1 - CGFloat(indexOffset)))) }}

最后我們還要向外公開一些樣式進行自定義:

extension WheelView { /// 前景色變化事件 override func tintColorDidChange() {  [toplabel, bottomLabel].forEach { $0.textColor = tintColor }  layer.borderColor = tintColor.cgColor } /// 背景色 override var backgroundColor: UIColor? {  get { return toplabel.backgroundColor }  set { [toplabel, bottomLabel].forEach { $0.backgroundColor = newValue } } } /// 邊框寬度 var borderWidth: CGFloat {  get { return layer.borderWidth }  set {   layoutMargins = UIEdgeInsets(top: newValue, left: newValue, bottom: newValue, right: newValue)   layer.borderWidth = newValue  } } /// 字體 var font: UIFont {  get { return toplabel.font }  set { [toplabel, bottomLabel].forEach { $0.font = newValue } } }}

至此,整個滾輪效果已經完成。

掛載視圖

在FirstViewController中實例化剛才自定義的視圖,設置好字體、邊框、背景色、Contents等內容,別忘了isUserInteractionEnabled設置為false,這樣就不會影響原先的事件響應。

 override func viewDidLoad() {  super.viewDidLoad()  // Do any additional setup after loading the view.    tableView.delegate = self  tableView.dataSource = self  tableView.register(UITableViewCell.self, forCellReuseIdentifier: "DefaultCell")  tableView.rowHeight = 44  wheelView = WheelView(frame: CGRect.zero)  wheelView.font = UIFont.systemFont(ofSize: 15, weight: .bold)  wheelView.borderWidth = 1  wheelView.backgroundColor = UIColor.white  wheelView.contents = data  wheelView.isUserInteractionEnabled = false}

然后要把視圖掛載到原先的圖標上,viewDidLoad()方法底部新增代碼:

 override func viewDidLoad() { ... guard let parentController = self.parent as? UITabBarController else { return } let controllerIndex = parentController.children.firstIndex(of: self)! var tabBarButtons = parentController.tabBar.subviews.filter({  type(of: $0).description().isEqual("UITabBarButton") }) guard !tabBarButtons.isEmpty else { return } let tabBarButton = tabBarButtons[controllerIndex] let swappableImageViews = tabBarButton.subviews.filter({  type(of: $0).description().isEqual("UITabBarSwappableImageView") }) guard !swappableImageViews.isEmpty else { return } let swappableImageView = swappableImageViews.first! tabBarButton.addSubview(wheelView) swappableImageView.isHidden = true NSLayoutConstraint.activate([  wheelView.widthAnchor.constraint(equalToConstant: 25),  wheelView.heightAnchor.constraint(equalToConstant: 25),  wheelView.centerXAnchor.constraint(equalTo: swappableImageView.centerXAnchor),  wheelView.centerYAnchor.constraint(equalTo: swappableImageView.centerYAnchor) ]) }

上述代碼的目的是最終找到對應標簽UITabBarButton內類型為UITabBarSwappableImageView的視圖并替換它??瓷先ハ喈攺碗s,但是它盡可能地避免出現意外情況導致程序異常。只要以后UIkit不更改類型UITabBarButton和UITabBarSwappableImageView,以及他們的包含關系,程序基本不會出現意外,最多導致自定義的視圖掛載不上去而已。另外一個好處是FirstViewController不用去擔心它被添加到TabBarController中的第幾個標簽上。總體來說這個方法并不完美,但目前似乎也沒有更好的方法?

實際上還可以將上面的代碼剝離出來,放到名為TabbarInteractable的protocol的默認實現上。有需要的ViewController只要宣布遵守該協議,然后在viewDidLoad方法中調用一個方法即可實現整個替換過程。

只剩下最后一步了,我們知道UITableView是UIScrollView的子類。在它滾動的時候,FirsViewController作為UITableView的delegate,同樣會收到scrollViewDidScroll方法的調用,所以在這個方法里更新滾動的進度再合適不過了:

// MARK: UITableViewDelegateextension FirstViewController: UITableViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) {  //`progress`怎么計算取決于你需求,這里的是為了把`tableview`當前可見區域最底部的2個數字給顯示出來。  let progress = Float((scrollView.contentOffset.y + tableView.bounds.height - tableView.rowHeight) / scrollView.contentSize.height)  wheelView.progress = progress }}

把項目跑起來看看吧,你會得到文章開頭的效果。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對武林網的支持。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美激情亚洲综合一区| 久久99久久99精品免观看粉嫩| 91av在线免费观看视频| 韩国精品美女www爽爽爽视频| 欧美在线视频在线播放完整版免费观看| 色99之美女主播在线视频| 黄色成人av在线| 日韩美女毛茸茸| 久热精品视频在线观看一区| 久久久久www| 欧美超级免费视 在线| 91久久精品国产| 国产亚洲精品美女久久久| 久久99精品久久久久久青青91| 亚洲网站在线播放| 日韩av免费在线看| 91久久久久久久久久久| 亚洲aⅴ日韩av电影在线观看| 91精品国产高清自在线看超| 亚洲欧洲在线看| 欧洲成人免费aa| 久久综合88中文色鬼| 亚洲片在线资源| 久久精品国产一区二区电影| 亚洲精品国精品久久99热一| 上原亚衣av一区二区三区| 亚洲va久久久噜噜噜久久天堂| 欧美中文在线观看国产| 久久久久久久久久婷婷| 亚洲精品视频网上网址在线观看| 97香蕉超级碰碰久久免费的优势| 国产精品视频xxxx| 欧美成人免费网| 亚洲精品午夜精品| xvideos成人免费中文版| 最近2019中文字幕大全第二页| 欧美在线不卡区| 欧美激情视频网| 欧美国产日韩xxxxx| 欧美综合在线第二页| 人人澡人人澡人人看欧美| 日韩中文在线中文网在线观看| 欧美一区二区三区……| 精品国产乱码久久久久久天美| 亚洲欧美国产一区二区三区| 色综合天天狠天天透天天伊人| 日韩在线视频观看正片免费网站| 久久精品电影网| 在线观看中文字幕亚洲| 久久影视免费观看| www.xxxx精品| 欧美一区二区三区四区在线| 日韩亚洲欧美成人| 国语对白做受69| 日本精品视频在线播放| 欧美激情一级精品国产| 国产精品高潮呻吟久久av无限| 在线精品高清中文字幕| 国产精品爽爽ⅴa在线观看| 97国产suv精品一区二区62| 亚洲性生活视频在线观看| 丁香五六月婷婷久久激情| 亚洲区免费影片| 91精品国产91久久久久久| 中文字幕视频一区二区在线有码| 97**国产露脸精品国产| 欧美视频专区一二在线观看| 日韩中文视频免费在线观看| 狠狠久久亚洲欧美专区| 中文字幕日韩在线观看| 国产一区二区日韩精品欧美精品| 久久夜精品香蕉| 国产精品视频专区| 81精品国产乱码久久久久久| 2024亚洲男人天堂| 欧美日本黄视频| 亚洲综合精品一区二区| 色99之美女主播在线视频| 精品视频在线播放色网色视频| 日韩在线播放一区| 韩曰欧美视频免费观看| 最近中文字幕mv在线一区二区三区四区| 午夜精品国产精品大乳美女| 久久这里有精品| 欧美国产亚洲精品久久久8v| 国产欧美日韩最新| 97在线视频免费播放| 亚洲精品国产综合区久久久久久久| 亚洲影视九九影院在线观看| 国产va免费精品高清在线观看| 亚洲自拍欧美色图| 精品久久久在线观看| 97热在线精品视频在线观看| 精品无人国产偷自产在线| 欧美日韩人人澡狠狠躁视频| 日韩一二三在线视频播| 欧美日韩国产在线| 2021久久精品国产99国产精品| 国产一区在线播放| 久久久久国色av免费观看性色| 欧美亚洲免费电影| 成人h视频在线| 欧美情侣性视频| 久久视频免费在线播放| 欧美视频在线看| 欧美区二区三区| 国模gogo一区二区大胆私拍| **欧美日韩vr在线| 国语自产偷拍精品视频偷| 日本中文字幕久久看| 日韩av电影中文字幕| 亚洲自拍高清视频网站| 日韩中文字幕欧美| 亚洲新声在线观看| 成人免费观看49www在线观看| 欧美性在线视频| 国产69久久精品成人看| 亚洲欧美日韩综合| 在线观看久久av| 欧美午夜精品久久久久久人妖| 91色琪琪电影亚洲精品久久| 亚洲女成人图区| 国产精品日韩在线观看| 国语自产精品视频在线看抢先版图片| 亚洲欧美在线看| 亚洲欧美国产日韩中文字幕| 国产精品久久久久久久午夜| 91精品国产高清久久久久久91| 91久久国产婷婷一区二区| 伊人久久大香线蕉av一区二区| 中文字幕av日韩| 成人精品网站在线观看| 亚洲人成网站777色婷婷| 亚洲成人a**站| 国产成人涩涩涩视频在线观看| 久久久国产在线视频| 欧美美女15p| 久久久久久久久久久国产| 91精品视频一区| 欧美日本中文字幕| 日韩成人xxxx| 91精品视频观看| 久久久久久久电影一区| 成人免费观看49www在线观看| 777777777亚洲妇女| 日韩美女在线观看一区| 深夜福利国产精品| xvideos国产精品| 国产亚洲视频在线观看| 国产主播欧美精品| 欧美激情va永久在线播放| 色诱女教师一区二区三区| 亚洲国产日韩欧美综合久久| 成人免费福利视频| 日韩欧美国产骚| 精品国产乱码久久久久酒店| 欧美激情视频一区二区三区不卡| 亚洲第一视频网站| 亚洲国产免费av| 久久综合久久88| 亚洲精品国产美女| 欧美乱妇40p| 欧美人在线视频| 欧美日韩国产丝袜美女|