iOS开发-下拉刷新动画小球左右交换位置Indicator指示器效果

news2024/12/28 20:36:19

iOS开发-下拉刷新动画小球左右交换位置Indicator指示器效果

之前开发中实现下拉刷新动画小球左右交换位置Indicator指示器效果。

一、效果图

在这里插入图片描述

二、基础动画

CABasicAnimation类的使用方式就是基本的关键帧动画。

所谓关键帧动画,就是将Layer的属性作为KeyPath来注册,指定动画的起始帧和结束帧,然后自动计算和实现中间的过渡动画的一种动画方式。

可以查看

https://blog.csdn.net/gloryFlow/article/details/131991202

三、实现代码

3.1 代码实现动画

主要两个球实现CABasicAnimation动画,KeyPath是position

- (void)showAnimation:(UIView *)view from:(CGPoint)from toPosition:(CGPoint)to {
    CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
    
    CAAnimationGroup *animationGroup;
    animationGroup = [CAAnimationGroup animation];
    animationGroup.removedOnCompletion = NO;
    animationGroup.timingFunction = defaultCurve;
    animationGroup.fillMode = kCAFillModeBoth;
    animationGroup.beginTime = CACurrentMediaTime();
    animationGroup.duration = 0.5;
    animationGroup.repeatCount = MAXFLOAT;
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:from];
    animation.toValue = [NSValue valueWithCGPoint:to]; // 终了帧
    
    CAKeyframeAnimation *scaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"];
    scaleAnimation.values = @[@1.0,@0.5,@1.0];
    scaleAnimation.keyTimes = @[@0.0,@0.5,@1.0];
    
    NSArray *animations = @[animation,scaleAnimation];
    
    animationGroup.animations = animations;
    
    [view.layer addAnimation:animationGroup forKey:@"animationGroup"];
}

完整代码如下

#import "INRefreshBallLoading.h"
#import "UIColor+Addition.h"

static CGFloat kBallSize = 12.0;
static CGFloat kDistance = 15.0;

@interface INRefreshBallLoading ()

@property (nonatomic, strong) UIImageView *aballImageView;
@property (nonatomic, strong) UIImageView *bballImageView;

@end

@implementation INRefreshBallLoading

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self addSubview:self.aballImageView];
        [self addSubview:self.bballImageView];
        [self layoutBallFrame];
    }
    return self;
}

- (void)layoutBallFrame {
    self.aballImageView.center = self.center;
    self.bballImageView.center = self.center;
}

- (void)displayPrecent:(CGFloat)precent {
    if (precent < 0) {
        precent = 0;
    }
    
    if (precent > 1.0) {
        precent = 1.0;
    }
    
    CGPoint centerCenter = self.center;
    CGPoint aBallCenter = self.aballImageView.center;
    CGPoint bBallCenter = self.bballImageView.center;
    
    aBallCenter.x = centerCenter.x - precent*kDistance;
    self.aballImageView.center = aBallCenter;
    self.aballImageView.transform = CGAffineTransformMakeScale(precent, precent);
    
    bBallCenter.x = centerCenter.x + precent*kDistance;
    self.bballImageView.center = bBallCenter;
    self.bballImageView.transform = CGAffineTransformMakeScale(precent, precent);
}

- (void)startAnimation {
    CGPoint centerCenter = self.center;
    CGFloat leftCenter = self.center.x - kDistance;
    CGFloat rightCenter = self.center.x + kDistance;

    [self showAnimation:self.aballImageView from:CGPointMake(leftCenter, centerCenter.y) toPosition:CGPointMake(rightCenter, centerCenter.y)];
    [self showAnimation:self.bballImageView from:CGPointMake(rightCenter, centerCenter.y) toPosition:CGPointMake(leftCenter, centerCenter.y)];
}

- (void)stopAnimation {
    [self.aballImageView.layer removeAnimationForKey:@"animationGroup"];
    [self.bballImageView.layer removeAnimationForKey:@"animationGroup"];
}

