iOS中間透明周圍不透明(鏤空)方法實現特種ProgressBar

前言

最近由於要給APP更新UI,然後UIDesigner爲了”嚐鮮”,給我一些新的界面設計,所以我這裏也不得不再來造些另外的輪子。如果對您有幫助,麻煩Github給個star

如果有興趣的,可以移步iOS UIBezierPath繪圖相關問題,裏邊一些遇到的問題的詳細的總結

這裏先來看下今天要造的輪子
效果圖

沒錯,就是上邊的進度條。

實現

  • 先說下思路(採用的是使用MASK遮罩,然後改變底層OrangeColorLayer長度,以達到進度條長度變化的思路
    1. 首先,我們創建一個新的類,繼承自UIView,然後,我們採用Core Graphics框架和UIBezierPath類來實現中間的進度條樣式。先來分析下進度條,可以看出來,進度條內部,有兩種顏色:橙色的TintColorDarkGray的進度達不到的進度條顏色,然後,進度條背景色lightDarkColor,和最上層圓形中的小圓點
    2. 我這裏的進度條,實現發方法是採用4層layer來實現:最底層,繪製DarkGray顏色的進度條,再往上一層,設置有顏色的一層layer,通過改變該層layer的寬度,已達到有顏色進度條的長度變化,再上層,是一個中間進度條輪廓透明,周圍是lightGrayColor背景色的遮罩View,最上層,是幾個進度條內部小圓的layer。採用的是使用MASK遮罩,然後改變底層OrangeColorLayer長度,以達到進度條長度變化的思路

  • 這裏代碼
    1.先看下暴露出來的API接口

    @interface RRMyMaterialProgressBar : UIView
    //一共項數
    //@property (nonatomic,assign) CGFloat totalStepsCounts;
    
    @property (nonatomic,strong) CAShapeLayer *firstArc;
    
    @property (nonatomic,strong) CAShapeLayer *secondArc;
    
    @property (nonatomic,strong) CAShapeLayer *thirdArc;
    
    @property (nonatomic,strong) CAShapeLayer *forthArc;
    
    //當前進度
    @property (nonatomic,assign) CGFloat currentPercentage;
    //渲染顏色
    //@property (nonatomic,strong) UIColor *progressBarTintColor;
    
    - (instancetype)initWithFrame:(CGRect)frame totalStepsCounts:(CGFloat)steps progressBarTintColor:(UIColor *)tintClr;
    
    @end
    

PS:

這裏是有一些問題的,最開始的時候,我是在drawRect方法中實現的,然後把總共的圓形個數,和進度條的渲染顏色都作爲了外部屬性,也是能實現的,但是:有些問題:考慮下我們的使用場景:一般我們都是在外層VC的viewWillAppear或者viewDidLoad方法中設置這個progressBar的進度,但是!如果在drawRect方法中實現的話,此時,內部的各個layer都還沒有初始化,全部都是nil,如果在viewDidAppear方法中設置的話,會有一個BUG:剛進到頁面內的時候,會看到進度條的進度是100%的,(因爲默認創建ColorLayer的時候是設置的整個Frame的),然後,調用外層viewDidAppear方法設置進度條的進度Layer長度,此時,進度條纔會又從頭開始顯示。這麼一來,用戶體驗就不是太好了。

所以,我又優化了一下:progressBarView 初始化的時候,就來繪製這些內層layer,以此來保證外部VC調用viewWillAppear或者viewDidLoad方法的時候,內層progressBar內的各個實例都已經ALLOC,並且可以設置他們。(所以,以前的totalStepsCounts和progressBarTintColor都可以設置爲私有變量),然後我們直接在初始化方法中傳入參數以賦值。

#import "RRMyMaterialProgressBar.h"

@interface RRMyMaterialProgressBar()

@property (nonatomic,strong) CALayer *backColorLayer;

@property (nonatomic,strong) CAShapeLayer *middleDarkArcLayer;

@property (nonatomic,strong) CAShapeLayer *foregroundMaskLayer;


//渲染顏色
@property (nonatomic,strong) UIColor *progressBarTintColor;

//上一次的比例
@property (nonatomic,assign) CGFloat lastPercentage;

//一共項數
@property (nonatomic,assign) CGFloat totalStepsCounts;

@end

@implementation RRMyMaterialProgressBar

- (instancetype)initWithFrame:(CGRect)frame totalStepsCounts:(CGFloat)steps progressBarTintColor:(UIColor *)tintClr{
    if (self == [super initWithFrame:frame]) {
        self.progressBarTintColor = tintClr;
        self.totalStepsCounts = steps;
        [self setupSubLayersWithFrame:frame];
    }
    return self;
}


- (void)setupSubLayersWithFrame:(CGRect)rect {
    self.layer.backgroundColor = [UIColor redColor].CGColor;

    //左右兩側的距離
    CGFloat padding = 10.f;
    //外圓的半徑
    CGFloat arcRadius = 7.f;
    //中間線的線寬
    CGFloat middleLineWidth = 1.f;
    //內層圓按鈕半徑
    CGFloat innerArcRadius = 4.f;
    CGFloat viewWidth = rect.size.width;
    CGFloat viewHeight = rect.size.height;


    double arcSinValue = middleLineWidth/arcRadius;
    double radian = asin(arcSinValue);

    double tanValue = tan(radian);

    CGFloat arcTanLength = middleLineWidth/tanValue;

    CGFloat middleLineDistance = (viewWidth - 2 * padding - arcRadius * 2 - (self.totalStepsCounts * 2 - 2) * arcTanLength)/(self.totalStepsCounts - 1);


    self.middleDarkArcLayer = [CAShapeLayer layer];
    UIBezierPath *middleDrakColorPath = [UIBezierPath bezierPath];
    [middleDrakColorPath moveToPoint:CGPointMake(padding, viewHeight/2)];
    [middleDrakColorPath addArcWithCenter:CGPointMake(padding + arcRadius, viewHeight/2) radius:arcRadius startAngle:M_PI endAngle:2*M_PI - radian clockwise:YES];
    [middleDrakColorPath addLineToPoint:CGPointMake(padding + arcRadius +arcTanLength + middleLineDistance, viewHeight/2 - middleLineWidth/2)];
    [middleDrakColorPath addArcWithCenter:CGPointMake(padding + arcRadius +arcTanLength * 2 + middleLineDistance, viewHeight/2) radius:arcRadius startAngle:M_PI + radian endAngle:2 * M_PI - radian clockwise:YES];
    [middleDrakColorPath addLineToPoint:CGPointMake(padding + arcRadius + 3 * arcTanLength + middleLineDistance * 2, viewHeight/2 - middleLineWidth/2)];
    [middleDrakColorPath addArcWithCenter:CGPointMake(padding + arcRadius + 4 * arcTanLength + middleLineDistance * 2, viewHeight/2) radius:arcRadius startAngle:M_PI + radian endAngle:2 * M_PI - radian clockwise:YES];
    [middleDrakColorPath addLineToPoint:CGPointMake(padding + arcRadius + 5 * arcTanLength + middleLineDistance * 3, viewHeight/2 - middleLineWidth/2)];
    [middleDrakColorPath addArcWithCenter:CGPointMake(padding + arcRadius + 6 * arcTanLength + middleLineDistance * 3, viewHeight/2) radius:arcRadius startAngle:M_PI + radian endAngle:3 * M_PI - radian clockwise:YES];
    [middleDrakColorPath addLineToPoint:CGPointMake(padding + arcRadius + 5 * arcTanLength + middleLineDistance * 2, viewHeight/2 + middleLineWidth/2)];
    [middleDrakColorPath addArcWithCenter:CGPointMake(padding + arcRadius + 4 * arcTanLength + middleLineDistance * 2, viewHeight/2) radius:arcRadius startAngle:radian endAngle:M_PI - radian clockwise:YES];
    [middleDrakColorPath addLineToPoint:CGPointMake(padding + arcRadius + 3 * arcTanLength + middleLineDistance, viewHeight/2 + middleLineWidth/2)];
    [middleDrakColorPath addArcWithCenter:CGPointMake(padding + arcRadius + 2 * arcTanLength + middleLineDistance, viewHeight/2) radius:arcRadius startAngle:radian endAngle:M_PI - radian clockwise:YES];
    [middleDrakColorPath addLineToPoint:CGPointMake(padding + arcRadius + arcTanLength, viewHeight/2 + middleLineWidth/2)];
    [middleDrakColorPath addArcWithCenter:CGPointMake(padding + arcRadius, viewHeight/2) radius:arcRadius startAngle:radian endAngle:M_PI - radian clockwise:YES];
    [middleDrakColorPath closePath];

    self.middleDarkArcLayer.fillColor = RGB(203, 204, 205).CGColor;
    self.middleDarkArcLayer.path = middleDrakColorPath.CGPath;
    [self.layer addSublayer:self.middleDarkArcLayer];


    //MARK: 顏色層layer
    self.backColorLayer = [CALayer layer];
    self.backColorLayer.frame = CGRectMake(0, 0, viewWidth, viewHeight);
    self.backColorLayer.backgroundColor = self.progressBarTintColor.CGColor;
    self.backColorLayer.position = CGPointMake(0, viewHeight/2);
    self.backColorLayer.anchorPoint = CGPointMake(0, 0.5);
    [self.layer addSublayer:self.backColorLayer];


    self.foregroundMaskLayer = [CAShapeLayer layer];
    //MARK: 使用UIBezierPath 繪製遮擋VIEW的路徑
    UIBezierPath *outerPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0,0, rect.size.width, rect.size.height) cornerRadius:rect.size.height/2];

    UIBezierPath *colorPath = [UIBezierPath bezierPath];
    [colorPath moveToPoint:CGPointMake(padding, viewHeight/2)];
    [colorPath addArcWithCenter:CGPointMake(padding + arcRadius, viewHeight/2) radius:arcRadius startAngle:M_PI endAngle:2*M_PI - radian clockwise:YES];
    [colorPath addLineToPoint:CGPointMake(padding + arcRadius +arcTanLength + middleLineDistance, viewHeight/2 - middleLineWidth/2)];
    [colorPath addArcWithCenter:CGPointMake(padding + arcRadius +arcTanLength * 2 + middleLineDistance, viewHeight/2) radius:arcRadius startAngle:M_PI + radian endAngle:2 * M_PI - radian clockwise:YES];
    [colorPath addLineToPoint:CGPointMake(padding + arcRadius + 3 * arcTanLength + middleLineDistance * 2, viewHeight/2 - middleLineWidth/2)];
    [colorPath addArcWithCenter:CGPointMake(padding + arcRadius + 4 * arcTanLength + middleLineDistance * 2, viewHeight/2) radius:arcRadius startAngle:M_PI + radian endAngle:2 * M_PI - radian clockwise:YES];
    [colorPath addLineToPoint:CGPointMake(padding + arcRadius + 5 * arcTanLength + middleLineDistance * 3, viewHeight/2 - middleLineWidth/2)];
    [colorPath addArcWithCenter:CGPointMake(padding + arcRadius + 6 * arcTanLength + middleLineDistance * 3, viewHeight/2) radius:arcRadius startAngle:M_PI + radian endAngle:3 * M_PI - radian clockwise:YES];
    [colorPath addLineToPoint:CGPointMake(padding + arcRadius + 5 * arcTanLength + middleLineDistance * 2, viewHeight/2 + middleLineWidth/2)];
    [colorPath addArcWithCenter:CGPointMake(padding + arcRadius + 4 * arcTanLength + middleLineDistance * 2, viewHeight/2) radius:arcRadius startAngle:radian endAngle:M_PI - radian clockwise:YES];
    [colorPath addLineToPoint:CGPointMake(padding + arcRadius + 3 * arcTanLength + middleLineDistance, viewHeight/2 + middleLineWidth/2)];
    [colorPath addArcWithCenter:CGPointMake(padding + arcRadius + 2 * arcTanLength + middleLineDistance, viewHeight/2) radius:arcRadius startAngle:radian endAngle:M_PI - radian clockwise:YES];
    [colorPath addLineToPoint:CGPointMake(padding + arcRadius + arcTanLength, viewHeight/2 + middleLineWidth/2)];
    [colorPath addArcWithCenter:CGPointMake(padding + arcRadius, viewHeight/2) radius:arcRadius startAngle:radian endAngle:M_PI - radian clockwise:YES];

    [outerPath appendPath:colorPath];
    [outerPath setUsesEvenOddFillRule:YES];


    self.foregroundMaskLayer.path = outerPath.CGPath;
    self.foregroundMaskLayer.fillRule = kCAFillRuleEvenOdd;
    self.foregroundMaskLayer.fillColor = RGB(221, 221, 221).CGColor;
    [self.layer addSublayer:self.foregroundMaskLayer];

    //MARK: 第一個內部圓
    self.firstArc = [CAShapeLayer layer];
    UIBezierPath *arcPath1 = [UIBezierPath bezierPath];
    [arcPath1 addArcWithCenter:CGPointMake(padding + arcRadius, viewHeight/2) radius:innerArcRadius startAngle:0 endAngle:2 * M_PI clockwise:1];
    self.firstArc.path = arcPath1.CGPath;
    self.firstArc.fillColor = RGB(221, 221, 221).CGColor;
    [self.layer addSublayer:self.firstArc];


    //MARK: 第二個內部圓
    self.secondArc = [CAShapeLayer layer];
    UIBezierPath *arcPath2 = [UIBezierPath bezierPath];
    [arcPath2 addArcWithCenter:CGPointMake(padding + arcRadius + arcTanLength + middleLineDistance + arcTanLength, viewHeight/2) radius:innerArcRadius startAngle:0 endAngle:2 * M_PI clockwise:1];
    self.secondArc.path = arcPath2.CGPath;
    self.secondArc.fillColor = RGB(221, 221, 221).CGColor;
    [self.layer addSublayer:self.secondArc];


    //MARK: 第三個內部圓
    self.thirdArc = [CAShapeLayer layer];
    UIBezierPath *arcPath3 = [UIBezierPath bezierPath];
    [arcPath3 addArcWithCenter:CGPointMake(padding + arcRadius + arcTanLength + middleLineDistance + arcTanLength + arcTanLength + middleLineDistance + arcTanLength, viewHeight/2) radius:innerArcRadius startAngle:0 endAngle:2 * M_PI clockwise:1];
    self.thirdArc.path = arcPath3.CGPath;
    self.thirdArc.fillColor = RGB(221, 221, 221).CGColor;
    [self.layer addSublayer:self.thirdArc];


    //MARK: 第四個內部圓
    self.forthArc = [CAShapeLayer layer];
    UIBezierPath *arcPath4 = [UIBezierPath bezierPath];
    [arcPath4 addArcWithCenter:CGPointMake(padding + arcRadius + arcTanLength + middleLineDistance + arcTanLength + arcTanLength + middleLineDistance + arcTanLength + arcTanLength + middleLineDistance + arcTanLength, viewHeight/2) radius:innerArcRadius startAngle:0 endAngle:2 * M_PI clockwise:1];
    self.forthArc.path = arcPath4.CGPath;
    self.forthArc.fillColor = RGB(221, 221, 221).CGColor;
    [self.layer addSublayer:self.forthArc];
}


- (void)setCurrentPercentage:(CGFloat)currentPercentage {
    _currentPercentage = currentPercentage;

    CABasicAnimation *animate = [CABasicAnimation animationWithKeyPath:@"transform.scale.x"];
    animate.fromValue = self.lastPercentage == 0 ? @(0) : @(self.lastPercentage);        
    animate.toValue = @(currentPercentage);
    animate.repeatCount = 1;
    animate.autoreverses = NO;
    animate.duration = 1.f;
    animate.removedOnCompletion = NO;
    animate.fillMode = kCAFillModeForwards;
    [self.backColorLayer addAnimation:animate forKey:@"progressAnimation"];

    self.lastPercentage = currentPercentage;

}

另外

整個進度條並不是圓形是環形的,如果有UI需求,我們可以通過設置API接口中的四個shapeLayer的fillColor值來改變內部小圓的顏色的

self.forthArc.fillColor = [UIColor yourWantedColor].CGColor;

這裏有些參考資料
都是關於view中間鏤空周圍不是空的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章