一个好用的多边形控件
来源:互联网 发布:金山特大网络诈骗团伙 编辑:程序博客网 时间:2024/05/11 17:28
前言
要实现如图片中左侧的正六边形按钮,其中要有边框以及角的弧度。由于以前做过CALayer
相关的功能,自然想起利用CALayer
绘制path
来实现该功能。
根据最大半径计算各顶点坐标
先确定按钮的size
得出最大r
值,然后按照这个模式得出每个点相对于按钮的坐标,使用UIBezierPath
绘制path
得到最后的图样。按照这样的逻辑确实可以做出如UI展示的效果(先忽略圆角和边框),但是因此会引发一个问题:这种代码是一种死代码。本着代码的可扩展性原则,在此封装了一个多边形按钮
弧度计算顶点坐标
- 首先进行一下逻辑分析,按照固定点坐标的做法会导致代码死板,因此换个思路进行设计: 使用
角度
的形式来设置对应点坐标。 - 以按钮的中心点
centerPoint
作为原点,按钮所在坐标系的x
轴上点(r, 0)
点作为绘制的起始点,依据顺时针方向依次计算每个点的坐标 - 根据规律可知对应弧度所表示的点的坐标的
x
值等于centerPoint.x +
,而
r * cos(弧度)y
值等于centerPoint.y + r * sin(弧度)
该坐标既是多边形对应顶点的坐标
获取单个点的坐标
private func vertexCoordinates(radius: CGFloat, angle: Double, offset: Double = 0) ->CGPoint { let centerPoint = CGPoint(x: bounds.width / 2, y: bounds.height / 2) let X = centerPoint.x + radius * (angle + offset).cosValue let Y = centerPoint.y + radius * (angle + offset).sinValue return CGPoint(x: X, y: Y) }
根据多边形的边数、最大半径以及偏移弧度获得多边形的每个顶点的坐标
private func regularPolygonCoordinates(sides: Int, radius: CGFloat, offset: Double = 0) ->[CGPoint] { assert(sides >= 3, "多边形最少为3边") assert(radius > 0, "多边形半径必须大于0") var coordinates = [CGPoint]() for i in 0..<sides { let corner = Double(360) / Double(sides) let radian = corner / Double(180) * Double.pi let radianOfPoint = Double(i) * radian let point = vertexCoordinates(radius: radius, angle: radianOfPoint, offset: offset) coordinates.append(point) } return coordinates }
再进一步根据获得的各个顶点的坐标数组可以用UIBezierPath
绘制图案
let points = regularPolygonCoordinates(sides: style.sides, radius: r, offset: style.offset) for (index, point) in points.enumerated() { if index == 0 { path.move(to: point) }else { path.addLine(to: point) } } path.close()
到现在只是得到了完整的path
还需要设置到layer
上才能变相的按该路径裁剪按钮
private func hexagon() { var Max_R: CGFloat if style.Max_Radius == 0 { let W = bounds.width let H = bounds.height assert(W > 0 && H > 0, "此时宽或者高没有值") Max_R = H > W ? W / 2 : H / 2 }else { Max_R = style.Max_Radius } assert(Max_R > style.borderWidth, "多边形最大半径不能小于边界宽度") for (index, layer) in self.layer.sublayers!.enumerated() { if index != 0 { layer.removeFromSuperlayer() } } let topLayer = CAShapeLayer() topLayer.path = drawPath(radius: Max_R) topLayer.strokeColor = style.borderColor.cgColor topLayer.fillColor = UIColor.clear.cgColor topLayer.lineWidth = style.borderWidth let bottomLayer = CAShapeLayer() bottomLayer.path = drawPath(radius: Max_R) self.layer.mask = bottomLayer self.layer.insertSublayer(topLayer, above: bottomLayer) }
然后在layoutSubviews()
方法中调用该绘制方法即可获得对应图形的按钮
override func layoutSubviews() { super.layoutSubviews() hexagon() }
圆角的设置
我这里的圆角采用的不是平时按钮的cornerRadius
,而是采用的贝塞尔曲线
的形式设置圆角,即根据两个定点以及一个控制点来绘制一条有弧度的曲线,原因是多边形如果设置cornerRadius
会导致曲率很大最后裁剪出的弧度十分难看(不认同的话欢迎指正)
如果把正多边形
以中心点
、顶点
与邻近顶点
组成的等边三角形
视为一个子模块,选取贝塞尔曲线
的固定两点其一为D
点(另一点以AC
边对称)与控制点C
,则只需要计算出D
点对应的坐标即可(C
点坐标已知,r
最大半径已知,CD
长度自定义,多边形边数自定义)
- 首先需要计算出
∠DAC
角的弧度 - 然后计算出
AD
边的大小 - 根据普通正多边形各顶点坐标的规律计算出
AD
为R
的多边形相对于原多边形偏移∠DAC
角度的各顶点坐标
private func regularPolygonCoordinatesWithRoundedCorner(sides: Int, radius: CGFloat, offset: Double = 0) ->[CGPoint] { assert(sides >= 3, "多边形最少为3边") assert(radius > 0, "多边形半径必须大于0") let CAB = Double(360) / Double(sides) / Double(180) * Double.pi let EC = Double(radius * (CAB / 2).sinValue) let AE = Double(radius * (CAB / 2).cosValue) let ED = EC - style.filletDegree let EAD = atan(ED / AE) let DAC = CAB / 2 - EAD let newRadius = sqrt(pow(AE, 2) + pow(ED, 2)) var coordinates = [CGPoint]() for i in 0..<sides { let direction = Double(i) * Double(360) / Double(sides) / Double(180) * Double.pi let point = vertexCoordinates(radius: radius, angle: direction, offset: offset) let leftAngle = direction - DAC let leftPoint = vertexCoordinates(radius: CGFloat(newRadius), angle: leftAngle, offset: offset) let rightAngle = direction + DAC let rightPoint = vertexCoordinates(radius: CGFloat(newRadius), angle: rightAngle, offset: offset) coordinates.append(leftPoint) coordinates.append(point) coordinates.append(rightPoint) } return coordinates }
通过上面的代码逻辑可以计算出有圆角的多边形的各关键点坐标,然后就只需要把这些关键点根据规律连接到一起。
let points = regularPolygonCoordinatesWithRoundedCorner(sides: style.sides, radius: r, offset: style.offset) var temPoint: CGPoint! for (index, point) in points.enumerated() { if index == 0 { path.move(to: point) }else { let remainder = index % 3 switch remainder { case 0: path.addLine(to: point) case 1: temPoint = point case 2: path.addQuadCurve(to: point, controlPoint: temPoint) default: break } } } path.close()
封装控制属性
class SYPolygonStyle { /// 是否可以圆角 var roundedCornersEnable: Bool = false /// 圆角程度 - 值越大,角越平滑 var filletDegree: Double = 5.0 /// 边界宽度(边线的宽度) var borderWidth: CGFloat = 0.0 /// 边界颜色 var borderColor: UIColor = UIColor.gray /// 多边形最大半径 -- 如果不设置该值, 默认是按钮中可显示的最大多边形的半径 var Max_Radius: CGFloat = 0.0 /// 整个路径以按钮的中心点为中心按顺时针方向偏移的弧度(需要传入一个带π的弧度) -- 默认六边形顶点为水平方向, 如果设置该值为π/2则顶点为竖直方向 var offset: Double = 0 /// 多边形的边数 - 默认是正六边形 var sides: Int = 6}
当然,有了控制属性的类没有使用场景怎么行,只需要一个新的构造方法即可
init(frame: CGRect = CGRect.zero, style: SYPolygonStyle = SYPolygonStyle()) { self.style = style super.init(frame: frame) }
使用方法类似于下例:
let style = SYPolygonStyle() style.filletDegree = 10 style.borderWidth = 10 style.borderColor = .orange style.roundedCornersEnable = true style.offset = Double.pi / 4 style.sides = 5 let liubianxing = SYPolygonButton(frame: CGRect.zero, style: style) liubianxing.setImage(UIImage.init(named: "zhbd_qq_icon"), for: .normal) liubianxing.setImage(UIImage.init(named: "zhbd_weibo_icon"), for: .selected) liubianxing.addTarget(self, action: #selector(btnClickAction(sender:)), for: .touchUpInside) view.addSubview(liubianxing) liubianxing.snp.makeConstraints { (make) in make.top.left.equalTo(100) make.width.height.equalTo(100) }
Demo
谷胜亚初次编辑于2017年9月25日
- 一个好用的多边形控件
- 一个好用的Treeview控件
- 一个比较好用的日历控件
- 一个好用的日期控件
- flexigrid--一个好用的控件
- 介绍一个好用的Grid控件"EjunGrid"
- 一个好用的JS日期选择控件
- 推荐一个好用的ColorPicker Silverlight 控件
- .net一个好用的时间控件My97DatePicker
- 一个好用的Setting开关(自定义组合控件)
- 好用的控件
- 一个点串超多的多边形
- 一个关于web控件的好站点
- 一个比较好的GridView 控件
- jquery 好用的控件
- 好用的打印控件
- 好用的控件收藏
- 分享一个比较好用的流式布局的label控件
- c++ 指针和数组
- Effective STL 43 Prefer algorithm calls to hand-written loops
- 波形音频(WAVE)底层接口的学习与使用
- python dict sorted 排序
- JavaScript基础 表单验证
- 一个好用的多边形控件
- C语言调用数据库
- 简单的鼠标悬停效果
- IE8可以兼容HTML5的标签
- group by的使用
- 吴聊(17)——喜闻乐见,大数网倒闭了
- 面向对象 访问类型的控制
- 贪吃蛇小demo
- Java中try-catch-finally语句块的应用