iOS 开源一个高度可定制支持各种动画效果,支持单击双击,小红点,支持自定义不规则按钮的tabbar

本文主要是介绍iOS 开源一个高度可定制支持各种动画效果,支持单击双击,小红点,支持自定义不规则按钮的tabbar,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

TYTabbarAnimationDemo

业务需求导致需要做一个tabbar,里面的按钮点击带有动画效果,tabbar中间的按钮凸出,凸出部分可以点击,支持badge 小红点等,为此封装了一个高度可定制的tabbar -> TYTabBar demo下载地址:https://github.com/qqcc1388/TYTabbarAnimationDemo

TYTabBar可以快速实现以下功能

  1. 每个Item都有单击,双击事件回调
  2. tem可以支持多种动画(帧动画,缩放动画,旋转动画),每个Item都可以单独设置
  3. 支持badgeText,支持小红点功能
  4. 只需要配置一下,就可以实现不规则按钮效果,并且超出边界仍然有点击效果
  5. 支持横竖屏切换

效果图(由于图片资源的问题导致动画切换比较生硬,更改为满足尺寸的资源就好啦)


思路回顾(TYTabBar)

系统的Tabbar功能不算完善,有时候没法完全满足需求,我们这里通过kvc的方式把系统的tabbar替换成我们自己定义的tabbar。
    [self setValue:tabbar forKeyPath:@"tabBar"];
