現(xiàn)在很多應(yīng)用都有評分功能.當(dāng)然我們項目也不例外,有了訂單就有訂單評論,訂單評論里就有星級評分控件了! 一般來說, 都是簡單整星的評價, 但是也有奇葩的小數(shù)星評價.
一. 簡單整星評價##
實現(xiàn)步驟:
1.創(chuàng)建 imageView, 用來改變星級圖片.
2.通過手勢來區(qū)分點擊到的位置.
3.通過位置判斷 imageView 顯示的圖片(一般都是5顆星評價,根據(jù)星星點亮顆數(shù)進(jìn)行命名:score1~score5)
現(xiàn)把項目中關(guān)鍵代碼整理如下:
@property (nonatomic, weak) UIImageView *starImage;
@property (nonatomic, copy) NSString *score;
// 設(shè)置starImage
UIImageView *starImage = [[UIImageView alloc] init];
starImage.userInteractionEnabled = YES;
starImage.backgroundColor = [UIColor clearColor];
starImage.frame = CGRectMake(CGRectGetMaxX(label.frame) + 15, 0, 80, 14);
starImage.image = [UIImage imageNamed:@"score0"];
[bgView addSubview:starImage];
self.starImage = starImage;
// 添加星級評價手勢
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scoreClick:)];
[starImage addGestureRecognizer:tap];
- (void)scoreClick:(UITapGestureRecognizer *)tap {
CGPoint point = [tap locationInView:tap.view];
QTXLog(@"%@", NSStringFromCGPoint(point));
if (point.x < self.starImage.width / 5) {
self.starImage.image = [UIImage imageNamed:@"score1"]; // 展示點亮圖片
self.score = @"1"; // 評論分?jǐn)?shù)
} else if (point.x < self.starImage.width / 5 * 2) {
self.starImage.image = [UIImage imageNamed:@"score2"];
self.score = @"2";
} else if (point.x < self.starImage.width / 5 * 3) {
self.starImage.image = [UIImage imageNamed:@"score3"];
self.score = @"3";
} else if (point.x < self.starImage.width / 5 * 4) {
self.starImage.image = [UIImage imageNamed:@"score4"];
self.score = @"4";
} else if (point.x < self.starImage.width) {
self.starImage.image = [UIImage imageNamed:@"score5"];
self.score = @"5";
}
}
二. 非整星精評價##
之前我們需求并沒有那么細(xì),就評論整顆星星評價, 但是最近有新需求半顆星星,這邊就先整理下. 同時這次不僅支持非整星精評價, 也支持簡單整星評價.
實現(xiàn)步驟:
- 初始化單個星星的實現(xiàn), 按百分比裁剪圖片改變星星整體圖片(一張灰色暗色星星圖, 一張高亮色星星圖).
- 初始化星星按鈕的布局, 通過觸摸事件響應(yīng)區(qū)分點擊到的位置, 展現(xiàn)整體評價效果.
- 對score分?jǐn)?shù)屬性和allowFraction是否小數(shù)整顆星屬性露出展現(xiàn),在當(dāng)前控制器里使用代理做分?jǐn)?shù)改變后的操作, 初始化星星評價組件。
Step1. 初始化單個星星的實現(xiàn)
單個星星的實現(xiàn), 要考慮到全灰狀態(tài),全亮狀態(tài),百分比高亮狀態(tài). 這里用UIButton來實現(xiàn), 先把圖片縮放到和Button大小一樣, 可以根據(jù)百分比將圖像進(jìn)行裁剪设褐,讓新圖像的寬度只有百分比所占的整個圖像的寬度. 這里自定義ZLStarButton
按百分比裁剪圖片
+ (UIImage *)cutImage:(UIImage *)image fraction:(CGFloat)fractonPart{
CGFloat width = image.size.width * fractonPart * image.scale;
CGRect newFrame = CGRectMake(0, 0, width , image.size.height * image.scale);
CGImageRef resultImage = CGImageCreateWithImageInRect(image.CGImage, newFrame);
UIImage *result = [UIImage imageWithCGImage:resultImage scale:image.scale orientation:image.imageOrientation];
CGImageRelease(resultImage);
return result;
}
暗色置灰的狀態(tài)設(shè)置為UIButton的正常普通狀態(tài)
- (void)setDarkImg:(UIImage *)darkImg{
_darkImg = [ZLStarButton reSizeImage:darkImg toSize:self.frame.size];
[self setImage:_darkImg forState:UIControlStateNormal];
}
全高亮的狀態(tài)設(shè)置為UIButton的點擊狀態(tài)
- (void)setBrightImg:(UIImage *)brightImg{
_brightImg = [ZLStarButton reSizeImage:brightImg toSize:self.frame.size];
[self setImage:_brightImg forState:UIControlStateSelected];
}
在BackgroundImage設(shè)置為灰色的星星圖像,設(shè)置為Button的高亮狀態(tài)
- (void)setFractionPart:(CGFloat)fractionPart{
if (fractionPart == 0) {
return;
}
UIImage *image = [ZLStarButton cutImage:self.brightImg fraction:fractionPart];
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
self.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
self.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
[self setImage:image forState:UIControlStateHighlighted];
[self setBackgroundImage:self.darkImg forState:UIControlStateHighlighted];
self.selected = NO;
self.highlighted = YES;
}
如果點擊到星星的一部分泣刹,把點擊點轉(zhuǎn)換成小數(shù)部分給上層助析。C語言的round函數(shù)可以四舍五入,這里的10代表保留一位小數(shù)椅您。
- (CGFloat)fractionPartOfPoint:(CGPoint)point{
CGFloat fractionPart = (point.x - self.frame.origin.x) / self.frame.size.width;
return round(fractionPart * 10) / 10;
}
Step2. 初始化星星按鈕的布局, 通過觸摸事件ZLStarRatingControl響應(yīng)區(qū)分點擊到的位置, 展現(xiàn)整體評價效果.
根據(jù)星星的數(shù)量逐個添加在視圖上外冀,用UIView的tag來標(biāo)記對應(yīng)的星星按鈕。
- (void)setupView {
for (NSInteger index = 0; index < self.numberOfStars; index++) {
ZLStarButton *starButton = [ZLStarButton.alloc initWithSize:self.starSize];
starButton.tag = index;
starButton.darkImg = self.darkStarImg;
starButton.brightImg = self.brightStarImg;
starButton.userInteractionEnabled = NO;
[self addSubview:starButton];
}
}
計算每個星星的位置及計算間隔和上下邊距并layout
- (void)layoutSubviews {
[super layoutSubviews];
for (NSInteger index = 0; index < self.numberOfStars; index ++) {
ZLStarButton *starButton = [self starForTag:index];
CGFloat starY = (self.frame.size.height - self.starSize.height) / 2;
CGFloat margin = 0;
if (self.numberOfStars > 1) {
margin = (self.frame.size.width - self.starSize.width * self.numberOfStars) / (self.numberOfStars - 1);
}
starButton.frame = CGRectMake((self.starSize.width + margin) * index, starY, self.starSize.width, self.starSize.height);
}
}
根據(jù)tag和根據(jù)點擊的CGPoint找到對應(yīng)的星星按鈕
- (ZLStarButton *)starForPoint:(CGPoint)point {
for (NSInteger i = 0; i < self.numberOfStars; i++) {
ZLStarButton *starButton = [self starForTag:i];
if (CGRectContainsPoint(starButton.frame, point)) {
return starButton;
}
}
return nil;
}
- (ZLStarButton *)starForTag:(NSInteger)tag {
__block UIView *target;
[self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.tag == tag) {
target = obj;
*stop = YES;
}
}];
return (ZLStarButton *)target;
}
處理觸摸事件:
從最后一個星星到某個星星從右到左依次熄滅
- (void)starsDownToIndex:(NSInteger)index {
for (NSInteger i = self.numberOfStars; i > index; --i) {
ZLStarButton *starButton = [self starForTag:i];
starButton.selected = NO;
starButton.highlighted = NO;
}
}
從第一個到某個星星開始從左到右依次點亮
- (void)starsUpToIndex:(NSInteger)index {
for (NSInteger i = 0; i <= index; i++) {
ZLStarButton *starButton = [self starForTag:i];
starButton.selected = YES;
starButton.highlighted = NO;
}
}
ZLStarRatingControl.h 文件設(shè)置一個評分的屬性score襟沮,重寫其setter方法. allowFraction锥惋,用來判斷組件是否需要分?jǐn)?shù)表示. 判斷評分的整數(shù)部分是否已經(jīng)亮著昌腰,亮著那么說明從左到右最后一個亮著的右邊,反之在左邊膀跌,分別調(diào)用從右到左依次熄滅遭商,或從左到右依次點亮的方法,最后再設(shè)置分?jǐn)?shù)部分.
- (void)setScore:(CGFloat)score{
if (_score == score) {
return;
}
_score = score;
NSInteger index = floor(score); // floor函數(shù)是取最大整數(shù)捅伤,相當(dāng)于直接去除小數(shù)點后面的數(shù)字劫流。
CGFloat fractionPart = score - index;
if (!self.isAllowFraction || fractionPart == 0) { // 不允許小數(shù)半顆星(取整顆星)
index -= 1;
}
ZLStarButton *starButton = [self starForTag:index];
if (starButton.selected || score == 0) { // 當(dāng)選中或者分?jǐn)?shù)為0則依次熄滅
[self starsDownToIndex:index];
} else { // 當(dāng)未選中則依次點亮
[self starsUpToIndex:index];
}
starButton.fractionPart = fractionPart;
}
這里面用到UIControl的四個方法:
開始點擊的時候的事件處理: 點擊時候只要確定點擊的星星,確定其小數(shù)部分丛忆,然后調(diào)用評分屬性score的setter方法就好了.
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint point = [touch locationInView:self];
ZLStarButton *pressedStar = [self starForPoint:point];
if (pressedStar) {
self.currentStar = pressedStar;
NSInteger index = pressedStar.tag;
CGFloat fractionPart = 1;
if (self.isAllowFraction) { // 允許小數(shù)半顆星
fractionPart = [pressedStar fractionPartOfPoint:point];
}
self.score = index + fractionPart;
}
return YES;
}
手指未抬起在屏幕上繼續(xù)移動的事件處理: 移動處理除了和點擊一樣的判斷邏輯祠汇,還要注意手指移開了星星之外的地方,分為所在星星的左邊(當(dāng)前星星熄滅)熄诡,右邊(當(dāng)前星星完全點亮)兩種狀態(tài).
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint point = [touch locationInView:self];
ZLStarButton *pressedStar = [self starForPoint:point];
if (pressedStar) {
self.currentStar = pressedStar;
NSInteger index = pressedStar.tag;
CGFloat fractionPart = 1;
if (self.isAllowFraction) { // 允許小數(shù)半顆星
fractionPart = [pressedStar fractionPartOfPoint:point];
}
self.score = index + fractionPart;
} else {
if (point.x < self.currentStar.frame.origin.x) {
self.score = self.currentStar.tag;
} else if (point.x > (self.currentStar.frame.origin.x + self.currentStar.frame.size.width)){
self.score = self.currentStar.tag + 1;
}
}
return YES;
}
離開屏幕處理
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
[super endTrackingWithTouch:touch withEvent:event];
if ([self.delegate respondsToSelector:@selector(starsControl:didChangeScore:)]) {
[self.delegate starsControl:self didChangeScore:self.score];
}
}
因為別的特殊情況事件被結(jié)束取消的處理
- (void)cancelTrackingWithEvent:(UIEvent *)event {
[super cancelTrackingWithEvent:event];
if ([self.delegate respondsToSelector:@selector(starsControl:didChangeScore:)]) {
[self.delegate starsControl:self didChangeScore:self.score];
}
}
完成觸摸操作時可很,設(shè)置一個代理, 可以用一個回調(diào)將當(dāng)前的評分傳給外界。
@protocol ZLStarRatingControlDelegate <NSObject>
@optional
/**
* 回調(diào)改變星星評價后的分?jǐn)?shù)
@param starsControl 星星組件
@param score 分?jǐn)?shù)
*/
- (void)starsControl:(ZLStarRatingControl *)starsControl didChangeScore:(CGFloat)score;
@end
初始化星星組件方法
// 初始化一個星星評價組件
- (instancetype)initWithFrame:(CGRect)frame
stars:(NSInteger)number
starSize:(CGSize)size
darkStarImg:(UIImage *)darkImg
brightStarImg:(UIImage *)brightImg{
if (self = [super initWithFrame:frame]) {
_numberOfStars = number;
_darkStarImg = darkImg;
_brightStarImg = brightImg;
_starSize = size;
_allowFraction = NO;
self.clipsToBounds = YES;
self.backgroundColor = [UIColor clearColor];
[self setupView];
}
return self;
}
// 初始化一個星星評價組件,默認(rèn)5顆星凰浮,默認(rèn)星星的長寬為frame的高度
- (instancetype)initWithFrame:(CGRect)frame
darkStarImg:(UIImage *)darkImg
brightStarImg:(UIImage *)brightImg{
return [self initWithFrame:frame stars:5 starSize:CGSizeMake(frame.size.height, frame.size.height) darkStarImg:darkImg brightStarImg:brightImg];
}
// 初始化一個星星評價組件我抠,默認(rèn)星星的長寬為frame的高度
- (instancetype)initWithFrame:(CGRect)frame
stars:(NSInteger)number
darkStarImg:(UIImage *)darkImg
brightStarImg:(UIImage *)brightImg{
return [self initWithFrame:frame stars:number starSize:CGSizeMake(frame.size.height, frame.size.height) darkStarImg:darkImg brightStarImg:brightImg];
}
Step3. 在當(dāng)前控制器里使用代理做分?jǐn)?shù)改變后的操作, 初始化星星評價組件.
使用代理做分?jǐn)?shù)改變后的操作
- (void)starsControl:(ZLStarRatingControl *)starsControl didChangeScore:(CGFloat)score{
self.soreLabel.text = [NSString stringWithFormat:@"%.1f", score];
}
初始化星星評價組件時, 如果不需要精確只是整顆星評分,則不需要設(shè)置starsControl.allowFraction = YES;
- (ZLStarRatingControl *)starsControl{
if (!_starsControl) {
// 初始化一個星星評價組件
// _starsControl = [ZLStarRatingControl.alloc initWithFrame:CGRectMake(50, 100, self.view.frame.size.width - 50 * 2, 50) stars:4 starSize:CGSizeMake(50, 50) darkStarImg:[UIImage imageNamed:@"dark"] brightStarImg:[UIImage imageNamed:@"bright"]];
// 初始化一個星星評價組件,默認(rèn)星星的長寬為frame的高度
// _starsControl = [ZLStarRatingControl.alloc initWithFrame:CGRectMake(50, 100, self.view.frame.size.width - 50 * 2, 50) stars:6 darkStarImg:[UIImage imageNamed:@"dark"] brightStarImg:[UIImage imageNamed:@"bright"]];
// 初始化一個星星評價組件,默認(rèn)5顆星袜茧,默認(rèn)星星的長寬為frame的高度
_starsControl = [ZLStarRatingControl.alloc initWithFrame:CGRectMake(50, 100, self.view.frame.size.width - 50 * 2, 50) darkStarImg:[UIImage imageNamed:@"dark"] brightStarImg:[UIImage imageNamed:@"bright"]];
_starsControl.delegate = self;
_starsControl.allowFraction = YES;
// 測試顯示
_starsControl.score = 3.5f;
}
return _starsControl;
}
這時候測試效果如下: