热门搜索 :
考研考公
您的当前位置:首页正文

iOS 知识点1--环形颜色渐变进度条2

来源:东饰资讯网

注:视频中添加了动画效果

effect.gif

之前已经进过的这里就不讲了

第一步 动画原理

原理:利用drawRect的不停重绘,制作动画效果

以下是方法解释,这里跟代码里相比,简化了一下,代码里更详细一些


//每一小块的角度(弧度值)
    float perAngle = M_PI*2*self.currentProgress/self.subdivCount;
    
    //当前开始角度
    float currentStartAngle;
    //当前结束角度
    float currentOverAngle;
    for (NSInteger i = 0; i<self.subdivCount; i++) {
        //当前开始角度
        currentStartAngle = self.startAngle+perAngle*i;
        //当前结束角度
        currentOverAngle  = currentStartAngle + perAngle;
        //添加路径
        CGContextAddArc(ctx,
                        center.x,
                        center.y,
                        self.radius-self.strokeWidth/2.0,
                        currentStartAngle,
                        currentOverAngle,
                        self.isClockDirection);
        //开始渲染绘制图形(画图)kCGPathFillStroke这个模式的意思是描边和填充部分都要绘制
        /**
         具体效果,自行尝试
         kCGPathFill,//填充
         kCGPathEOFill,//奇偶填充
         kCGPathStroke,//描边
         kCGPathFillStroke,//填充 描边
         kCGPathEOFillStroke//奇偶填充 描边
         */
        CGContextDrawPath(ctx, kCGPathFillStroke);

第二步 制作使用步骤

创建一个输入框和一个确定按钮在效果图上面

在输入框里输入进度值之后,按下确定按钮,即可

//在头上加个输入框测试动画功能。
    UITextField *animationTestTF = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 50)];
    animationTestTF.keyboardType = UIKeyboardTypeDecimalPad;
    [self.view addSubview:animationTestTF];
    animationTestTF.backgroundColor = [UIColor groupTableViewBackgroundColor];
    animationTestTF.center = CGPointMake(cpView.center.x-40, cpView.center.y-70-50);
    self.animationTestTF = animationTestTF;
    //确定输入进度值按钮
    UIButton *sureBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    sureBtn.frame = CGRectMake(0, 0, 30, 30);
    sureBtn.titleLabel.adjustsFontSizeToFitWidth = YES;
    [sureBtn setTitle:@"sure" forState:UIControlStateNormal];
    [sureBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    sureBtn.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:sureBtn];
    sureBtn.center = CGPointMake(animationTestTF.frame.size.width+animationTestTF.frame.origin.x+40, animationTestTF.center.y);
    [sureBtn addTarget:self action:@selector(sureThisProgress) forControlEvents:UIControlEventTouchUpInside];
//确定按键方法
- (void)sureThisProgress{
    self.cpView.isNoAnimated = NO;
    if (self.animationTestTF.text&&self.animationTestTF.text.length) {
        //改变进度条的值
        self.cpView.progress = self.animationTestTF.text.floatValue;
    }
}
//点击屏幕,收回键盘
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    
    [self.view endEditing:YES];
}

第三步 CircleProgressView中动画效果相关属性与方法讲解

/**
 不加动画, 默认为NO。
 友情提示,如果你写的页面有类似滑动条的方法控制进度线性增减的画,就将此属性设置为YES,即不加动画。
 */
@property (nonatomic, assign) BOOL isNoAnimated;

/**
 是否从上次数值开始动画。默认为YES,即每次都从上次动画结束开始动画。
 */
@property (nonatomic, assign) BOOL isStartFromLast;

/**
 元素(小块)个数。默认为64,越高线条越平滑,性能越差。64就刚刚好,当然这个属性其实还是看项目的需求而设置的更高或更低。
 */
@property (nonatomic, assign) NSInteger subdivCount;

/**
 动画时长。当animationSameTime为NO时,此属性为动画的最长时间,即progress=1时的动画时间。
 */
@property (nonatomic, assign) CGFloat animationDuration;

/**
 动画是否同等时间。
 为YES则不同进度动画时长都为animationDuration,
 为NO则根据不同进度对应不同动画时长,进度最大时动画时长为animationDuration。
 默认为YES
 */
@property (nonatomic, assign) BOOL animationSameTime;

//这部分是.m文件里的属性
/**
 定时器,如果你是用UISlider控件来控制进度的话,那么只要在设置进度的时候,标记一下需要重绘,就会给人一种动画感。因为UISlider的值变化是连续变化。但如果你需要让进度直接从0.3->0.8就不行了,这个时候你就需要一个动画来给用户更好的体验。而CADisplayLink定时器这时就是你必不可少的动画利器在。他的优缺点此处不多言,自己上网查,一大堆。这里只说一句,CADisplayLink是基于屏幕刷新而制定的。
 */
@property (nonatomic, strong) CADisplayLink *playLink;

/**
 当前进度,顾名思义,在动画过程中,每次屏幕刷新重绘时相对应的进度值
 */
@property (nonatomic, assign) CGFloat currentProgress;

/**
 增加的进度度值,一句话,动画时每次屏幕刷新,当前进度所增加的值
 */
@property (nonatomic, assign) CGFloat increaseProgressValue;

/**
 是否巅倒反转,进度值不单单会增长,也会减少。所以动画不单单需要前进增长,也要能后退缩短。
 */
@property (nonatomic, assign) BOOL isReverse;

这里是相关的初始设置,好吧,实然发现又没什么好讲的了,因为demo注释相当清楚。