自定义一个TYTabBar继承UITabBar这样就可以继承很多系统TabBar既有很多属性和功能
@interface TYTabBar : UITabBar
初始化配置信息(很重要关系到Item个数,PlusItem的位置)
#define barItemCount                5                               //tabbarItem 个数
#define barItemPlusButtonIndex      2                              //➕按钮的位置  -1表示不存在+按钮  从0开始
#define barItemFontSize             12                              //字体大小
#define barItemNomalTextColor       [UIColor grayColor]             //字体默认颜色
#define barItemSelectedTextColor    [UIColor redColor]              //选中字体颜色
#define barItemSubMargin            3                               //文字和图片的间距
初始化需要TYTabBar中需要展示的按钮并保存起来,并添加点击事件 传入默认第几个tabbarItem默认被选中
-(void)loadItemsWithData:(NSArray<TYBarItemModel *> *)itemModels defaultSelect:(NSInteger)index{
//初始化
NSMutableArray *mutalArr = [NSMutableArray array];
TYAnimationButton *tempItem = nil;
for (int i = 0; i < barItemCount; i++) {
//取出model
TYBarItemModel *model = [itemModels objectAtIndex:i];
TYAnimationButton *button = [[TYAnimationButton alloc] init];
button.images = model.images;
[button setImage:[UIImage imageNamed:model.normalImage] forState:UIControlStateNormal];
[button setImage:[UIImage imageNamed:model.selectedImage] forState:UIControlStateSelected];
[button setTitle:model.title forState:UIControlStateNormal];
button.tag = i;
button.titleLabel.font = [UIFont systemFontOfSize:barItemFontSize];
[button setTitleColor:barItemSelectedTextColor forState:UIControlStateSelected];
[button setTitleColor:barItemNomalTextColor forState:UIControlStateNormal];
[button addTarget:self action:@selector(itemClick:) forControlEvents:UIControlEventTouchDown];
button.layoutType = LXButtonLayoutTypeImageTop;
button.subMargin = barItemSubMargin;
button.animationType = model.animationType;
[self addSubview:button];
//第index按钮默认选中
if (i == index) {
tempItem = button;
}
//保存按钮
[mutalArr addObject:button];
}
self.myItems = mutalArr;
//设置默认选中第index个
if (tempItem) {
[self itemClick:tempItem];
}else{
//如果传入的index无效 则默认选中第0个
TYAnimationButton *item = self.myItems.firstObject;
[self itemClick:item];
}
}
在TabBar的layoutSubviews方法中找到对应的系统的TabBarItem并隐藏起来,创建我们自己的Button并占用系统TabBarItem的位置
-(void)layoutSubviews{
[super layoutSubviews];
Class class = NSClassFromString(@"UITabBarButton");
int btnIndex = 0;
//假设这里有5个item
CGFloat width = self.bounds.size.width/barItemCount*1.0;
CGFloat height = self.bounds.size.height;
for (UIView *btn in self.subviews) {//遍历tabbar的子控件
if ([btn isKindOfClass:class]) {
//先隐藏系统的tabbarItem
btn.hidden = YES;
//取出前面保存的按钮
UIButton *item = [self.myItems objectAtIndex:btnIndex];
//设置item的位置
item.frame = CGRectMake(width*btnIndex, 0, width, height);
if (barItemPlusButtonIndex == btnIndex) {  //plusButton位置  具体偏移根据时间情况进行调节
item.frame = CGRectMake(width*btnIndex, -20, width, height+20);
item.subMargin = 10;
}
btnIndex++;
}
}
}
单击 双击事件处理和传递(这里通过代理将信息传递出去realDelegate一定要要实现,涉及到控制器跳转)
-(void)itemClick:(TYAnimationButton *)item{
//双击效果处理
//2次双击之间间隔至少1s
static  NSTimeInterval lastClickTime = 0;
if ([self checkIsDoubleClick:item]) {
NSTimeInterval clickTime = [NSDate timeIntervalSinceReferenceDate];
if (clickTime - lastClickTime > 1) {  //去掉连击的可能性
if (_realDelegate && [_realDelegate respondsToSelector:@selector(tabBar:doubleClick:)]) {
[self.realDelegate tabBar:self doubleClick:item.tag];
}
}
lastClickTime = clickTime;
}
//按钮重复点击没有效果
if (item == _lastItem && self.canRepeatClick) { //可以重复点击
if (!item.isAnimating) { //正在动画什么都不做否则开始动画
[item animationStart];
}
}else if(item != _lastItem){  //不可以重复点击
//先把上次item动画关闭
_lastItem.selected = NO;
[_lastItem animationStop];
//新点击的item动画开启
item.selected = YES;
[item animationStart];
//把按钮的点击状态传到出去 让tabbarController可以切换控制器
if (_realDelegate && [_realDelegate respondsToSelector:@selector(tabBar:clickIndex:)]) {
[self.realDelegate tabBar:self clickIndex:item.tag];
}
_lastItem = item;
}
}
- (BOOL)checkIsDoubleClick:(UIButton *)currentBtn
{
static UIButton *lastBtn = nil;
static NSTimeInterval lastClickTime = 0;
if (lastBtn != currentBtn) {
lastBtn = currentBtn;
lastClickTime = [NSDate timeIntervalSinceReferenceDate];
return NO;
}
NSTimeInterval clickTime = [NSDate timeIntervalSinceReferenceDate];
if (clickTime - lastClickTime > 0.5 ) {
lastClickTime = clickTime;
return NO;
}
lastClickTime = clickTime;
return YES;
}
badgeText 小红点设置(参考JSBadgeView并在源码上做了一点修改) 显示数字 @"2" 不显示内容@""或者nil 显示小红点@"."
-(void)badgeText:(NSString *)text forIndex:(NSInteger)index{
//给指定的badgeText设置角标
if (index < 0 || index >(self.myItems.count - 1)) {
return;
}
TYAnimationButton *item = self.myItems[index];
//设置角标
[item setBadgeText:text];
}

思路回顾(TYAnimationButton)

