iOS 一個可高度自定義化的評分控制元件、打分、打星

SionChen發表於2019-04-19

  在近期的開發計劃中,我們的開發計劃有關於商品打分的功能需求列了進來,我就提前看了下,網上的這種demo實在太多了,但是都不是很符合我們的需求,索性自己寫一個得了,那就開始動手。先看一下最終效果(最後有demo哦):

QQ20181115-174009-HD.gif
  那有興趣的同學繼續往下看。

  先理一下實現思路,我的實現思路是利用CALayer的maskLayer來實現,就跟一般的進度條實現思路差不多,不過這次要用帶星的view來做mask。具體實現看下面。

  首先寫maskLayer的view 也就是帶星星的view:

#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, CYXRatingStarStyle) {
    RatingStarStyleFull = 0, //滿星打分
    RatingStarStyleHalf = 1, //可半星打分
};
NS_ASSUME_NONNULL_BEGIN

@interface CYXRatingStarMaskView : UIView

/*星星個數*/
-(instancetype)initWithStarNum:(NSInteger)starNum;
/**
 初始化方法
 
 @param starNum 星星個數
 @param space 星星直接的間距 預設15
 @return self
 */
-(instancetype)initWithStarNum:(NSInteger)starNum andSpace:(CGFloat)space;
/*更新佈局 在設定frame之後呼叫*/
-(void)updateViewConstrains;
/*觸控*/
-(void)touchesWithPoint:(CGPoint)touchPoint;
/*打分風格*/
@property (nonatomic,assign) CYXRatingStarStyle ratingStarStyle;
/*總分*/
@property (nonatomic,assign) NSInteger fullScore;
/*點選 呼叫block  transformPoint轉換過的點  score得分*/
@property (nonatomic,strong) void(^touchBlock)(CGPoint transformPoint,NSInteger score);

@end
複製程式碼

相關實現(多的我就不再囉嗦了註釋裡面都有):

#import "CYXRatingStarMaskView.h"
@interface CYXRatingStarMaskView ()
/*星星個數 最少兩個*/
@property (nonatomic,assign) NSInteger starNum;
/*星星間距 預設15*/
@property (nonatomic,assign) CGFloat space;

@property (nonatomic,strong) NSMutableArray *itemList;
@end
@implementation CYXRatingStarMaskView
-(instancetype)initWithStarNum:(NSInteger)starNum{
    return [self initWithStarNum:starNum andSpace:15];
}

-(instancetype)initWithStarNum:(NSInteger)starNum andSpace:(CGFloat)space{
    if (self = [super init]) {
        self.starNum = starNum;
        self.space = space;
        [self createItems];
    }
    return self;
}
-(void)createItems{
    self.itemList = [NSMutableArray new];
    for (int i =0; i<self.starNum; i++) {
        UIImageView * item = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"starUnselected"]];
        [self.itemList addObject:item];
        [self addSubview:item];
    }
}
-(void)updateViewConstrains{
    if (self.starNum>1) {
        CGFloat itemWidth = (self.frame.size.width-(self.starNum -1)*self.space)/self.starNum;
        if (itemWidth>0) {
            CGFloat x = 0;
            for (UIImageView * item in self.itemList) {
                item.frame = CGRectMake(x, 0, itemWidth, self.frame.size.height);
                x+=itemWidth;
                x+=self.space;
            }
        }
    }
}
-(void)touchesWithPoint:(CGPoint)touchPoint{
    [self transformPointWithTouchPoint:touchPoint];
}

/*將觸點轉化成填充星星的點*/
-(CGPoint)transformPointWithTouchPoint:(CGPoint)touchPoint{
    /*滿星平均分*/
    NSInteger average = self.fullScore/[self.itemList count];
    /*觸控點x*/
    CGFloat x = touchPoint.x;
    /*得分*/
    CGFloat score = 0;
    /*倒序遍歷*/
    for (NSInteger i = [self.itemList count]-1; i>=0; i--) {
        UIImageView * item = self.itemList[i];
        if (x>item.frame.origin.x) {
            switch (self.ratingStarStyle) {
                case RatingStarStyleFull:{//滿星
                    score = (i+1)*average;
                    x = item.frame.origin.x+item.frame.size.width;
                }
                    break;
                case RatingStarStyleHalf:{//支援半星
                    if (x>(item.frame.origin.x+item.frame.size.width/2)) {//超過半星
                        score = (i+1)*average;
                        x = item.frame.origin.x+item.frame.size.width;
                    }else{
                        score = (i+1)*average - (average/2);
                        x = item.frame.origin.x+item.frame.size.width/2;
                    }
                    
                }
                    break;
                default:
                    break;
            }
            break;
        }
    }
    CGPoint transformPoint = CGPointMake(x, touchPoint.y);
    if (self.touchBlock) {
        self.touchBlock(transformPoint, score);
    }
    return CGPointZero;
}
複製程式碼

