项目需求中遇到获取UIView
上某个坐标点的RGB
颜色值的需求,现在把自己找到的解决方案简单总结记录一下,遇到了下面的情况:
- 不可移动的UIView
- 旋转式的UIView
- 滑条式的UIView
不可移动的UIView
如下图所示,有一个圆形的颜色板,当手指在颜色板上移动时,UIViewController
的backgroundColor
将会设置成手指在颜色板上触点的颜色值:
实现该功能的方案搜索至stackoverflow
, 出处点击这里.
核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #import "UIView+ColorOfPoint.h" #import <QuartzCore/QuartzCore.h>
@implementation UIView (ColorOfPoint)
- (UIColor *)colorOfPoint:(CGPoint)point { unsigned char pixel[4] = {0}; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(pixel, 1, 1, 8, 4, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast); CGContextTranslateCTM(context, -point.x, -point.y); [self.layer renderInContext:context]; CGContextRelease(context); CGColorSpaceRelease(colorSpace); UIColor *color = [UIColor colorWithRed:pixel[0]/255.0 green:pixel[1]/255.0 blue:pixel[2]/255.0 alpha:pixel[3]/255.0]; return color; }
|
该功能封装成UIView
的一个Category
了,主要注意头文件QuartzCore.h
的引入以及坐标系的转换.
最终,当手指在颜色板上滑动时,获取ColorPanel
中颜色值的调用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #import "ImmovableColorPanel.h" #import "UIView+ColorOfPoint.h"
@implementation ImmovableColorPanel
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { CGPoint location = [touch locationInView:self]; self.color = [self colorOfPoint:location]; [self sendActionsForControlEvents:UIControlEventValueChanged]; return YES; }
@end
|
旋转式的UIView
现在假设上图所示的颜色板是可旋转的,获取颜色值的方式修改为获取色环中间正上方某点的颜色值,如下图所示,获取的是下三角所指某点的颜色值(下三角不会随着颜色板的旋转而旋转).
先看看颜色板随着手指的滑动而旋转是如何实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #import "RoundColorPanel.h"
@interface RoundColorPanel ()
/** * ColorPanel中心点的坐标值 */ @property (nonatomic) CGPoint centerPoint;
@end
@implementation RoundColorPanel
- (void)awakeFromNib { [super awakeFromNib]; self.centerPoint = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); }
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { CGPoint previousLocation = [touch previousLocationInView:self]; CGPoint location = [touch locationInView:self]; CGFloat previousRadian = [self radianToCenterPoint:self.centerPoint withPoint:previousLocation]; CGFloat curRadian = [self radianToCenterPoint:self.centerPoint withPoint:location]; CGFloat changedRadian = curRadian - previousRadian; [self rotateByRadian:changedRadian]; [self sendActionsForControlEvents:UIControlEventValueChanged]; return YES; }
/** * 以ColorPanel的anchorPoint为坐标原点建立坐标系,计算坐标点|point|与坐标原点的连线距离x轴正方向的夹角 * * @param centerPoint 坐标原点坐标 * @param point 某坐标点 * * @return 坐标点|point|与坐标原点的连线距离x轴正方向的夹角 */ - (CGFloat)radianToCenterPoint:(CGPoint)centerPoint withPoint:(CGPoint)point { CGVector vector = CGVectorMake(point.x - centerPoint.x, point.y - centerPoint.y); return atan2f(vector.dy, vector.dx); }
/** * 将图层旋转radian弧度 * * @param radian 旋转的弧度 */ - (void)rotateByRadian:(CGFloat)radian { CGAffineTransform transform = self.layer.affineTransform; transform = CGAffineTransformRotate(transform, radian); self.layer.affineTransform = transform; }
|
核心代码就是rotateByRadian:
消息,使用CALayer的仿射变换完成。
可能你会发现,在continueTrackingWithTouch:
消息中没有看到UIView的分类消息colorOfPoint:
的发送,其实如果你尝试这样做,会发现这样取出的颜色值是旋转开始时下三角所指点referPoint
的颜色值,随着ColorPanel
的旋转,该颜色值不会改变,这是因为我们对ColorPanel
的旋转是通过仿射变换实现,而仿射变换会改变图层的坐标系,也就是说,referPoint
现在所处的坐标系是已做过仿射变换的坐标系,而不再是以图层左上角为坐标原点,向右为x轴正方向,向下为y轴正方向的坐标系。当然你可以计算ColorPanel
总的旋转角度totalChangedRadian
,然后通过使referPoint
旋转-totalChangedRadian
角度来计算该点在新坐标系中的坐标值,如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| #import "RoundColorPanel.h" #import "UIView+ColorOfPoint.h"
@interface RoundColorPanel ()
/** * 颜色板中心点坐标 */ @property (nonatomic) CGPoint centerPoint; /** * 获取颜色值参考点与|centerPoint|的连线与x轴正方向的夹角 */ @property (nonatomic) CGFloat referRadian; /** * 获取颜色值参考点与|centerPoint|的连线长度 */ @property (nonatomic) CGFloat referRadius; /** * 颜色板在整个旋转过程中的总和 */ @property (nonatomic) CGFloat totalChangedRadian;
@end
@implementation RoundColorPanel
- (void)awakeFromNib { [super awakeFromNib]; self.centerPoint = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); //|referPoint|获取该点的颜色值 CGPoint referPoint = CGPointMake(CGRectGetMidX(self.bounds), 2); CGVector vector = CGVectorMake(referPoint.x - self.centerPoint.x, referPoint.y - self.centerPoint.y); self.referRadian = atan2(vector.dy, vector.dx); self.referRadius = sqrt(vector.dx * vector.dx + vector.dy * vector.dy); self.layer.cornerRadius = CGRectGetWidth(self.bounds) / 2; }
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { CGPoint previousLocation = [touch previousLocationInView:self]; CGPoint location = [touch locationInView:self]; CGFloat previousRadian = [self radianToCenterPoint:self.centerPoint withPoint:previousLocation]; CGFloat curRadian = [self radianToCenterPoint:self.centerPoint withPoint:location]; CGFloat changedRadian = curRadian - previousRadian; [self rotateByRadian:changedRadian]; self.totalChangedRadian += changedRadian; CGFloat radian = self.referRadian - self.totalChangedRadian; CGPoint referPoint1 = CGPointMake(self.centerPoint.x + self.referRadius * cos(radian), self.centerPoint.y + self.referRadius * sin(radian)); self.color = [self colorOfPoint:referPoint1]; [self sendActionsForControlEvents:UIControlEventValueChanged]; return YES; }
- (CGFloat)radianToCenterPoint:(CGPoint)centerPoint withPoint:(CGPoint)point { CGVector vector = CGVectorMake(point.x - centerPoint.x, point.y - centerPoint.y); return atan2f(vector.dy, vector.dx); }
- (void)rotateByRadian:(CGFloat)radian { CGAffineTransform transform = self.layer.affineTransform; transform = CGAffineTransformRotate(transform, radian); self.layer.affineTransform = transform; }
|
不过,我更趋向于使用接下来使用的方法,更加简单和理解。
将RoundColorPanel
放入一个容器视图RotaryColorPanel
中,RoundColorPanel
的旋转改变的是自身视图的坐标系,而不会改变其父视图的坐标系,因此我们可以在其父视图的坐标系中定义需要取颜色值的坐标点,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #import "RotaryColorPanel.h" #import "RoundColorPanel.h" #import "UIView+ColorOfPoint.h" #import "XXNibBridge.h"
@interface RotaryColorPanel () <XXNibBridge>
/** * 可旋转的颜色板 */ @property (weak, nonatomic) IBOutlet RoundColorPanel *roundColorPanel; /** * 下三角指示标识 */ @property (weak, nonatomic) IBOutlet UIImageView *indicator;
/** * 获取该点的颜色值 */ @property (nonatomic) CGPoint referPoint;
@end
@implementation RotaryColorPanel
- (void)awakeFromNib { [super awakeFromNib]; self.referPoint = CGPointMake(CGRectGetMidX(self.indicator.frame), CGRectGetMaxY(self.indicator.frame)); [self.roundColorPanel addTarget:self action:@selector(colorPanelRotated:) forControlEvents:UIControlEventValueChanged]; }
- (void)colorPanelRotated:(id)sender { UIColor *color = [self colorOfPoint:self.referPoint]; if (self.colorChangedHandler) { self.colorChangedHandler(color); } }
@end
|
获取颜色值的调用变成如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #import "RotaryColorPanelViewController.h" #import "RotaryColorPanel.h"
@interface RotaryColorPanelViewController ()
@property (weak, nonatomic) IBOutlet RotaryColorPanel *rotaryColorPanel;
@end
@implementation RotaryColorPanelViewController
- (void)viewDidLoad { [super viewDidLoad]; __weak typeof(self) weakSelf = self; self.rotaryColorPanel.colorChangedHandler = ^(UIColor *color) { weakSelf.view.backgroundColor = color; }; }
- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. }
@end
|
滑条式UIView
该样式与上面的区别是,在颜色条上有遮挡物滑块,获取颜色值的点坐标是滑块中心点的坐标,
如下图所示:
解决方案同第2中情况类似,就是让滑块和颜色板处在并列的层级,它们同时直属于同一superview
中。
上述所有代码已上传github
,可点击此处获取.
目前已转行教育行业,欢迎加微信交流:CaryaLiu