前言
很久沒(méi)有更新簡(jiǎn)書了系冗,這段時(shí)間一直在忙著擼代碼。項(xiàng)目的事情暫時(shí)總算是告一段落了薪鹦,打開(kāi)簡(jiǎn)書一看掌敬,整個(gè)六月份一篇都沒(méi)有寫。本來(lái)還想強(qiáng)行解釋一波的池磁,算了奔害,總結(jié)一句話,還是懶地熄。新項(xiàng)目設(shè)計(jì)妹紙正在做設(shè)計(jì)稿华临,看到項(xiàng)目中有一個(gè)星形評(píng)分的控件,閑來(lái)無(wú)事端考,找了幾個(gè)網(wǎng)上的看了一下雅潭,都不是很滿足自己的需求,所以自己動(dòng)手實(shí)現(xiàn)了一下却特。
效果圖
實(shí)現(xiàn)思路
- 創(chuàng)建前后兩個(gè)frame相等的視圖
- 在創(chuàng)建好的視圖上循環(huán)添加需要個(gè)數(shù)的UIImageView扶供,并設(shè)置對(duì)應(yīng)的圖片。
- 根據(jù)手指在控件上的位置獲取X軸上的偏移量核偿,計(jì)算成分?jǐn)?shù)诚欠。
代碼實(shí)現(xiàn)
大致的實(shí)現(xiàn)思路了解了,接下來(lái)要做的就是編碼實(shí)現(xiàn)漾岳。首先創(chuàng)建一個(gè)類 XKStarRateView
繼承自 UIView
轰绵,在 XKStarRateView.h
中,聲明一些初始的構(gòu)造方法尼荆。
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, XKStarRateStyle) {
XKWholeStarStyle = 0,
XKHalfStarStyle = 1,
XKIncompleteStarStyle = 2
};
typedef void(^XKStarRateSelectedBlock)(CGFloat score);
@class XKStarRateView;
@protocol XKStarRateViewDelegate <NSObject>
- (void)starRateView:(XKStarRateView *)starRateView currentScore:(CGFloat)currentScore;
@end
@interface XKStarRateView : UIView
/// 是否顯示動(dòng)畫(默認(rèn)為NO)
@property (nonatomic, assign) BOOL isAnimation;
/// 評(píng)分樣式 (XKWholeStarStyle 整星評(píng)論 XKHalfStarStyle 半星評(píng)論 XKIncompleteStarStyle 不完整星評(píng)論)
@property (nonatomic, assign) XKStarRateStyle rateStyle;
/// 代理
@property (nonatomic, weak) id<XKStarRateViewDelegate> delegate;
/**
初始化方法
@param frame 控件frame
@param numberOfStars 星星數(shù)量
@param rateStyle 評(píng)分樣式 (XKWholeStarStyle 整星評(píng)論 XKHalfStarStyle 半星評(píng)論 XKIncompleteStarStyle 不完整星評(píng)論)
@param isAnimation 是否動(dòng)畫
@param delegate 代理
@return XKStarRateView
*/
- (instancetype)initWithFrame:(CGRect)frame
numberOfStars:(NSInteger)numberOfStars
rateStyle:(XKStarRateStyle)rateStyle
isAnination:(BOOL)isAnimation
delegate:(id<XKStarRateViewDelegate>)delegate;
/**
初始化方法
@param frame 控件frame
@param starRateSelectedBlock 點(diǎn)擊星星的回調(diào)
@return XKStarRateView
*/
- (instancetype)initWithFrame:(CGRect)frame
starRateSelectedBlock:(XKStarRateSelectedBlock)starRateSelectedBlock;
/**
初始化方法
@param frame 控件frame
@param numberOfStars 星星數(shù)量
@param rateStyle 評(píng)分樣式 (XKWholeStarStyle 整星評(píng)論 XKHalfStarStyle 半星評(píng)論 XKIncompleteStarStyle 不完整星評(píng)論)
@param isAnimation 是否動(dòng)畫
@param starRateSelectedBlock 點(diǎn)擊星星的回調(diào)
@return XKStarRateView
*/
- (instancetype)initWithFrame:(CGRect)frame
numberOfStars:(NSInteger)numberOfStars
rateStyle:(XKStarRateStyle)rateStyle
isAnination:(BOOL)isAnimation
starRateSelectedBlock:(XKStarRateSelectedBlock)starRateSelectedBlock;
@end
這里提供了三種樣式供選擇左腔,整星,半星捅儒,任意星液样。以及代理和Block兩種方式獲得當(dāng)前控件顯示所對(duì)應(yīng)的分?jǐn)?shù)。還提供了一個(gè)可選動(dòng)畫的屬性巧还。
在實(shí)現(xiàn)的過(guò)程中鞭莽,首先定義一些必要的屬性參數(shù)。
typedef void(^completeBlock)(CGFloat currentScore);
@interface XKStarRateView ()
@property (nonatomic, strong) UIView *foregroundStarView;
@property (nonatomic, strong) UIView *backgroundStarView;
@property (nonatomic, assign) NSInteger numberOfStars;
@property (nonatomic, assign) CGFloat currentScore;
@property (nonatomic, strong) completeBlock complete;
@end
接下來(lái)聲明一個(gè)方法創(chuàng)建需要的視圖
/**
根據(jù)圖片名稱創(chuàng)建StarView
@param imageName 圖片名稱
*/
- (UIView *)createStarViewWithImageName:(NSString *)imageName {
UIView *view = [[UIView alloc] initWithFrame:self.bounds];
view.clipsToBounds = YES;
view.backgroundColor = [UIColor clearColor];
for (NSInteger i = 0; i < self.numberOfStars; i ++) {
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:imageName]];
imageView.frame = CGRectMake(i * self.bounds.size.width / self.numberOfStars, 0, self.bounds.size.width / self.numberOfStars, self.bounds.size.height);
imageView.contentMode = UIViewContentModeScaleAspectFit;
[view addSubview:imageView];
}
return view;
}
初始化視圖
/**
初始化評(píng)論視圖
*/
- (void)initStarView {
self.foregroundStarView = [self createStarViewWithImageName:NormalImageName];
self.backgroundStarView = [self createStarViewWithImageName:SelectedImageName];
self.foregroundStarView.frame = CGRectMake(0, 0, self.bounds.size.width * _currentScore / self.numberOfStars, self.bounds.size.height);
[self addSubview:self.backgroundStarView];
[self addSubview:self.foregroundStarView];
}
視圖什么的都創(chuàng)建好了麸祷,接著處理事件澎怒,這里的點(diǎn)擊事件和滑動(dòng)事件我并沒(méi)有去給視圖添加手勢(shì),而是直接放到視圖的Touch事件中去處理的阶牍。
#pragma mark -- touch事件處理
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[event touchesForView:self];
NSSet *allTouches = [event allTouches];
UITouch *touch = [allTouches anyObject];
CGPoint point = [touch locationInView:[touch view]];
CGFloat offset = point.x;
CGFloat realStarScore = offset / (self.bounds.size.width / self.numberOfStars);
[self handleRealStarScore:realStarScore];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[event touchesForView:self];
NSSet *allTouches = [event allTouches];
UITouch *touch = [allTouches anyObject];
CGPoint point = [touch locationInView:[touch view]];
CGFloat offset = point.x;
CGFloat realStarScore = offset / (self.bounds.size.width / self.numberOfStars);
[self handleRealStarScore:realStarScore];
}
根據(jù)偏移量計(jì)算最終的評(píng)分喷面,這里根據(jù)選擇不同的樣式計(jì)算方法有不一樣的地方星瘾。
/**
根據(jù)偏移量計(jì)算最終的評(píng)分
@param realStarScore 真實(shí)的偏移量
*/
- (void)handleRealStarScore:(CGFloat)realStarScore {
switch (_rateStyle) {
case XKWholeStarStyle:
self.currentScore = ceilf(realStarScore);
break;
case XKHalfStarStyle:
self.currentScore = roundf(realStarScore) > realStarScore ? ceilf(realStarScore) : (ceilf(realStarScore) - 0.5);
break;
case XKIncompleteStarStyle:
self.currentScore = realStarScore;
break;
default:
break;
}
}
這里主要的就是兩個(gè)C語(yǔ)言函數(shù)的使用。
-
round
:如果參數(shù)是小數(shù)惧辈,則求本身的四舍五入琳状。 -
ceilf
:如果參數(shù)是小數(shù),則向上取整盒齿。
拿到當(dāng)前的偏移量計(jì)算出來(lái)的分?jǐn)?shù)之后念逞,在setter方法中將這個(gè)值傳出去。并且刷新視圖县昂。
- (void)setCurrentScore:(CGFloat)currentScore {
if (_currentScore == currentScore) {
return;
}
if (currentScore < 0) {
_currentScore = 0;
} else if (currentScore > _numberOfStars) {
_currentScore = _numberOfStars;
} else {
_currentScore = currentScore;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(starRateView:currentScore:)]) {
[self.delegate starRateView:self currentScore:_currentScore];
}
if (self.complete) {
_complete(_currentScore);
}
[self setNeedsLayout];
}
在 - (void)layoutSubviews
方法中肮柜,去改變 foregroundStarView
的frame即可。
- (void)layoutSubviews {
[super layoutSubviews];
__weak XKStarRateView *weakSelf = self;
CGFloat animationTimeInterval = self.isAnimation ? 0.2 : 0;
[UIView animateWithDuration:animationTimeInterval animations:^{
weakSelf.foregroundStarView.frame = CGRectMake(0, 0, weakSelf.bounds.size.width * weakSelf.currentScore / self.numberOfStars, weakSelf.bounds.size.height);
}];
}
到這里倒彰,基本的功能就做完了审洞,構(gòu)造方法中只需要初始化部分參數(shù)就好了。運(yùn)行程序待讳,發(fā)現(xiàn)有一個(gè)問(wèn)題芒澜,就是當(dāng)你選擇了一個(gè)分?jǐn)?shù)之后,在 XKWholeStarStyle
和 XKHalfStarStyle
兩種樣式中创淡,始終沒(méi)有辦法將評(píng)分設(shè)置為0痴晦,問(wèn)題出在 - (void)handleRealStarScore:(CGFloat)realStarScore
這個(gè)方法中使用到的兩個(gè)C語(yǔ)言函數(shù)。這里最后也沒(méi)有去在想其他的方式來(lái)實(shí)現(xiàn)琳彩。在這個(gè)方法中手動(dòng)的設(shè)置了一下誊酌,當(dāng) realStarScore
比0.5還小的時(shí)候,就直接讓分?jǐn)?shù)為0露乏。簡(jiǎn)單粗暴碧浊。
/**
根據(jù)偏移量計(jì)算最終的評(píng)分
@param realStarScore 真實(shí)的偏移量
*/
- (void)handleRealStarScore:(CGFloat)realStarScore {
switch (_rateStyle) {
case XKWholeStarStyle:
if (realStarScore < 0.5) {
self.currentScore = 0;
} else {
self.currentScore = ceilf(realStarScore);
}
break;
case XKHalfStarStyle:
if (realStarScore < 0.4) {
self.currentScore = 0;
} else {
self.currentScore = roundf(realStarScore) > realStarScore ? ceilf(realStarScore) : (ceilf(realStarScore) - 0.5);
}
break;
case XKIncompleteStarStyle:
self.currentScore = realStarScore;
break;
default:
break;
}
}
最后,整個(gè)實(shí)現(xiàn)文件的代碼如下
#import "XKStarRateView.h"
#define NormalImageName @"b27_icon_star_yellow"
#define SelectedImageName @"b27_icon_star_gray"
typedef void(^completeBlock)(CGFloat currentScore);
@interface XKStarRateView ()
@property (nonatomic, strong) UIView *foregroundStarView;
@property (nonatomic, strong) UIView *backgroundStarView;
@property (nonatomic, assign) NSInteger numberOfStars;
@property (nonatomic, assign) CGFloat currentScore;
@property (nonatomic, strong) completeBlock complete;
@end
@implementation XKStarRateView
#pragma mark -- 構(gòu)造方法
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
_numberOfStars = 5;
_rateStyle = XKWholeStarStyle;
[self initStarView];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
numberOfStars:(NSInteger)numberOfStars
rateStyle:(XKStarRateStyle)rateStyle
isAnination:(BOOL)isAnimation
delegate:(id<XKStarRateViewDelegate>)delegate {
if (self = [super initWithFrame:frame]) {
_numberOfStars = numberOfStars;
_rateStyle = rateStyle;
_isAnimation = isAnimation;
_delegate = delegate;
[self initStarView];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
starRateSelectedBlock:(XKStarRateSelectedBlock)starRateSelectedBlock{
if (self = [super initWithFrame:frame]) {
_numberOfStars = 5;
_rateStyle = XKWholeStarStyle;
_complete = ^(CGFloat currentScore){
starRateSelectedBlock(currentScore);
};
[self initStarView];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
numberOfStars:(NSInteger)numberOfStars
rateStyle:(XKStarRateStyle)rateStyle
isAnination:(BOOL)isAnimation
starRateSelectedBlock:(XKStarRateSelectedBlock)starRateSelectedBlock {
if (self = [super initWithFrame:frame]) {
_numberOfStars = numberOfStars;
_rateStyle = rateStyle;
_isAnimation = isAnimation;
_complete = ^(CGFloat currentScore) {
starRateSelectedBlock(currentScore);
};
[self initStarView];
}
return self;
}
#pragma mark -- 私有方法
/**
初始化評(píng)論視圖
*/
- (void)initStarView {
self.foregroundStarView = [self createStarViewWithImageName:NormalImageName];
self.backgroundStarView = [self createStarViewWithImageName:SelectedImageName];
self.foregroundStarView.frame = CGRectMake(0, 0, self.bounds.size.width * _currentScore / self.numberOfStars, self.bounds.size.height);
[self addSubview:self.backgroundStarView];
[self addSubview:self.foregroundStarView];
}
/**
根據(jù)圖片名稱創(chuàng)建StarView
@param imageName 圖片名稱
*/
- (UIView *)createStarViewWithImageName:(NSString *)imageName {
UIView *view = [[UIView alloc] initWithFrame:self.bounds];
view.clipsToBounds = YES;
view.backgroundColor = [UIColor clearColor];
for (NSInteger i = 0; i < self.numberOfStars; i ++) {
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:imageName]];
imageView.frame = CGRectMake(i * self.bounds.size.width / self.numberOfStars, 0, self.bounds.size.width / self.numberOfStars, self.bounds.size.height);
imageView.contentMode = UIViewContentModeScaleAspectFit;
[view addSubview:imageView];
}
return view;
}
/**
根據(jù)偏移量計(jì)算最終的評(píng)分
@param realStarScore 真實(shí)的偏移量
*/
- (void)handleRealStarScore:(CGFloat)realStarScore {
switch (_rateStyle) {
case XKWholeStarStyle:
if (realStarScore < 0.5) {
self.currentScore = 0;
} else {
self.currentScore = ceilf(realStarScore);
}
break;
case XKHalfStarStyle:
if (realStarScore < 0.4) {
self.currentScore = 0;
} else {
self.currentScore = roundf(realStarScore) > realStarScore ? ceilf(realStarScore) : (ceilf(realStarScore) - 0.5);
}
break;
case XKIncompleteStarStyle:
self.currentScore = realStarScore;
break;
default:
break;
}
}
#pragma mark -- touch事件處理
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[event touchesForView:self];
NSSet *allTouches = [event allTouches];
UITouch *touch = [allTouches anyObject];
CGPoint point = [touch locationInView:[touch view]];
CGFloat offset = point.x;
CGFloat realStarScore = offset / (self.bounds.size.width / self.numberOfStars);
[self handleRealStarScore:realStarScore];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[event touchesForView:self];
NSSet *allTouches = [event allTouches];
UITouch *touch = [allTouches anyObject];
CGPoint point = [touch locationInView:[touch view]];
CGFloat offset = point.x;
CGFloat realStarScore = offset / (self.bounds.size.width / self.numberOfStars);
[self handleRealStarScore:realStarScore];
}
- (void)layoutSubviews {
[super layoutSubviews];
__weak XKStarRateView *weakSelf = self;
CGFloat animationTimeInterval = self.isAnimation ? 0.2 : 0;
[UIView animateWithDuration:animationTimeInterval animations:^{
weakSelf.foregroundStarView.frame = CGRectMake(0, 0, weakSelf.bounds.size.width * weakSelf.currentScore/self.numberOfStars, weakSelf.bounds.size.height);
}];
}
#pragma mark -- setter方法
- (void)setCurrentScore:(CGFloat)currentScore {
if (_currentScore == currentScore) {
return;
}
if (currentScore < 0) {
_currentScore = 0;
} else if (currentScore > _numberOfStars) {
_currentScore = _numberOfStars;
} else {
_currentScore = currentScore;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(starRateView:currentScore:)]) {
[self.delegate starRateView:self currentScore:_currentScore];
}
if (self.complete) {
_complete(_currentScore);
}
[self setNeedsLayout];
}
@end
全部的代碼都在這里了瘟仿。同樣的箱锐,需要的朋友可以點(diǎn)擊這里下載Demo工程。如果在使用過(guò)程中發(fā)現(xiàn)問(wèn)題歡迎留言提出劳较。謝謝驹止!
特別感謝
在代碼封裝的過(guò)程中有參考這里的代碼 在此感謝。