TYAnimationButton是整个TYTabBar的核心,每一个Item都是一个TYAnimationButton
提供的动画类型
typedef NS_ENUM(NSUInteger, TYBarItemAnimationType) {
TYBarItemAnimationTypeNomal = 0, //系统默认tabar效果
TYBarItemAnimationTypeFrames,    //帧动画(imageView)
TYBarItemAnimationTypeScale,     //缩放动画
TYBarItemAnimationTypeRotate     //旋转动画
};
关于动画提供了2个动画方法 一个开始动画,一个结束动画,如果是帧动画,需要传入动画帧
#pragma mark - 动画
-(void)animationStart{
if (_animationType == TYBarItemAnimationTypeNomal) {  //系统默认不带动画
}else if(_animationType == TYBarItemAnimationTypeFrames){  //帧动画
if (_images) {  //有提供动画图片的才可以动画
if (!self.imageView.isAnimating) {  //没有动画 则开启动画 如果当前正在动画 则什么都不做
[self frameAnimation];
}
}
}else if(_animationType == TYBarItemAnimationTypeScale){  //
[self scaleAnimationRepeatCount:1];
}else if(_animationType == TYBarItemAnimationTypeRotate){
[self rotateAnimation];
}else{
}
}
-(void)animationStop{
if (_animationType == TYBarItemAnimationTypeNomal) {
}else if(_animationType == TYBarItemAnimationTypeFrames){
if (_images) {  //有提供动画图片的才可以动画
if (self.imageView.isAnimating)
{  //正在动画 则开始动画
self.imageView.animationImages = nil;
[self.imageView stopAnimating];
}
}
}else if(_animationType == TYBarItemAnimationTypeScale){
}else if(_animationType == TYBarItemAnimationTypeRotate){
}else{
}
}
-(void)frameAnimation{
//设置动画图片 给button imageView 添加帧动画
UIImageView * imageView = self.imageView;
//设置动画帧
NSMutableArray *mutalImages = [NSMutableArray array];
for (NSString *imageName in self.images) {
[mutalImages addObject:[UIImage imageNamed:imageName]];
}
imageView.animationImages= mutalImages;
//设置动画总时间
imageView.animationDuration = _duration;
//设置重复次数,0表示无限
imageView.animationRepeatCount = 1;
[imageView startAnimating];
}
//缩放动画
- (void)scaleAnimationRepeatCount:(float)repeatCount {
//需要实现的帧动画,这里根据需求自定义
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"transform.scale";
animation.values = @[@1.0,@1.3,@0.9,@1.15,@0.95,@1.02,@1.0];
animation.duration = 1;
animation.repeatCount = repeatCount;
animation.calculationMode = kCAAnimationCubic;
[self.imageView.layer addAnimation:animation forKey:nil];
}
//旋转动画
- (void)rotateAnimation {
// 针对旋转动画,需要将旋转轴向屏幕外侧平移,最大图片宽度的一半
// 否则背景与按钮图片处于同一层次,当按钮图片旋转时,转轴就在背景图上,动画时会有一部分在背景图之下。
// 动画结束后复位
//    CGFloat oldZPosition = self.layer.zPosition;//0
self.layer.zPosition = 65.f / 2;
[UIView animateWithDuration:0.32 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.imageView.layer.transform = CATransform3DMakeRotation(M_PI, 0, 1, 0);
} completion:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.70 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0.2 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.imageView.layer.transform = CATransform3DMakeRotation(2 * M_PI, 0, 1, 0);
} completion:nil];
});
}
给每个TYAnimationButton添加一个badgeView使其具备角标的功能 监听屏幕横竖屏变化并实时修改badgeView的位置
/**
角标x轴方向的偏移 默认15
*/
@property (nonatomic,assign) CGFloat badgeOffsetX;
/**
角标y轴方向的偏移 默认15
*/
@property (nonatomic,assign) CGFloat badgeOffsetY;
/**
角标x轴方向的偏移(横屏状态) 默认15 请根据具体需求微调
*/
@property (nonatomic,assign) CGFloat badgeLandscapeOffsetX;
/**
角标y轴方向的偏移(横屏状态) 默认40 请根据具体需求微调
*/
@property (nonatomic,assign) CGFloat badgeLandscapeOffsetY;
-(void)layoutSubviews{
[super layoutSubviews];
if((self.bounds.size.width !=0 && !self.badgeView) || self.orientation){
//先移除badgeView
[self.badgeView removeFromSuperview];
//重新添加新的badgeView
self.badgeView = [[JSBadgeView alloc] initWithParentView:self alignment:JSBadgeViewAlignmentTopRight];
//设置角标参数
_badgeView.badgeTextFont = [UIFont systemFontOfSize:12];
if (self.orientation == UIDeviceOrientationLandscapeLeft || self.orientation == UIDeviceOrientationLandscapeRight) { //横屏状态
_badgeView.badgePositionAdjustment = CGPointMake(-_badgeLandscapeOffsetX, _badgeLandscapeOffsetY);
}else{  //竖屏状态
_badgeView.badgePositionAdjustment = CGPointMake(-_badgeOffsetX, _badgeOffsetY);
}
_badgeView.badgeStrokeWidth = 0.2;
_badgeView.badgeText  = _badgeText;
//清除旋转状态
self.orientation = UIDeviceOrientationUnknown;
}
}

