我们知道,很多app都有星星评分的功能,特别是商城app,需要你对商品质量、发货速度、服务态度等进行打分。
项目开发的app正好也需要这个功能,于是自己进行了封装,使用起来也是很简单,满足大部分功能需要,功能如下:
- 可全星打分
- 可半星打分
- 可不完整星打分
- 可点击、可滑动打分
-
可设置星星数量、大小、间隔等
一切根据你的需要来定制,看一下效果图。
使用也是非常简单的
GBStarRateView *starRateView = [[GBStarRateView alloc] initWithFrame:CGRectMake(150, 30, 200, 10) style:GBStarRateViewStyleHalfStar numberOfStars:5 isAnimation:YES delegate:self];
[cell.contentView addSubview:starRateView];
starRateView.numberOfStars = 5;
starRateView.style = GBStarRateViewStyleHalfStar;
首先看.h头文件
typedef NS_ENUM(NSInteger, GBStarRateViewStyle){
GBStarRateViewStyleWholeStar = 0,//全星评分
GBStarRateViewStyleHalfStar = 1, //可半星评分
GBStarRateViewStyleIncompleteStar = 2,//不完整星评分
};
@class GBStarRateView;
@protocol GBStarRateViewDelegate <NSObject>
@optional
- (void)starRateView:(GBStarRateView *)starRateView didSelecteStarAtStarRate:(CGFloat)starRate;
@end
typedef void(^GBStarRateDidSelectStarBlock)(GBStarRateView *starRateView, CGFloat starRate);
@interface GBStarRateView : UIView
@property (nonatomic, assign) GBStarRateViewStyle style; //星星评分样式
@property (nonatomic, weak) id <GBStarRateViewDelegate> delegate; //代理
@property (nonatomic, assign) NSInteger numberOfStars; //星星数量 默认为5
@property (nonatomic, assign) CGFloat currentStarRate; //当前打分 默认为0.0
@property (nonatomic, assign) CGFloat spacingBetweenStars; //星星间隔 默认为10
@property (nonatomic, assign) CGSize starSize; //星星尺寸 默认 {24,24}
@property (nonatomic, strong) UIImage *starImage; //未选中star image 有默认图片
@property (nonatomic, strong) UIImage *currentStarImage; //选中star image 有默认图片
@property (nonatomic, assign) BOOL isAnimation; //是否动画 默认YES
@property (nonatomic, assign) BOOL allowSlideScore; //是否滑动打分 默认NO 适用于不完整星星打分
@property (nonatomic, assign) BOOL allowClickScore; //是否点击打分 默认YES
@property (nonatomic, copy) GBStarRateDidSelectStarBlock didSelectStarBlock;//点击或滑动打分的回调
- (instancetype)initWithFrame:(CGRect)frame style:(GBStarRateViewStyle)style numberOfStars:(NSInteger)numbersOfStars isAnimation:(BOOL)isAnimation delegate:(id <GBStarRateViewDelegate>)delegate;
- (instancetype)initWithFrame:(CGRect)frame style:(GBStarRateViewStyle)style numberOfStars:(NSInteger)numbersOfStars isAnimation:(BOOL)isAnimation finish:(GBStarRateDidSelectStarBlock)finish;
@end
然后再看一下.m的实现
static CGFloat const kAnimatinDuration = 0.3;
@interface GBStarRateView ()
@property (nonatomic, strong) NSMutableArray <UIView *> *starsContentViews;
@end
@implementation GBStarRateView
@synthesize starSize = _starSize;
- (instancetype)initWithFrame:(CGRect)frame style:(GBStarRateViewStyle)style numberOfStars:(NSInteger)numbersOfStars isAnimation:(BOOL)isAnimation finish:(GBStarRateDidSelectStarBlock)finish {
if (self = [super initWithFrame:frame]) {
[self config];
self.style = style;
self.numberOfStars = numbersOfStars;
self.isAnimation = isAnimation;
self.didSelectStarBlock = finish;
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame style:(GBStarRateViewStyle)style numberOfStars:(NSInteger)numbersOfStars isAnimation:(BOOL)isAnimation delegate:(id<GBStarRateViewDelegate>)delegate {
if (self = [super initWithFrame:frame]) {
[self config];
self.style = style;
self.numberOfStars = numbersOfStars;
self.isAnimation = isAnimation;
self.delegate = delegate;
}
return self;
}
- (id)init {
if (self = [super init]) {
[self config];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self config];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[self config];
}
return self;
}
#pragma mark - 基本配置
- (void)config {
self.style = GBStarRateViewStyleWholeStar;
self.numberOfStars = 5;
self.spacingBetweenStars = 10;
_starSize = CGSizeMake(24, 24);
self.currentStarImage = [UIImage imageNamed:@"ic_star_selected"];
self.starImage = [UIImage imageNamed:@"ic_star_default"];
self.isAnimation = YES;
self.allowClickScore = YES;
self.allowSlideScore = NO;
}
#pragma mark - 创建视图
- (void)resetStarsContentView {
for (UIView *starContentView in self.starsContentViews) {
[starContentView removeFromSuperview];
}
[self.starsContentViews removeAllObjects];
[self createStarsContentView:self.starImage starRate:_numberOfStars];
[self createStarsContentView:self.currentStarImage starRate:_currentStarRate];
}
- (void)createStarsContentView:(UIImage *)starImage starRate:(CGFloat)starRate {
if (self.numberOfStars == 0) {
return;
}
CGRect frame = [self frameForStarsContentViewAtCurrentStarRate:starRate];
UIView *starsContentView = [[UIView alloc] initWithFrame:frame];
starsContentView.clipsToBounds = YES;//必须要设置,不设试试效果
[self addSubview:starsContentView];
for (int i = 0; i < self.numberOfStars; i++) {
UIImageView *imageView = [[UIImageView alloc] initWithImage:starImage];
imageView.frame = CGRectMake((self.starSize.width + self.spacingBetweenStars) * i, 0, self.starSize.width, self.starSize.height);
imageView.contentMode = UIViewContentModeScaleAspectFit;
[starsContentView addSubview:imageView];
}
[self.starsContentViews addObject:starsContentView];
}
#pragma mark - StarsContentView frame
- (CGRect)frameForStarsContentViewAtCurrentStarRate:(CGFloat)currentStarRate {
NSInteger index = (NSInteger)floor(currentStarRate);
CGFloat w = (self.starSize.width + self.spacingBetweenStars) * index + (currentStarRate - index) * self.starSize.width;
CGFloat x = (CGRectGetWidth(self.bounds) - [self sizeForNumberOfStar:self.numberOfStars].width) * 0.5;
CGFloat y = (CGRectGetHeight(self.bounds) - [self sizeForNumberOfStar:self.numberOfStars].height) * 0.5;
CGFloat h = self.starSize.height;
return CGRectMake(x, y, w, h);
}
#pragma mark - setter
- (void)setNumberOfStars:(NSInteger)numberOfStars {
if (_numberOfStars != numberOfStars) {
_numberOfStars = numberOfStars;
[self resetStarsContentView];
}
}
- (void)setSpacingBetweenStars:(CGFloat)spacingBetweenStars {
if (_spacingBetweenStars != spacingBetweenStars) {
_spacingBetweenStars = spacingBetweenStars;
[self resetStarsContentView];
}
}
- (void)setStarImage:(UIImage *)starImage {
if (_starImage != starImage) {
_starImage = starImage;
[self resetStarsContentView];
}
}
- (void)setCurrentStarImage:(UIImage *)currentStarImage {
if (_currentStarImage != currentStarImage) {
_currentStarImage = currentStarImage;
[self resetStarsContentView];
}
}
- (void)setStarSize:(CGSize)starSize {
if (!CGSizeEqualToSize(_starSize, starSize)) {
_starSize = starSize;
[self resetStarsContentView];
}
}
- (CGSize)starSize {
if (CGSizeEqualToSize(_starSize, CGSizeZero)) {
_starSize = self.starImage.size;
}
return _starSize;
}
- (void)setCurrentStarRate:(CGFloat)currentStarRate {
if (self.starsContentViews.count == 0 || _currentStarRate == currentStarRate) {
return;
}
if (currentStarRate < 0) {
return;
} else if (currentStarRate > self.numberOfStars) {
_currentStarRate = self.numberOfStars;
} else {
_currentStarRate = currentStarRate;
}
UIView *starsContentView = self.starsContentViews[1];
[UIView animateWithDuration:_isAnimation ? kAnimatinDuration : 0.0 animations:^{
starsContentView.frame = [self frameForStarsContentViewAtCurrentStarRate:currentStarRate];
}];
if (self.didSelectStarBlock) {
self.didSelectStarBlock(self, currentStarRate);
}
if ([self.delegate respondsToSelector:@selector(starRateView:didSelecteStarAtStarRate:)]) {
[self.delegate starRateView:self didSelecteStarAtStarRate:currentStarRate];
}
}
#pragma mark - event
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (_allowClickScore) {
UITouch *touch = [touches anyObject];
UIView *view = touch.view;
if (view != self) {
CGPoint point = [touch locationInView:view];
[self setupScoreWithOffsetX:point.x];
}
}
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (_allowSlideScore) {
UITouch *touch = [touches anyObject];
UIView *view = touch.view;
if (view != self && [self.starsContentViews containsObject:view]) {
CGPoint point = [touch locationInView:view];
[self setupScoreWithOffsetX:point.x];
}
}
}
#pragma mark - 根据offsetx计算分数
- (void)setupScoreWithOffsetX:(CGFloat)offsetX {
NSInteger index = offsetX / (self.starSize.width + self.spacingBetweenStars);
CGFloat mathOffsetX = (index + 1) * self.starSize.width + index * self.spacingBetweenStars;
CGFloat score = (offsetX - index * self.spacingBetweenStars)/(self.starSize.width);
if (offsetX > mathOffsetX) {
score = index + 1;
}
self.currentStarRate = [self currentStarRateWithScore:score];
NSLog(@"offsetX=%f,index=%ld, score=%f, starRate=%f", offsetX, index, score, self.currentStarRate);
}
- (CGFloat)currentStarRateWithScore:(CGFloat)score {
switch (self.style) {
case GBStarRateViewStyleWholeStar: //全星
score = ceil(score);
break;
case GBStarRateViewStyleHalfStar: //半星
score = round(score) > score ? round(score) : (score < (ceil(score)-0.5) ? (ceil(score)-0.5) : ceil(score));
break;
case GBStarRateViewStyleIncompleteStar: //不完整星
score = score;
break;
}
return score;
}
- (CGSize)sizeForNumberOfStar:(NSInteger)starCount {
CGFloat w = (self.starSize.width + self.spacingBetweenStars)*starCount - self.spacingBetweenStars;
return CGSizeMake(w, self.starSize.height);
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
//实现在星星范围内也能响应 即使父视图高度少于星星高度
if (point.y <= (self.starImage.size.height*0.5) && point.y >= - (self.starImage.size.height*0.5 - self.bounds.size.height*0.5)) {
return YES;
}
return [super pointInside:point withEvent:event];
}
#pragma mark - getter
- (NSMutableArray<UIView *> *)starsContentViews {
if (!_starsContentViews) {
_starsContentViews = [NSMutableArray array];
}
return _starsContentViews;
}
欢迎大家下载使用传送门, 比心~~~