前几天做项目时需要用到一个星级评分的展示以及通过点选来评价的功能,所以就写了一个自定义的小框架来实现,废话不多说,直接进入正题:
先看如何使用与效果展示
Demo地址:https://github.com/WallaceYou/YYStarView
如果觉得有帮助到您,给颗星鼓励一下哦
如果您只想使用,而不想知道怎么实现的,那后面的就不需要看啦~
这里主要讲一下如何实现:
- 在.h文件中先定义一些公开属性:
typedef NS_ENUM(NSUInteger, StarViewType) {
StarViewTypeShow = 1,
StarViewTypeSelect = 2,
};
@interface YYStarView : UIView
/** 亮色星图片名称,如果不设置,则使用默认图片 */
@property (nonatomic, copy) NSString *starBrightImageName;
/** 暗色星图片名称,如果不设置,则使用默认图片 */
@property (nonatomic, copy) NSString *starDarkImageName;
/** 星与星之间的间距,如果不设置,则默认为0 */
@property (nonatomic, assign) CGFloat starSpacing;
/** 每颗星的大小,如果不设置,则按照图片大小自适应 */
@property (nonatomic, assign) CGSize starSize;
/** 星的个数,如果不设置,则默认为5颗 */
@property (nonatomic, assign) NSInteger starCount;
/** starView的类型,如果不设置,默认为Select类型 */
@property (nonatomic, assign) StarViewType type;
/** 星级评分,如果不设置,默认为0颗星 */
@property (nonatomic, assign) CGFloat starScore;
/** 点击星回调的block */
@property (nonatomic, copy) void(^starClick)(void);
@end
- .m文件中:
1、首先在初始化的时候创建所有子视图:
- (void)awakeFromNib {
[super awakeFromNib];
[self setUpSubviews];//创建所有子视图
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setUpSubviews];//创建所有子视图
}
return self;
}
2、创建好子视图后,默认全部是暗星:
- (void)setUpSubviews {
if (self.starCount <= 0) {
_starCount = 5;
}
for (int i = 0; i < self.starCount; i++) {
UIButton *starBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[starBtn addTarget:self action:@selector(starBtnClick:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:starBtn];
[self.buttons addObject:starBtn];
}
self.starScore = 0;//默认设置显示亮星为0,即全部显示暗星
}
3、在设置starScore的时候,先做区间判断,如果小于0则等于0,大于星的个数时则等于星的个数,设置完后再设置图片:
- (void)setStarScore:(CGFloat)starScore {
_starScore = floorf(starScore);//先向下取整
if (starScore < 0) {
_starScore = 0;
}
if (starScore > self.starCount) {
_starScore = self.starCount;
}
[self setStarImage];//设置图片
}
4、设置图片,如果用户有设置,则用,没有,则用默认的,同时支持亮星与暗星的高亮图片的设置(只需将高亮图片的名字改为普通图片后面加"_highlighted"即可自动设置):
- (void)setStarImage {
NSBundle *resourcesBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"YYStarView" ofType:@"bundle"]];
if (!self.starBrightImageName) {
self.starBrightImageName = @"yy_star_bright";
}
if (!self.starDarkImageName) {
self.starDarkImageName = @"yy_star_dark";
}
//1.首先准备星要显示的图片
UIImage *starBrightImage = [UIImage imageNamed:self.starBrightImageName];
UIImage *starDarkImage = [UIImage imageNamed:self.starDarkImageName];
UIImage *starBrightHighlightedImage = [UIImage imageNamed:[self.starBrightImageName stringByAppendingString:@"_highlighed"]];
UIImage *starDarkHighlightedImage = [UIImage imageNamed:[self.starDarkImageName stringByAppendingString:@"_highlighed"]];;
if (starBrightImage == nil) {
starBrightImage = [UIImage imageNamed:@"yy_star_bright" inBundle:resourcesBundle compatibleWithTraitCollection:nil];
starBrightHighlightedImage = nil;
}
if (starDarkImage == nil) {
starDarkImage = [UIImage imageNamed:@"yy_star_dark" inBundle:resourcesBundle compatibleWithTraitCollection:nil];
starDarkHighlightedImage = nil;
}
for (int i = 0; i < self.buttons.count; i++) {
UIButton *starBtn = [self.buttons objectAtIndex:i];
//根据当前的score数来选择显示亮星还是暗星
if (i < self.starScore) {
[starBtn setBackgroundImage:starBrightImage forState:UIControlStateNormal];
[starBtn setBackgroundImage:starBrightHighlightedImage forState:UIControlStateHighlighted];
} else {
[starBtn setBackgroundImage:starDarkImage forState:UIControlStateNormal];
[starBtn setBackgroundImage:starDarkHighlightedImage forState:UIControlStateHighlighted];
}
}
}
5、在layoutSubviews中使用VFL布局,因为要做到不依赖其他框架,所以使用VFL布局:
- (void)layoutSubviews {
[super layoutSubviews];
//1.首先,先删除所有约束
[self removeConstraints:self.constraints];
//2.然后再添加新的约束
//2.1.准备VFL中子视图与实际对象引用之间的关联关系的字典
NSMutableDictionary *diction = [NSMutableDictionary new];
//2.2.准备水平方向的VFL表达式
NSMutableString *hVFL = [[NSMutableString alloc] initWithString:@"H:|"];
//2.3.准备竖直方向的VFL表达式
NSMutableString *vVFL = [[NSMutableString alloc] initWithString:@"V:|"];
//2.3.创建一个VFL中的长度参照表
NSDictionary *metrics = @{@"left":@0,@"right":@0,@"top":@0,@"bottom":@0,@"space":@(self.starSpacing)};
for (int i = 0; i < self.buttons.count; i++) {
UIButton *starBtn = [self.buttons objectAtIndex:i];
//2.4.关掉自动翻译功能(必须关闭,否则VFL不生效)
starBtn.translatesAutoresizingMaskIntoConstraints = NO;
//2.5.设置关联关系的字典
[diction setValue:starBtn forKey:[NSString stringWithFormat:@"button%i",i+1]];
//2.6.设置VFL表达式
CGFloat starWidth = self.starSize.width;
CGFloat starHeight = self.starSize.height;
if (i == 0) {
if (starWidth > 0) {//如果有设置starSize,则按照starSize显示
[hVFL appendFormat:@"-left-[button%i(%lf)]",i+1,starWidth];
} else {//如果没有设置starSize,则按照图片大小自适应
[hVFL appendFormat:@"-left-[button%i]",i+1];
}
if (starHeight > 0) {
[vVFL appendFormat:@"-top-[button%i(%lf,",i+1,starHeight];
} else {
[vVFL appendFormat:@"-top-[button%i(",i+1];
}
if (self.buttons.count == 1) {//对于一颗星要做特殊处理
[vVFL deleteCharactersInRange:NSMakeRange(vVFL.length-1, 1)];
if (starWidth > 0) {
[vVFL appendFormat:@")"];
}
[hVFL appendFormat:@"-bottom-|"];
[vVFL appendFormat:@"]-bottom-|"];
}
} else if (i == self.starCount-1) {
[hVFL appendFormat:@"-space-[button%i(button1)]-right-|",i+1];
[vVFL appendFormat:@"button%i)]-bottom-|",i+1];
} else {
[hVFL appendFormat:@"-space-[button%i(button1)]",i+1];
[vVFL appendFormat:@"button%i,",i+1];
}
}
//2.7. 创建约束对象
NSArray *cs1 = [NSLayoutConstraint constraintsWithVisualFormat:hVFL options:NSLayoutFormatAlignAllCenterY metrics:metrics views:diction];
//2.8.添加约束
[self addConstraints:cs1];
//创建竖直方向的约束
NSArray *cs2 = [NSLayoutConstraint constraintsWithVisualFormat:vVFL options:0 metrics:metrics views:diction];
//添加约束到父视图中
[self addConstraints:cs2];
}
注:
将添加约束的操作写在layoutSubviews中,这样在重复赋值时,直接调用[self setNeedsLayout]就可以完成重新布局了,另外,补充几个小知识点:
layoutSubviews的调用时机:
- 被addSubview一定会调用。
- addSubview一定会调用。
- 改变其frame大小,如果只改变x值,则不会调用,其余不管改变谁都会调用。
- 改变其子视图的size大小。
- 旋转屏幕。
- 手动调用setNeedsLayout、layoutIfNeeded。
setNeedsLayout与layoutNeeded的区别:
- setNeedsLayout :标记为需要重新布局,会异步调用layoutIfNeeded刷新布局,不会立即刷新,但layoutSubviews一定会被调用。
- layoutIfNeeded:如果有需要刷新的标记,立即调用layoutSubViews刷新布局,如果没有标记,什么都不做。
6、知道这些以后剩下的就非常简单了,当外部设置了某个属性,就在这个属性的set方法中做该做的事情,比如说:
- 如果图片换掉了就设置图片:
- (void)setStarBrightImageName:(NSString *)starBrightImageName {
_starBrightImageName = starBrightImageName;
[self setStarImage];
}
- (void)setStarDarkImageName:(NSString *)starDarkImageName {
_starDarkImageName = starDarkImageName;
[self setStarImage];
}
- 如果间隙或星大小重新设置了,就重新布局一下:
- (void)setStarSpacing:(CGFloat)starSpacing {
_starSpacing = starSpacing;
[self setNeedsLayout];
}
- (void)setStarSize:(CGSize)starSize {
_starSize = starSize;
[self setNeedsLayout];
}
- 这里需要注意的是如果星的个数重新设置了,仅仅重新布局是不行的,因为整体架构变了,需要重新创建子视图(按钮):
- (void)setStarCount:(NSInteger)starCount {
_starCount = starCount;
//将子视图全部移除
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self.buttons removeAllObjects];
//重新创建
[self setUpSubviews];
}
最后
第一次写文章,希望多多支持哈,如果可以,GitHub跪求一颗星~
以后有好的东西,想法,还是要写出来分享一下的,这样记忆才深刻,对人对己都有好处~