关于使用

初始化tabbar
-(void)setupTabbar{
TYTabBar *tabbar = [[TYTabBar alloc] init];
self.tyTabbar = tabbar;
self.tyTabbar.realDelegate = self;
//给tabbar设置内容
//准备数据
NSArray *arr = @[@"竞猜", @"赛事", @"发现", @"优惠", @"我的"];                //title
NSArray *animationImageArr = @[self.quizAnimationImages, self.matchAnimationImages, @[], self.discountAnimationImages, self.mineAnimationImages];
NSArray *barImages = @[@[@"quiz_normal", @"quiz_selected"],                //按钮默认图片和选中后图片
@[@"match_normal", @"match_selected"],
@[@"discovery_normal", @"discovery_selected"],
@[@"discount_normal", @"discount_selected"],
@[@"mine_normal", @"mine_selected"],
];
NSArray *anmations = @[@(TYBarItemAnimationTypeFrames),@(TYBarItemAnimationTypeFrames),@(TYBarItemAnimationTypeRotate),@(TYBarItemAnimationTypeFrames),@(TYBarItemAnimationTypeFrames)];
NSMutableArray *datas = [NSMutableArray array];
for (int i = 0;  i < 5; i++) {
//0. 根据需求选择填写对应的内容
//1. 每个按钮都可以单独设置图片 文字 动画效果(暂时只有4种效果)等
//2. 如果点击后不需要帧动画 images可以传nil
//3. 如果所有按钮都是统一动画,AnimationType 默认传一个值就可以了
//4. title为BarItem显示的文字
//5. nomalImage selectdImage 为BarItem选中和非选中的图片 选中和非选中按钮颜色在TYTabBar中配置
TYBarItemModel *model = [[TYBarItemModel alloc] initWithTitle:arr[i] images:animationImageArr[i] normalImage:barImages[i][0] selectedImage:barImages[i][1] AnimationType:[anmations[i] integerValue]];
[datas addObject:model];
}
//初始化tabbar数据
[self.tyTabbar loadItemsWithData:datas defaultSelect:2];
//替换系统的tabbar
[self setValue:tabbar forKeyPath:@"tabBar"];
//如果希望TabbarItem重复点击都有动画效果 则需要设置canRepeatClick= YES 默认为NO
self.tyTabbar.canRepeatClick = YES;
//设置角标
[self.tyTabbar badgeText:@"9" forIndex:0];
[self.tyTabbar badgeText:@"." forIndex:3];
}
初始化控制器
- (void) loadViewControllers {
//这里的title是导航栏的标题
ViewController* homepageVC = [[ViewController alloc] init];
[self setUpOneChildVcWithVc:homepageVC  title:@"竞猜"];
ViewController * classifyVC = [[ViewController alloc] init];
[self setUpOneChildVcWithVc:classifyVC  title:@"赛事"];
ViewController* shoppingCartVC = [[ViewController alloc] init];
[self setUpOneChildVcWithVc:shoppingCartVC  title:@"发现"];
ViewController * searchVC = [[ViewController alloc] init];
[self setUpOneChildVcWithVc:searchVC  title:@"优惠"];
ViewController* personalCenterVC = [[ViewController alloc] init];
[self setUpOneChildVcWithVc:personalCenterVC  title:@"我的"];
}
- (void)setUpOneChildVcWithVc:(UIViewController *)Vc title:(NSString *)title {
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:Vc];
Vc.navigationItem.title = title;
[self addChildViewController:nav];
}
tabbar代理方法使用
-(void)tabBar:(TYTabBar *)tabbar clickIndex:(NSInteger)index{
[self setSelectedIndex:index];
}
-(void)tabBar:(TYTabBar *)tabBar doubleClick:(NSInteger)index{
NSLog(@"第%zi个Item被双击",index);
//根据选中的Inex,拿到对应的控制器然后让控制器刷新数据
UINavigationController *naviVC = (UINavigationController *)self.selectedViewController;
ViewController *dpVc = (ViewController *)naviVC.viewControllers.firstObject;
[dpVc reloadColor];
}