然後建立用於打分的View來利用這個maskview實現:

#import <UIKit/UIKit.h>
#import "CYXRatingStarMaskView.h"
NS_ASSUME_NONNULL_BEGIN
@interface CYXRatingStarView : UIView

/**
 初始化方法

 @param frame frame
 @param ratingStarStyle 打分風格
 @param fullScore 滿分
 @return self
 */
-(instancetype)initWithFrame:(CGRect)frame
          andRatingStarStyle:(CYXRatingStarStyle)ratingStarStyle
                andFullScore:(NSInteger)fullScore;
/**
 初始化layer 在完成frame賦值後呼叫一下
 */
-(void)initLayers;
/*選擇分數*/
@property (nonatomic,strong) void (^selectScore)(NSInteger score);
@end

複製程式碼

相關實現(沒啥難點都有註釋自己看吧):

#import "CYXRatingStarView.h"

@interface CYXRatingStarView()
/*背景紅色*/
@property (nonatomic,strong) CAShapeLayer *backColorLayer;
@property (nonatomic,strong) CYXRatingStarMaskView *maskView;
@end
@implementation CYXRatingStarView

-(instancetype)initWithFrame:(CGRect)frame
          andRatingStarStyle:(CYXRatingStarStyle)ratingStarStyle
                andFullScore:(NSInteger)fullScore{
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor whiteColor];
        self.maskView.fullScore = fullScore;
        self.maskView.ratingStarStyle = ratingStarStyle;
        [self.layer addSublayer:self.backColorLayer];
        
    }
    return self;
}
-(void)initLayers{
    self.maskView.frame = self.bounds;
    [self.maskView updateViewConstrains];
    self.backColorLayer.frame = self.bounds;
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(0, self.frame.size.height/2)];
    [path addLineToPoint:CGPointMake(self.frame.size.width, self.frame.size.height/2)];
    self.backColorLayer.path = path.CGPath;
    self.backColorLayer.lineWidth = self.frame.size.height;
    self.backColorLayer.mask = self.maskView.layer;
    self.backColorLayer.strokeEnd = 0;
}
/*設定選擇的星星*/
-(void)setStrokeWithTransformPoint:(CGPoint)transformPoint{
    CGPoint newPoint = [self convertPoint:transformPoint fromView:self.maskView];
    NSLog(@"%f",newPoint.x);
    self.backColorLayer.strokeEnd = newPoint.x/self.frame.size.width;
}
/*一根或者多根手指開始觸控view,系統會自動呼叫view的下面方法*/
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //獲取touch
    UITouch * touch = [touches anyObject];
    //獲取當前點
    CGPoint touchPoint = [touch locationInView:self];
    CGPoint newPoint = [self convertPoint:touchPoint toView:self.maskView];
    [self.maskView touchesWithPoint:newPoint];
}
#pragma mark ---G
-(CAShapeLayer*)backColorLayer{
    if(!_backColorLayer){
        _backColorLayer = [[CAShapeLayer alloc] init];
        _backColorLayer.backgroundColor = [UIColor grayColor].CGColor;//在這裡是未填充的顏色
        _backColorLayer.fillColor = [UIColor clearColor].CGColor; // 填充色為透明(不設定為黑色)
        //_backColorLayer.lineCap = kCALineCapSquare; // 設定線為圓角
        _backColorLayer.strokeColor = [UIColor redColor].CGColor; // 路徑顏色顏色(填充顏色)
    }
    return _backColorLayer;
}
-(CYXRatingStarMaskView*)maskView{
    if(!_maskView){
        __weak __typeof(&*self)weakSelf = self;
        _maskView = [[CYXRatingStarMaskView alloc] initWithStarNum:5];
        _maskView.touchBlock = ^(CGPoint transformPoint, NSInteger score) {
            NSLog(@"%@",NSStringFromCGPoint(transformPoint));
            NSLog(@"%ld",(long)score);
            [weakSelf setStrokeWithTransformPoint:transformPoint];
            if (weakSelf.selectScore) {
                weakSelf.selectScore(score);
            }
        };
    }
    return _maskView;
}
複製程式碼

  總體寫下來思路還是很明確的,而且實現過程當中並沒有什麼疑難雜症,有什麼問題歡迎討論。

  demo地址:github.com/SionChen/CY…

相關文章