//需要动画
    _isNoAnimated           = NO;
    //新动画结束时,从上次动画结束的位置开始动画
    _isStartFromLast        = YES;
    //动画时的被分割的块数
    _subdivCount = 64;
    //动画默认时长
    _animationDuration      = 2;
    //不同动画的时长是否相同
    _animationSameTime      = YES;

这里只贴部分核心代码,demo里很详细,可以自己下着看

//每一小块的角度(弧度值)
    float perAngle = M_PI*2*self.currentProgress/self.subdivCount;
    
    //当前开始角度
    float currentStartAngle;
    //当前结束角度
    float currentOverAngle;
    for (NSInteger i = 0; i<self.subdivCount; i++) {
        //当前开始角度
        currentStartAngle = self.startAngle+perAngle*i;
        //当前结束角度
        currentOverAngle  = currentStartAngle + perAngle;
        //添加路径
        CGContextAddArc(ctx,
                        center.x,
                        center.y,
                        self.radius-self.strokeWidth/2.0,
                        currentStartAngle,
                        currentOverAngle,
                        self.isClockDirection);
        //开始渲染绘制图形(画图)kCGPathFillStroke这个模式的意思是描边和填充部分都要绘制
        /**
         具体效果,自行尝试
         kCGPathFill,//填充
         kCGPathEOFill,//奇偶填充
         kCGPathStroke,//描边
         kCGPathFillStroke,//填充 描边
         kCGPathEOFillStroke//奇偶填充 描边
         */
        CGContextDrawPath(ctx, kCGPathFillStroke);

//进度设置相关

//当前进度,如果要求从上次结束的动画开始的话,将当前进度设置为上次的进度值。否则设为0.0
    self.currentProgress = self.isStartFromLast==YES?self.progress:0.0;
    
    //对比传进来的进度值与当前进度,以确定动画是否为后退动画,不明白这句代码意思的,多看两遍,你就明白其中的逻辑了。
    self.isReverse = progress<self.currentProgress?YES:NO;
    
    //当前进度
    _progress = progress;
    
    if (self.isNoAnimated) {//不需要动画
        //直接将当前进度值设置为本次进度值
        self.currentProgress = self.progress;
        //标记重绘,当屏幕刷新的时候会自动调取drawrect方法
        /**
         ios 屏幕刷新1秒60次。
         */
        [self setNeedsDisplay];
    }else {//需要动画
        //这里要做的事就有点多了,慢慢看就懂了
        CGFloat pinLv = 60;//屏幕每秒刷新的次数(有兴趣的可以了解一下)
        //是否从上次结束开始动画
        if (self.isStartFromLast) {//是(从上次结束开始动画)
            if (self.animationSameTime) {
                //这里注释说明下,要不然,逻辑就容易迷糊了
                /**
                 首先,要记得在这里的都是需要动画的。其次如果前面的逻辑你理解清晰的话,那这里就容易理解多了。
                 self.progress - self.currentProgress,这个减式是为了求出,这一段动画的动画距离(弧度)。(有正负之分,关系到动画是前进还是后退)
                 pinLv*self.animationDuration这个是动画的时间,千万要理解,这个乘式的含义。接下来我们要用的是CADisplayLink定时器。一秒60次。如果直接用self.animationDuration作分母忘了乘以频率的话,呵呵……
                 */
                //每次动画增加的值
                self.increaseProgressValue = (self.progress - self.currentProgress)/(pinLv*self.animationDuration);
            }else {
                //如果你能领会上面的代码这里你一定懂。如果想不通,可以看一下下面的CADisplayLink定时器相关方法
                self.increaseProgressValue = self.isReverse == YES ? -1.0/(pinLv*_animationDuration):1.0/(pinLv*_animationDuration);
            }
        }else {//不是
            //从0开始动画
            if (self.animationSameTime) {
                //理解了上面的代码你就懂
                self.increaseProgressValue = self.progress/(pinLv*self.animationDuration);
            }else {
                //理上你懂
                self.increaseProgressValue = 1.0/(pinLv*_animationDuration);
            }
        }
        
        
        
        //重置定时器
        if (self.playLink) {
            [self.playLink invalidate];
            self.playLink = nil;
        }
        //CADisplayLink定时器使用方法,不解释,自查,可以关注下preferredFramesPerSecond、frameInterval这两个属性(想优化代码性能的话)
        CADisplayLink *playLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(countingAction)];
        [playLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
        self.playLink = playLink;
    }
//定时器方法
- (void)countingAction{
    
    //当前进度设置每次动画都要加一份增加值
    self.currentProgress += self.increaseProgressValue;
    [self setNeedsDisplay];
    
    /**
     判断动画是否该结束了
     */
    if (self.isStartFromLast) {
        if (self.isReverse) {//倒退动画
            if (self.currentProgress <= self.progress) {//当前进度值小于等于目标进度值时结束
                [self dealWithLast];
            }
        } else {
            if (self.currentProgress >= self.progress) {//当前进度值大于等于目标进度值时结束
                [self dealWithLast];
            }
        }
    } else {
        if (self.currentProgress >= self.progress) {//当前进度值大于等于目标进度值时结束
            [self dealWithLast];
        }
    }
    
}
//最后一次动画内容
- (void)dealWithLast {
    //调节一下进度值
    self.currentProgress = self.progress;
    
    //注销定时器
    [self.playLink invalidate];
    self.playLink = nil;
    
    //标记刷新(动画增长)
    [self setNeedsDisplay];
}

结语:

逻辑很清晰,但看起代码来,可能还是会头大,不过不用担心,因为我代码注释写的很清楚,还是中文的。

Top