- (void)showAnimation:(UIView *)view from:(CGPoint)from toPosition:(CGPoint)to {
    CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
    
    CAAnimationGroup *animationGroup;
    animationGroup = [CAAnimationGroup animation];
    animationGroup.removedOnCompletion = NO;
    animationGroup.timingFunction = defaultCurve;
    animationGroup.fillMode = kCAFillModeBoth;
    animationGroup.beginTime = CACurrentMediaTime();
    animationGroup.duration = 0.5;
    animationGroup.repeatCount = MAXFLOAT;
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:from];
    animation.toValue = [NSValue valueWithCGPoint:to]; // 终了帧
    
    CAKeyframeAnimation *scaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"];
    scaleAnimation.values = @[@1.0,@0.5,@1.0];
    scaleAnimation.keyTimes = @[@0.0,@0.5,@1.0];
    
    NSArray *animations = @[animation,scaleAnimation];
    
    animationGroup.animations = animations;
    
    [view.layer addAnimation:animationGroup forKey:@"animationGroup"];
}

#pragma mark - SETTER/GETTER
- (UIImageView *)aballImageView {
    if (!_aballImageView) {
        _aballImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, kBallSize, kBallSize)];
        _aballImageView.clipsToBounds = YES;
        _aballImageView.userInteractionEnabled = YES;
        _aballImageView.backgroundColor = [UIColor colorWithHexString:@"ff7e48"];
        _aballImageView.layer.cornerRadius = kBallSize/2;
        _aballImageView.layer.masksToBounds = YES;
    }
    return _aballImageView;
}

- (UIImageView *)bballImageView {
    if (!_bballImageView) {
        _bballImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, kBallSize, kBallSize)];
        _bballImageView.clipsToBounds = YES;
        _bballImageView.userInteractionEnabled = YES;
        _bballImageView.backgroundColor = [UIColor colorWithHexString:@"fe4373"];
        _bballImageView.layer.cornerRadius = kBallSize/2;
        _bballImageView.layer.masksToBounds = YES;
    }
    return _bballImageView;
}

@end

3.2 MJRefresh使用该动画

我这里继承MJRefreshStateHeader

需要根据刷新控件的状态来执行开启动画与结束动画操作

刷新控件的状态如下

typedef NS_ENUM(NSInteger, MJRefreshState) {
    // 普通闲置状态
    MJRefreshStateIdle = 1,
    // 松开就可以进行刷新的状态
    MJRefreshStatePulling,
    // 正在刷新中的状态
    MJRefreshStateRefreshing,
    // 即将刷新的状态
    MJRefreshStateWillRefresh,
    // 所有数据加载完毕,没有更多的数据了
    MJRefreshStateNoMoreData
};

INRefreshHeader.h

#import "MJRefresh.h"
#import "INRefreshFourBallLoading.h"

@interface INRefreshHeader : MJRefreshStateHeader

@property (nonatomic, assign) BOOL showInsetTop;

@property (nonatomic, strong) INRefreshBallLoading *ballLoadingView;

@end

INRefreshHeader.m

@implementation INRefreshHeader

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.lastUpdatedTimeLabel.hidden = YES;
        self.stateLabel.hidden = YES;
        
        [self addSubview:self.ballLoadingView];
    }
    return self;
}

- (INRefreshBallLoading *)ballLoadingView {
    if (!_ballLoadingView) {
        _ballLoadingView = [[INRefreshBallLoading alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth([UIScreen mainScreen].bounds), self.bounds.size.height)];
    }
    return _ballLoadingView;
}


- (void)setState:(MJRefreshState)state {
    MJRefreshCheckState
    
    // 根据状态做事情
    if (state == MJRefreshStateIdle) {
        if (oldState == MJRefreshStateRefreshing) {
            self.ballLoadingView.alpha = 1.0;

            // 如果执行完动画发现不是idle状态,就直接返回,进入其他状态
            if (self.state != MJRefreshStateIdle) return;
            
            self.ballLoadingView.alpha = 1.0;

            [self.ballLoadingView stopAnimation];

        } else {
            [self.ballLoadingView stopAnimation];

        }
    } else if (state == MJRefreshStatePulling) {
        [self.ballLoadingView stopAnimation];

    } else if (state == MJRefreshStateRefreshing) {
        self.ballLoadingView.alpha = 1.0;
    }
}

- (void)prepare {
    [super prepare];
    self.mj_h = 60.0;
}

- (void)placeSubviews {
    [super placeSubviews];
    
    CGFloat centerX = self.mj_w * 0.5;
    CGFloat centerY = self.mj_h * 0.5;
    self.ballLoadingView.center = CGPointMake(centerX, centerY);
}

/** 当scrollView的contentOffset发生改变的时候调用 */
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change {
    [super scrollViewContentOffsetDidChange:change];
    NSLog(@"change:%@",change);
    
    CGPoint old = [change[@"old"] CGPointValue];
    CGPoint new = [change[@"new"] CGPointValue];
    
    CGFloat precent = -new.y/self.mj_h;
    [self.ballLoadingView displayIndicator:precent];
}

