這篇文章主要為大家展示了“iOS如何實(shí)現(xiàn)支持小數(shù)的星星評(píng)分組件”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“iOS如何實(shí)現(xiàn)支持小數(shù)的星星評(píng)分組件”這篇文章吧。
10年積累的做網(wǎng)站、網(wǎng)站設(shè)計(jì)經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問(wèn)題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站設(shè)計(jì)后付款的網(wǎng)站建設(shè)流程,更有賓川免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
實(shí)現(xiàn)后效果如下:
單個(gè)星星的實(shí)現(xiàn)
對(duì)于單個(gè)星星的實(shí)現(xiàn),先考慮星星有三個(gè)狀態(tài),完全置灰狀態(tài),完全高亮狀態(tài),根據(jù)百分比半高亮狀態(tài)。而我這邊用的是UIButton來(lái)實(shí)現(xiàn),因?yàn)閁IButton本身已經(jīng)有普通,高亮,選擇的狀態(tài)。主要實(shí)現(xiàn)的就是百分比高亮狀態(tài)。我們可以根據(jù)百分比將圖像進(jìn)行裁剪,讓新圖像的寬度只有百分比所占的整個(gè)圖像的寬度。但是這時(shí)候調(diào)用setImage,會(huì)發(fā)現(xiàn)圖片處于整個(gè)button中間。這是因?yàn)閁IButton的imageView的contentMode默認(rèn)是AspectFit的,而AspectFit是默認(rèn)居中的。Contentmode中的UIViewContentModeLeft,是不會(huì)把長(zhǎng)邊縮放到imageView的邊長(zhǎng)的。況且,直接改變UIButton里的ImageView的ContentMode是沒(méi)有效果的。要通過(guò)UIControl的contentVerticalAlignment屬性(垂直)和contentHorizontalAlignment屬性(水平)來(lái)達(dá)到類似ImageView的contentMode的效果。但是這兩個(gè)屬性都沒(méi)有帶Fit后綴的選項(xiàng),也就是說(shuō)并不會(huì)根據(jù)邊長(zhǎng)等比縮放。所以需要先把圖片縮放到和Button大小一致。
之后,我們就可以按百分比裁剪圖片
+ (UIImage *)croppedImage:(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; }
并在BackgroundImage設(shè)置為灰色的星星圖像,設(shè)置為Button的highlighted狀態(tài)
- (void)setFractionPart:(CGFloat)fractionPart{ if (fractionPart == 0) { return; } UIImage *image = [CDZStarButton croppedImage:self.highlightedImage fraction:fractionPart]; self.imageView.contentMode = UIViewContentModeScaleAspectFit; self.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; self.contentVerticalAlignment = UIControlContentVerticalAlignmentFill; [self setImage:image forState:UIControlStateHighlighted]; [self setBackgroundImage:self.normalImage forState:UIControlStateHighlighted]; self.selected = NO; self.highlighted = YES; }
而全高亮的狀態(tài)可以設(shè)置為UIButtonde點(diǎn)擊態(tài)
- (void)setHighlightedImage:(UIImage *)highlightedImage{ _highlightedImage = [CDZStarButton reSizeImage:highlightedImage toSize:self.frame.size]; [self setImage:_highlightedImage forState:UIControlStateSelected]; }
而點(diǎn)擊事件交由給上層的UIControl去處理,因?yàn)辄c(diǎn)擊的時(shí)候,除了點(diǎn)擊的星星本身,之前的星星也應(yīng)該處于完全高亮狀態(tài)。在上層初始化按鈕的時(shí)候把其userInteractionEnabled屬性設(shè)置為NO即可,這樣觸摸事件就會(huì)忽略傳遞到Button去做事件處理,從而從響應(yīng)者鏈傳遞上去交由父視圖的UIControl處理。
如果點(diǎn)擊到星星的一半,我們應(yīng)該把點(diǎn)擊點(diǎn)轉(zhuǎn)換成小數(shù)部分給上層。C語(yǔ)言的round函數(shù)可以四舍五入,這里的10代表保留一位小數(shù),可以根據(jù)實(shí)際情況保留位數(shù),一般評(píng)分很少需要更高精度的。
- (CGFloat)fractionPartOfPoint:(CGPoint)point{ CGFloat fractionPart = (point.x - self.frame.origin.x) / self.frame.size.width; return round(fractionPart * 10) / 10; }
到這里一個(gè)可以顯示分?jǐn)?shù)的星星按鈕就完成了,接下來(lái)就是在上層的UIControl去處理觸摸事件的響應(yīng)。
UIControl部分的實(shí)現(xiàn)
主要分兩塊,星星按鈕的布局,觸摸事件響應(yīng)。
布局
首先根據(jù)星星的數(shù)量一個(gè)個(gè)添加上視圖,用UIView的tag來(lái)表示對(duì)應(yīng)第幾個(gè)星星按鈕。
- (void)setupView { for (NSInteger index = 0; index < self.numberOfStars; index++) { CDZStarButton *starButton = [CDZStarButton.alloc initWithSize:self.starSize]; starButton.tag = index; starButton.normalImage = self.normalStarImage; starButton.highlightedImage = self.highlightedStarImage; starButton.userInteractionEnabled = NO;//關(guān)閉事件響應(yīng),交由UIControl本身處理 [self addSubview:starButton]; } }
然后是計(jì)算每個(gè)星星的位置,計(jì)算間隔和上下邊距并layout
- (void)layoutSubviews { [super layoutSubviews]; for (NSInteger index = 0; index < self.numberOfStars; index ++) { CDZStarButton *starButton = [self starForTag:index]; CGFloat newY = (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, newY, self.starSize.width, self.starSize.height); } }
這時(shí),我們可以加上兩個(gè)方法去找到對(duì)應(yīng)的星星button,根據(jù)tag和根據(jù)點(diǎn)擊的CGPoint
- (CDZStarButton *)starForPoint:(CGPoint)point { for (NSInteger i = 0; i < self.numberOfStars; i++) { CDZStarButton *starButton = [self starForTag:i]; if (CGRectContainsPoint(starButton.frame, point)) { return starButton; } } return nil; } - (CDZStarButton *)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 (CDZStarButton *)target; }
處理觸摸事件
我們先加上兩個(gè)方法。一個(gè)是從第一個(gè)到某個(gè)星星開(kāi)始從左到右依次點(diǎn)亮,一個(gè)是從最后一個(gè)星星到某個(gè)星星從右到左依次熄滅。
- (void)starsDownToIndex:(NSInteger)index { for (NSInteger i = self.numberOfStars; i > index; --i) { CDZStarButton *starButton = [self starForTag:i]; starButton.selected = NO; starButton.highlighted = NO; } } - (void)starsUpToIndex:(NSInteger)index { for (NSInteger i = 0; i <= index; i++) { CDZStarButton *starButton = [self starForTag:i]; starButton.selected = YES; starButton.highlighted = NO; } }
然后設(shè)置一個(gè)評(píng)分的屬性,重寫(xiě)其setter方法。allowFraction,用來(lái)判斷組件是否需要分?jǐn)?shù)表示,floor函數(shù)是取最大整數(shù),相當(dāng)于直接去除小數(shù)點(diǎn)后面的數(shù)字。判斷評(píng)分的整數(shù)部分是否已經(jīng)亮著,亮著那么說(shuō)明從左到右最后一個(gè)亮著的右邊,反之在左邊,分別調(diào)用從右到左依次熄滅,或從左到右依次點(diǎn)亮的方法,最后再設(shè)置分?jǐn)?shù)部分。
- (void)setScore:(CGFloat)score{ if (_score == score) { return; } _score = score; NSInteger index = floor(score); CGFloat fractionPart = score - index;; if (!self.isAllowFraction || fractionPart == 0) { index -= 1; } CDZStarButton *starButton = [self starForTag:index]; if (starButton.selected) { [self starsDownToIndex:index]; } else{ [self starsUpToIndex:index]; } starButton.fractionPart = fractionPart; }
主要用到UIControl的四個(gè)方法
第一個(gè)是開(kāi)始點(diǎn)擊的時(shí)候的事件處理,第二個(gè)是手指未抬起在屏幕上繼續(xù)移動(dòng)的事件處理,第三個(gè)是離開(kāi)屏幕,第四個(gè)是因?yàn)閯e的特殊情況事件被結(jié)束的處理。
點(diǎn)擊時(shí)候只要確定點(diǎn)擊的星星,確定其小數(shù)部分,然后調(diào)用評(píng)分屬性的setter方法就好了。
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { CGPoint point = [touch locationInView:self]; CDZStarButton *pressedStar = [self starForPoint:point]; if (pressedStar) { self.currentStar = pressedStar; NSInteger index = pressedStar.tag; CGFloat fractionPart = 1; if (self.isAllowFraction) { fractionPart = [pressedStar fractionPartOfPoint:point]; } self.score = index + fractionPart; } return YES; }
移動(dòng)處理除了和點(diǎn)擊一樣的判斷邏輯,還要注意手指移開(kāi)了星星之外的地方,分為所在星星的左邊(當(dāng)前星星熄滅),右邊(當(dāng)前星星完全點(diǎn)亮)兩種。
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { CGPoint point = [touch locationInView:self]; CDZStarButton *pressedStar = [self starForPoint:point]; if (pressedStar) { self.currentStar = pressedStar; NSInteger index = pressedStar.tag; CGFloat fractionPart = 1; if (self.isAllowFraction) { fractionPart = [pressedStar fractionPartOfPoint:point]; } self.score = index + fractionPart; } else{ //移到了當(dāng)前星星的左邊 if (point.x < self.currentStar.frame.origin.x) { self.score = self.currentStar.tag; } //移到了當(dāng)前星星的右邊 else if (point.x > (self.currentStar.frame.origin.x + self.currentStar.frame.size.width)){ self.score = self.currentStar.tag + 1; } } return YES; }
而完成觸摸操作時(shí),我們就可以用一個(gè)回調(diào)將當(dāng)前的評(píng)分傳給外界。
@protocol CDZStarsControlDelegate@optional /** 回調(diào)星星改變后的分?jǐn)?shù) @param starsControl 星星組件 @param score 分?jǐn)?shù) */ - (void)starsControl:(CDZStarsControl *)starsControl didChangeScore:(CGFloat)score; @end
- (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]; } } - (void)cancelTrackingWithEvent:(UIEvent *)event { [super cancelTrackingWithEvent:event]; if ([self.delegate respondsToSelector:@selector(starsControl:didChangeScore:)]) { [self.delegate starsControl:self didChangeScore:self.score]; } }
封裝
- (instancetype)initWithFrame:(CGRect)frame stars:(NSInteger)number starSize:(CGSize)size noramlStarImage:(UIImage *)normalImage highlightedStarImage:(UIImage *)highlightedImage{ if (self = [super initWithFrame:frame]) { _numberOfStars = number; _normalStarImage = normalImage; _highlightedStarImage = highlightedImage; _starSize = size; _allowFraction = NO; self.clipsToBounds = YES; self.backgroundColor = [UIColor clearColor]; [self setupView]; } return self; }
開(kāi)放score屬性和allowFraction屬性即可。使用起來(lái)也很簡(jiǎn)單。
CDZStarsControl *starsControl = [CDZStarsControl.alloc initWithFrame:CGRectMake(0, 100, self.view.frame.size.width, 50) stars:5 starSize:CGSizeMake(50, 50) noramlStarImage:[UIImage imageNamed:@"star_normal"] highlightedStarImage:[UIImage imageNamed:@"star_highlighted"]]; starsControl.delegate = self; starsControl.allowFraction = YES; starsControl.score = 2.6f; [self.view addSubview:starsControl];
然后在delegate里處理分?jǐn)?shù)改變后的操作即可。
以上是“iOS如何實(shí)現(xiàn)支持小數(shù)的星星評(píng)分組件”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!