Клип CGPath и заполнить двумя разными цветами
Я нахожусь в процессе создания пользовательского элемента управления "звезда", чтобы вы могли передать поплавок в элемент управления, так как рейтинг, т.е. 2,5, и 2,5 из 5 звезд будут окрашены в красный цвет, а остальные - в серый.
Я рисую звезды, используя UIBezierPath с 5 точками, и это работает отлично. Однако, поскольку я использую числа с плавающей точкой, мне нужно убедиться, что десятичные дроби учтены. Я думал, что лучший способ сделать это - обрезать путь Безье до пропорции конечной ширины, однако этот метод, похоже, не влияет на сам рисунок; звезды нарисованы как обычно, без учета десятичных дробей.
Как вы, вероятно, ожидали, что я скажу, я действительно только начал баловаться с CoreGraphics и хотел бы объяснить, почему мой метод не работает, и метод, чтобы исправить это, чтобы помочь мне пройти через каркас.
С нетерпением ждем некоторых ответов!
- (void)drawStarsWithRating:(float)rating maxRating:(float)maxRating yOrigin:(CGFloat)yOrigin inContext:(CGContextRef)context {
float width = MKRGlyphSize;
CGFloat xCenter = MKRLeftBorderPadding + (0.5 * width);
CGFloat yCenter = yOrigin + (0.5 * width);
double r = width / 2.0;
float flip = -1.0;
for (NSUInteger i = 0; i < maxRating; i++) {
CGContextSaveGState(context);
if (i < rating) {
if (self.selected) {
CGContextSetFillColorWithColor(context, RGB(125, 212, 67).CGColor);
}
else {
CGContextSetFillColorWithColor(context, RGB(215, 35, 32).CGColor);
}
}
else {
if (self.selected) {
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
}
else {
CGContextSetFillColorWithColor(context, RGB(178, 178, 178).CGColor);
}
}
double theta = 2.0 * M_PI * (2.0 / 5.0);
UIBezierPath *bezier = [UIBezierPath bezierPath];
[bezier moveToPoint:CGPointMake(xCenter, r * flip + yCenter)];
for (NSUInteger k = 1; k < 5; k++) {
float x = r * sin(k * theta);
float y = r * cos(k * theta);
[bezier addLineToPoint:CGPointMake(x + xCenter, y * flip + yCenter)];
}
[bezier setLineWidth:1.0f];
[bezier setLineJoinStyle:kCGLineJoinMiter];
[bezier closePath];
[bezier fill];
if (rating - floorf(rating) > 0) {
CGRect clipRect = CGRectMake(xCenter, yOrigin, width * (rating - floorf(rating)), width);
CGContextClipToRect(context, clipRect);
}
xCenter += width;
CGContextRestoreGState(context);
}
}
2 ответа
Одна проблема, которую я заметил, это то, что вы [fill]
путь только один раз. это означает, что для дробных звезд вы бы видели только одну половину звезды, а не другую. В приведенном ниже коде каждая звезда заполняется белым цветом, а затем, если какая-либо часть звезды попадает в рейтинг, она снова заполняется синим цветом.
Я также заметил, что прямоугольник отсечения, который вы использовали, начал свой X в xCenter
вместо левой стороны звезды.
Я также немного скорректировал математику, чтобы рассчитать% заполнения для каждой звезды более последовательно.
- (void)drawStarsWithRating:(float)rating maxRating:(float)maxRating yOrigin:(CGFloat)yOrigin inContext:(CGContextRef)context {
float width = MKRGlyphSize;
CGFloat xCenter = MKRLeftBorderPadding + (0.5 * width);
CGFloat yCenter = yOrigin + (0.5 * width);
double r = width / 2.0;
float flip = -1.0;
for (NSUInteger i = 0; i < maxRating; i++) {
CGContextSaveGState(context);
// for clarity, i removed the colors from the top
// and ignore selected state. i use blue/white
// colors hard coded below
//
// you can easily change those colors just as you
// had before
// create star path
double theta = 2.0 * M_PI * (2.0 / 5.0);
UIBezierPath *bezier = [UIBezierPath bezierPath];
[bezier moveToPoint:CGPointMake(xCenter, r * flip + yCenter)];
for (NSUInteger k = 1; k < 5; k++) {
float x = r * sin(k * theta);
float y = r * cos(k * theta);
[bezier addLineToPoint:CGPointMake(x + xCenter, y * flip + yCenter)];
}
[bezier setLineWidth:1.0f];
[bezier setLineJoinStyle:kCGLineJoinMiter];
[bezier closePath];
// fill background of star with white
[[UIColor whiteColor] setFill];
[bezier fill];
// calculate the percentage of this star
// that we should fill
CGFloat currStar = i;
CGFloat percentOfStar;
if(rating > currStar){
// at least some of the star should be filled
percentOfStar = rating - currStar > 0 ? rating - currStar : 0;
percentOfStar = percentOfStar > 1 ? 1 : percentOfStar;
}else{
// none of the star should be filled
percentOfStar = 0;
}
if (percentOfStar) {
// if we need at least a little filling, then clip to that % of the star
// notice (xCenter - .5*width) to align the clipRect to the left side of
// the star.
// now fill the selected portion of the star with blue
CGRect clipRect = CGRectMake(xCenter - .5*width, yOrigin, width * (percentOfStar), width);
CGContextClipToRect(context, clipRect);
[[UIColor blueColor] setFill];
[bezier fill];
}
xCenter += width;
CGContextRestoreGState(context);
}
}
Вы могли бы сделать это немного по-другому:
заполните фон вашего вида цветом фона
Используйте процент OfStar, чтобы создать путь прямоугольника, который отражает рейтинг.
Используйте звездочку, чтобы обрезать.