/** 当scrollView的contentSize发生改变的时候调用 */
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change {
    [super scrollViewContentSizeDidChange:change];
}

/** 当scrollView的拖拽状态发生改变的时候调用 */
- (void)scrollViewPanStateDidChange:(NSDictionary *)change {
    [super scrollViewPanStateDidChange:change];
}

- (void)setShowInsetTop:(BOOL)showInsetTop {
    _showInsetTop = showInsetTop;
    
}

- (void)backInitState {
    
}

@end

3.3 具体的TableView使用

需要设置UITableView的下拉刷新操作:tableView.mj_header = header

- (void)configureRefresh {
    __weak typeof(self) weakSelf = self;
    INRefreshHeader *header = [INRefreshHeader headerWithRefreshingBlock:^{
        [weakSelf refreshData];
    }];
    
    INRefreshFooter *footer = [INRefreshFooter footerWithRefreshingBlock:^{
        [weakSelf loadMoreData];
    }];
    self.editView.tableView.mj_header = header;
    self.editView.tableView.mj_footer = footer;
}

- (void)refreshData {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.editView.tableView.mj_header endRefreshing];
        [self.editView.tableView.mj_footer endRefreshing];
    });
}

- (void)loadMoreData {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.editView.tableView.mj_header endRefreshing];
        [self.editView.tableView.mj_footer endRefreshing];
    });
}

四、小结

iOS开发-下拉刷新动画小球左右交换位置Indicator指示器效果,使用mjrefresh一个好用的上下拉刷新的控件。实现CABasicAnimation基础效果,根据不同的mjrefresh下拉刷新操作来执行动画效果。

学习记录,每天不停进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/806867.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux:shell命令运行原理和权限的概念

文章目录 shell和kernelshell的概念和原理Linux的权限文件的权限文件的类型文件的权限管理权限的实战应用 shell和kernel 从狭义上来讲&#xff0c;Linux是一个操作系统&#xff0c;我们叫它叫kernel&#xff0c;意思是核心&#xff0c;核心的意思顾名思义&#xff0c;就是最关…

【LeetCode】72.(最短)编辑距离(闫氏dp,分析加可视化)

考虑两个数组&#xff1a;a、b 定义dp[ i ][ j ]为&#xff0c;让数组a从1到 i 的字符&#xff0c;与数组b从1到 j 的字符&#xff0c;正好匹配上的最小操作数。 假设现在面前有一个正好匹配的数组a和b&#xff0c;其中a的长度为 i &#xff0c;b的长度为 j &#xff08;两个…

python离散仿真器

文章目录 类图示例 类图 示例

浅谈3D隐式表示(SDF,Occupancy field,NeRF)

本篇文章介绍了符号距离函数Signed Distance Funciton(SDF)&#xff0c;占用场Occupancy Field&#xff0c;神经辐射场Neural Radiance Field&#xff08;NeRF&#xff09;的概念、联系与区别。 显式表示与隐式表示 三维空间的表示形式可以分为显式和隐式。 比较常用的显式表…

基于SpringBoot+Vue的财务管理系统设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

【动态规划part10】| 121.买卖股票的最佳时机、122.买卖股票的最佳时机II

目录 &#x1f388;LeetCode121. 买卖股票的最佳时机 &#x1f388;LeetCode122.买卖股票的最佳时机II &#x1f388;LeetCode121. 买卖股票的最佳时机 链接&#xff1a;121.买卖股票的最佳时机 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定…

图神经网络(GNN)入门学习笔记(直观且简单)

文章目录 图的定义和表示可以使用图数据结构的问题将图结构用于机器学习的挑战最基本的图神经网络概述汇聚操作基于信息传递的改进图神经网络全局向量信息的利用 本篇文章参考发表于Distill上的图神经网络入门博客&#xff1a; A Gentle Introduction to Graph Neural Network…

网络防御之IDS

1. 什么是IDS&#xff1f; IDS是入侵检测系统&#xff0c;一种对于网络传输进行及时监视&#xff0c;在发现可疑的传输时发出警报或者采取主动反应措施的网络安全设备。IDS是一种积极地主动的防御技术。 2. IDS和防火墙有什么不同&#xff1f; 防火墙是一种隔离并过滤非授权用…

