前言
评分功能是我们大家都不陌生的一个功能,现在在很多电商,外卖,餐饮型应用里随处可见,都会在商品结束后评价中有一个星星组件。核心思路就是用uicontrol并自定义实现其中的tracktouch的几个方法。而显示不到一个的星星,比如半个星星的思路是根据分数切割星星的图像并显示其中一部分。
实现后效果如下:
单个星星的实现
对于单个星星的实现,先考虑星星有三个状态,完全置灰状态,完全高亮状态,根据百分比半高亮状态。而我这边用的是uibutton来实现,因为uibutton本身已经有普通,高亮,选择的状态。主要实现的就是百分比高亮状态。我们可以根据百分比将图像进行裁剪,让新图像的宽度只有百分比所占的整个图像的宽度。但是这时候调用setimage,会发现图片处于整个button中间。这是因为uibutton的imageview的contentmode默认是aspectfit的,而aspectfit是默认居中的。contentmode中的uiviewcontentmodeleft,是不会把长边缩放到imageview的边长的。况且,直接改变uibutton里的imageview的contentmode是没有效果的。要通过uicontrol的contentverticalalignment属性(垂直)和contenthorizontalalignment属性(水平)来达到类似imageview的contentmode的效果。但是这两个属性都没有带fit后缀的选项,也就是说并不会根据边长等比缩放。所以需要先把图片缩放到和button大小一致。
之后,我们就可以按百分比裁剪图片
1
2
3
4
5
6
7
8
|
+ (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设置为灰色的星星图像,设置为button的highlighted状态
1
2
3
4
5
6
7
8
9
10
11
12
13
|
- ( 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;
}
|
而全高亮的状态可以设置为uibuttonde点击态
1
2
3
4
|
- ( void )sethighlightedimage:(uiimage *)highlightedimage{
_highlightedimage = [cdzstarbutton resizeimage:highlightedimage tosize:self.frame.size];
[self setimage:_highlightedimage forstate:uicontrolstateselected];
}
|
而点击事件交由给上层的uicontrol去处理,因为点击的时候,除了点击的星星本身,之前的星星也应该处于完全高亮状态。在上层初始化按钮的时候把其userinteractionenabled属性设置为no即可,这样触摸事件就会忽略传递到button去做事件处理,从而从响应者链传递上去交由父视图的uicontrol处理。
如果点击到星星的一半,我们应该把点击点转换成小数部分给上层。c语言的round函数可以四舍五入,这里的10代表保留一位小数,可以根据实际情况保留位数,一般评分很少需要更高精度的。
1
2
3
4
|
- (cgfloat)fractionpartofpoint:(cgpoint)point{
cgfloat fractionpart = (point.x - self.frame.origin.x) / self.frame.size.width;
return round(fractionpart * 10) / 10;
}
|
到这里一个可以显示分数的星星按钮就完成了,接下来就是在上层的uicontrol去处理触摸事件的响应。
uicontrol部分的实现
主要分两块,星星按钮的布局,触摸事件响应。
布局
首先根据星星的数量一个个添加上视图,用uiview的tag来表示对应第几个星星按钮。
1
2
3
4
5
6
7
8
9
10
|
- ( 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; //关闭事件响应,交由uicontrol本身处理
[self addsubview:starbutton];
}
}
|
然后是计算每个星星的位置,计算间隔和上下边距并layout
1
2
3
4
5
6
7
8
9
10
11
12
|
- ( 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);
}
}
|
这时,我们可以加上两个方法去找到对应的星星button,根据tag和根据点击的cgpoint
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
- (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;
}
|
处理触摸事件
我们先加上两个方法。一个是从第一个到某个星星开始从左到右依次点亮,一个是从最后一个星星到某个星星从右到左依次熄灭。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
- ( 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;
}
}
|
然后设置一个评分的属性,重写其setter方法。allowfraction,用来判断组件是否需要分数表示,floor函数是取最大整数,相当于直接去除小数点后面的数字。判断评分的整数部分是否已经亮着,亮着那么说明从左到右最后一个亮着的右边,反之在左边,分别调用从右到左依次熄灭,或从左到右依次点亮的方法,最后再设置分数部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
- ( 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的四个方法
第一个是开始点击的时候的事件处理,第二个是手指未抬起在屏幕上继续移动的事件处理,第三个是离开屏幕,第四个是因为别的特殊情况事件被结束的处理。
点击时候只要确定点击的星星,确定其小数部分,然后调用评分属性的setter方法就好了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
- ( 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;
}
|
移动处理除了和点击一样的判断逻辑,还要注意手指移开了星星之外的地方,分为所在星星的左边(当前星星熄灭),右边(当前星星完全点亮)两种。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
- ( 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 {
//移到了当前星星的左边
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;
}
|
而完成触摸操作时,我们就可以用一个回调将当前的评分传给外界。
1
2
3
4
5
6
7
8
9
10
11
|
@protocol cdzstarscontroldelegate<nsobject>
@optional
/**
回调星星改变后的分数
@param starscontrol 星星组件
@param score 分数
*/
- ( void )starscontrol:(cdzstarscontrol *)starscontrol didchangescore:(cgfloat)score;
@end
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
- ( 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];
}
}
|
封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
- (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;
}
|
开放score属性和allowfraction属性即可。使用起来也很简单。
1
2
3
4
5
|
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里处理分数改变后的操作即可。
源码下载
所有源码和demo
可以直接拿文件去使用或修改,也支持cocoapods.
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:http://www.jianshu.com/p/c5d0ed7f035c