更多详细使用参考demo中代码

版权归tinych,qqcc1388所有,转载请标注转载来源:http://www.cnblogs.com/tinych/p/7421633.html

参考来源:

JSBadgeView https://github.com/JaviSoto/JSBadgeView
CYLTabBarController https://github.com/ChenYilong/CYLTabBarController

这篇关于iOS 开源一个高度可定制支持各种动画效果,支持单击双击,小红点,支持自定义不规则按钮的tabbar的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/227377

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

阿里开源语音识别SenseVoiceWindows环境部署

SenseVoice介绍 SenseVoice 专注于高精度多语言语音识别、情感辨识和音频事件检测多语言识别: 采用超过 40 万小时数据训练,支持超过 50 种语言,识别效果上优于 Whisper 模型。富文本识别:具备优秀的情感识别,能够在测试数据上达到和超过目前最佳情感识别模型的效果。支持声音事件检测能力,支持音乐、掌声、笑声、哭声、咳嗽、喷嚏等多种常见人机交互事件进行检测。高效推

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

金融业开源技术 术语

金融业开源技术  术语 1  范围 本文件界定了金融业开源技术的常用术语。 本文件适用于金融业中涉及开源技术的相关标准及规范性文件制定和信息沟通等活动。

安全管理体系化的智慧油站开源了。

AI视频监控平台简介 AI视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。用户只需在界面上进行简单的操作,就可以实现全视频的接入及布控。摄像头管理模块用于多种终端设备、智能设备的接入及管理。平台支持包括摄像头等终端感知设备接入,为整个平台提

防近视护眼台灯什么牌子好?五款防近视效果好的护眼台灯推荐

在家里,灯具是属于离不开的家具,每个大大小小的地方都需要的照亮,所以一盏好灯是必不可少的,每个发挥着作用。而护眼台灯就起了一个保护眼睛,预防近视的作用。可以保护我们在学习,阅读的时候提供一个合适的光线环境,保护我们的眼睛。防近视护眼台灯什么牌子好?那我们怎么选择一个优秀的护眼台灯也是很重要,才能起到最大的护眼效果。下面五款防近视效果好的护眼台灯推荐: 一:六个推荐防近视效果好的护眼台灯的

K8S(Kubernetes)开源的容器编排平台安装步骤详解

K8S(Kubernetes)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。以下是K8S容器编排平台的安装步骤、使用方式及特点的概述: 安装步骤: 安装Docker:K8S需要基于Docker来运行容器化应用程序。首先要在所有节点上安装Docker引擎。 安装Kubernetes Master:在集群中选择一台主机作为Master节点,安装K8S的控制平面组件,如AP

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。