CV前沿方向:Visual Prompting 视觉提示工程下的范式

prompt在视觉领域&#xff0c;也越来越重要&#xff0c;在图像生成&#xff0c;作为一种可控条件&#xff0c;增进交互和可控性&#xff0c;在多模态理解方面&#xff0c;指令prompt也使得任务灵活通用。视觉提示工程&#xff0c;已然成为CV一个前沿方向&#xff01; 下面来看看…

Python Numpy入门基础(二)数组操作

入门基础&#xff08;二&#xff09; NumPy是Python中一个重要的数学运算库&#xff0c;它提供了了一组多维数组对象和一组用于操作这些数组的函数。以下是一些NumPy的主要特点&#xff1a; 多维数组对象&#xff1a;NumPy的核心是ndarray对象&#xff0c;它是一个多维数组对…

TCP/IP协议详解(二)

目录内容 TCP协议的可靠性 TCP的三次握手 TCP的四次挥手 C#中&#xff0c;TCP/IP建立 三次握手和四次挥手常见面试题 在上一篇文章中讲解了TCP/IP的由来以及报文格式&#xff0c;详情请见上一篇文章&#xff0c;现在接着来讲讲TCP/IP的可靠性以及通过代码的实现。 在TCP首部的…

Javadoc comment自动生成

光标放在第二行 按下Alt Shift j 下面是Java doc的生成 Next Next-> Finish

java多线程(超详细)

1 - 线程 1.1 - 进程 进程就是正在运行中的程序&#xff08;进程是驻留在内存中的&#xff09; 是系统执行资源分配和调度的独立单位 每一进程都有属于自己的存储空间和系统资源 注意&#xff1a;进程A和进程B的内存独立不共享。 1.2 - 线程 线程就是进程中的单个顺序控制…

【数据预测】基于蜣螂优化算法DBO的VMD-KELM光伏发电功率预测 短期功率预测【Matlab代码#53】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】1. 蜣螂优化算法DBO2. 变分模态分解VMD3. 核极限学习机KELM4. 部分代码展示5. 仿真结果展示6. 资源获取 【可更换其他算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】 1. 蜣螂…

【雕爷学编程】Arduino动手做(175)---机智云ESP8266开发板模块8

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

二、vagrant中安装centos-7

篇章二、vagrant中安装centos-7 前言 使用Vagrant创建镜像时&#xff0c;需要指定一个镜像&#xff0c;也就是box&#xff0c;若不存在Vagrant则会先从网上下载&#xff0c;而后缓存在本地目录下。 Vagrant有一个 镜像网站 &#xff0c;可以根据需要在这个网站中下载所需镜像…

Banana Pi 推出带有 2 个 2.5GbE 端口的迷你路由器开源硬件开发板

Banana Pi 今天推出了一款迷你路由器板&#xff0c;基于 MediaTek MT7986 无线网络片上系统&#xff0c;针对路由器进行了优化。Banana Pi BPI-R3 迷你路由器板还支持无线连接&#xff0c;起价约为 78.95 美元。 产品公告显示&#xff0c;这款新路由器板集成了 MediaTek Filog…

LAL v0.34.3发布,G711音频来了,Web UI也来了

Go语言流媒体开源项目 LAL 今天发布了v0.34.3版本。 LAL 项目地址&#xff1a;https://github.com/q191201771/lal 老规矩&#xff0c;简单介绍一下&#xff1a; ▦ 一. 音频G711 新增了对音频G711A/G711U(也被称为PCMA/PCMU)的支持。主要表现在&#xff1a; ✒ 1) rtmp G71…

《向量数据库指南》——使用Milvus Cloud操作员安装Milvus Cloud独立版

Milvus cloud操作员HelmDocker Compose Milvus cloud Operator是一种解决方案,帮助您在目标Kubernetes(K8s)集群上部署和管理完整的Milvus cloud服务堆栈。该堆栈包含所有Milvus cloud组件和相关依赖项,如etcd、Pulsar和MinIO。本主题介绍如何使用Milvus cloud Operator安…

Git的安装以及本地仓库的创建和配置

文章目录 1.Git简介2.安装Git2.1在Centos上安装git2.2 在ubuntu上安装git 3.创建本地仓库4.配置本地仓库 1.Git简介 Git是一个分布式版本控制系统&#xff0c;用于跟踪和管理文件的更改。它可以记录和存储代码的所有历史版本&#xff0c;并可以方便地进行分支管理、合并代码和协…