iOS- 非整星的評分控件(支持小數(shù))

現(xiàn)在很多應(yīng)用都有評分功能.當(dāng)然我們項目也不例外,有了訂單就有訂單評論,訂單評論里就有星級評分控件了! 一般來說, 都是簡單整星的評價, 但是也有奇葩的小數(shù)星評價.

一. 簡單整星評價##

實現(xiàn)步驟:
1.創(chuàng)建 imageView, 用來改變星級圖片.
2.通過手勢來區(qū)分點擊到的位置.
3.通過位置判斷 imageView 顯示的圖片(一般都是5顆星評價,根據(jù)星星點亮顆數(shù)進(jìn)行命名:score1~score5)

F77FFF9A-F84F-420E-B9ED-702FE5E15C48.jpg

現(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)步驟:

  1. 初始化單個星星的實現(xiàn), 按百分比裁剪圖片改變星星整體圖片(一張灰色暗色星星圖, 一張高亮色星星圖).
  2. 初始化星星按鈕的布局, 通過觸摸事件響應(yīng)區(qū)分點擊到的位置, 展現(xiàn)整體評價效果.
  3. 對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;
}

這時候測試效果如下:


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末菜拓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子笛厦,更是在濱河造成了極大的恐慌纳鼎,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裳凸,死亡現(xiàn)場離奇詭異贱鄙,居然都是意外死亡,警方通過查閱死者的電腦和手機登舞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門贰逾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悬荣,“玉大人菠秒,你說我怎么就攤上這事÷扔兀” “怎么了践叠?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嚼蚀。 經(jīng)常有香客問我禁灼,道長,這世上最難降的妖魔是什么轿曙? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任僻孝,我火速辦了婚禮,結(jié)果婚禮上穿铆,老公的妹妹穿的比我還像新娘。我一直安慰自己斋荞,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布凤优。 她就那樣靜靜地躺著,像睡著了一般蜈彼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上幸逆,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機與錄音秉颗,去河邊找鬼痢毒。 笑死,一個胖子當(dāng)著我的面吹牛蚕甥,可吹牛的內(nèi)容都是我干的哪替。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼菇怀,長吁一口氣:“原來是場噩夢啊……” “哼凭舶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起爱沟,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤帅霜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后呼伸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體身冀,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年括享,在試婚紗的時候發(fā)現(xiàn)自己被綠了搂根。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡铃辖,死狀恐怖剩愧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情娇斩,我是刑警寧澤仁卷,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布穴翩,位于F島的核電站,受9級特大地震影響锦积,放射性物質(zhì)發(fā)生泄漏藏否。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一充包、第九天 我趴在偏房一處隱蔽的房頂上張望副签。 院中可真熱鬧,春花似錦基矮、人聲如沸淆储。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽本砰。三九已至,卻和暖如春钢悲,著一層夾襖步出監(jiān)牢的瞬間点额,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工莺琳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留还棱,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓惭等,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辞做。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容