0

我有一个 UIView 边界到一个圆圈,像这样

在此处输入图像描述

我想做一个像拉伸一样的效果,看下图

在此处输入图像描述

它喜欢 pullToRefresh 效果,有很多库或开源来制作 pullToRefresh,但它们始终属于 tableview。我只想独立制作拉伸效果。我只有一个带圆圈的 UIView,当我拉它时它被拉伸了

我怎样才能做到

4

3 回答 3

4

基本思想是使用贝塞尔路径来勾勒出您正在寻找的精确形状。然后,您可以使用手势来更改该形状并使用显示链接动画将拉伸的圆圈返回到其圆形形式:

@IBDesignable
class RefreshView: UIView {

    private let shapeLayer = CAShapeLayer()

    @IBInspectable
    var fillColor: UIColor = UIColor.lightGrayColor() {
        didSet {
            shapeLayer.fillColor = fillColor.CGColor
        }
    }

    @IBInspectable
    var strokeColor: UIColor = UIColor.clearColor() {
        didSet {
            shapeLayer.strokeColor = strokeColor.CGColor
        }
    }

    @IBInspectable
    var lineWidth: CGFloat = 0.0 {
        didSet {
            shapeLayer.strokeColor = strokeColor.CGColor
        }
    }

    /// Center of main circle is in center top

    private var pullDownCenter: CGPoint {
        return CGPoint(x: bounds.size.width / 2.0, y: bounds.size.width / 2.0)
    }

    /// Radius of view spans width of view

    private var radius: CGFloat {
        return bounds.size.width / 2.0
    }

    override var frame: CGRect {
        get {
            return super.frame
        }
        set {
            super.frame = newValue
            updatePath()
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        configureView()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        configureView()
    }

    /// Update the path, add shape layer, and add gesture recognizer

    private func configureView() {
        shapeLayer.fillColor = fillColor.CGColor
        shapeLayer.strokeColor = strokeColor.CGColor
        shapeLayer.lineWidth = lineWidth

        updatePath()
        layer.addSublayer(shapeLayer)

        let pan = UIPanGestureRecognizer(target: self, action: #selector(RefreshView.handlePan(_:)))
        addGestureRecognizer(pan)
    }

    /// Update path

    private func updatePath() {
        shapeLayer.path = stretchyCirclePathWithCenter(pullDownCenter, radius: radius, yOffset: yOffset).CGPath
    }

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        yOffset = yOffsetMax
    }

    // MARK: Gesture Recognizer

    private var yOffset: CGFloat = 0.0 { didSet { updatePath() } }
    private var yOffsetMax: CGFloat { return bounds.size.width * 1.5 }
    private var yOldOffset: CGFloat = 0.0

    func handlePan(gesture: UIPanGestureRecognizer) {
        if gesture.state == .Began {
            yOldOffset = yOffset
        } else if gesture.state == .Changed {
            yOffset = yOldOffset + max(0, min(gesture.translationInView(gesture.view).y, yOffsetMax))
        } else if gesture.state == .Ended || gesture.state == .Cancelled {
            animateBackToCircle()
        }
    }

    // MARK: Animation

    private var displayLink: CADisplayLink?
    private var duration: CGFloat?
    private var startTime: CFAbsoluteTime?
    private var originalOffset: CGFloat?

    private func animateBackToCircle() {
        displayLink = CADisplayLink(target: self, selector: #selector(RefreshView.handleDisplayLink(_:)))
        duration = 0.5
        originalOffset = yOffset
        startTime = CFAbsoluteTimeGetCurrent()
        displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
    }

    func handleDisplayLink(displayLink: CADisplayLink) {
        let percent = CGFloat(CFAbsoluteTimeGetCurrent() - startTime!) / duration!

        if percent < 1.0 {
            yOffset = originalOffset! * (1.0 - sin(percent * CGFloat(M_PI_2)))
        } else {
            self.displayLink?.invalidate()
            self.displayLink = nil
            updatePath()
        }
    }

    // MARK: Stretch circle path

    private func stretchyCirclePathWithCenter(center: CGPoint, radius: CGFloat, yOffset: CGFloat = 0.0) -> UIBezierPath {
        func pointWithCenter(center: CGPoint, radius: CGFloat, angle: CGFloat) -> CGPoint {
            return CGPoint(x: center.x + radius * cos(angle), y: center.y + radius * sin(angle))
        }

        if yOffset == 0 {
            return UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: 2.0 * CGFloat(M_PI), clockwise: true)
        }

        let lowerRadius = radius * (1 - yOffset / yOffsetMax * 0.5)
        let yOffsetTop = yOffset / 4
        let yOffsetBottom = yOffset / 1.5
        let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(M_PI), endAngle: 0, clockwise: true)
        path.addCurveToPoint(CGPoint(x: center.x + lowerRadius, y:center.y + yOffset), controlPoint1: CGPoint(x: center.x + radius, y:center.y + yOffsetTop), controlPoint2: CGPoint(x: center.x + lowerRadius, y:center.y + yOffset - yOffsetBottom))
        path.addArcWithCenter(CGPoint(x: center.x, y:center.y + yOffset), radius: lowerRadius, startAngle: 0, endAngle: CGFloat(M_PI), clockwise: true)
        path.addCurveToPoint(CGPoint(x: center.x - radius, y:center.y), controlPoint1: CGPoint(x: center.x - lowerRadius, y:center.y + yOffset - yOffsetBottom), controlPoint2: CGPoint(x: center.x - radius, y:center.y + yOffsetTop))

        return path
    }

}

这呈现如下内容:

在此处输入图像描述

显然,您可以随意调整路径,添加额外的装饰,如旋转箭头或其他等等。但这说明了构建贝塞尔路径的基础知识,用手势拉伸它,并将其动画化回圆形.

于 2016-05-18T04:36:16.540 回答
0

已经有一个控件可供您使用: circle with strech

除了上面我还会尝试这种方法:我认为你可以有完整的拉伸图像,隐藏在它上面的白色视图中。当用户向下或向上滑动时,您可以显示/隐藏部分拉伸图像。

如果用户没有滑动则显示未拉伸的圆形图像,如果用户滑动了阈值距离,则显示完全拉伸的图像。

于 2016-05-17T16:44:24.153 回答
0

您可能需要进行自定义绘图。只是拉伸视图会导致锯齿和视图变形。

为此,您应该继承UIView并重写drawRect以进行某种可以响应用户交互的绘图。

我创建了一个示例类:

class CircleRefresh:UIView{
    var taperFactor:CGFloat = 0.00{
        didSet{
            self.setNeedsDisplay()
        }
    }

    var heightFactor: CGFloat = 0.40
        {
        didSet{
            self.setNeedsDisplay()
        }
    }

    var radius:CGFloat
    {
        return self.frame.size.height * 0.20
    }
    //Set this to add an arrow or another image to the top circle
    var refreshImage:UIImage?{
        didSet{
            //
            self.subviews.forEach{$0.removeFromSuperview()}
            if let image = self.refreshImage{
                let imageView = UIImageView(frame: CGRect(x:self.frame.size.width * 0.5 - self.radius,y:0.0,width:self.radius * 2.0,height:self.radius * 2.0))
                imageView.image = image
                imageView.contentMode = .ScaleAspectFit
                imageView.layer.cornerRadius = imageView.frame.size.height * 0.5
                imageView.layer.masksToBounds = true
                self.addSubview(imageView)
            }
        }
    }

    override func drawRect(rect:CGRect)
    {
        UIColor.lightGrayColor().setFill()

        let endCenter = CGPoint(x:self.frame.size.width * 0.5,y:self.frame.size.height * heightFactor - radius * taperFactor)

        //top arc
        let path = UIBezierPath(arcCenter: CGPoint(x:self.frame.size.width * 0.5, y:radius), radius: self.frame.size.height * 0.20, startAngle: 0.0, endAngle: CGFloat(M_PI), clockwise: false)
        //left curve
        path.addCurveToPoint(CGPoint(x:endCenter.x - radius * taperFactor,y:endCenter.y), controlPoint1: CGPoint(x:endCenter.x - radius, y:radius * 2.0), controlPoint2: CGPoint(x: endCenter.x - radius * taperFactor, y: radius * 2.0))
        //bottom arc
        path.addArcWithCenter(endCenter, radius: radius * taperFactor, startAngle: CGFloat(M_PI), endAngle: 0.0, clockwise: false)
        //right curve
        path.addCurveToPoint(CGPoint(x:endCenter.x + radius,y:radius), controlPoint1: CGPoint(x: endCenter.x + radius * taperFactor, y: radius * 2.0),controlPoint2: CGPoint(x:endCenter.x + radius, y:radius * 2.0))

        path.fill()
    }
}

这将绘制一个圆圈,但会发生变化taperFactor,并heightFactor会产生拉伸效果。设置heightFactor为 1.0 将意味着绘图将占据视图的整个高度,设置taperFactor为 1.0 将导致绘图的尾部与顶部圆圈一样宽。

要了解如何更改heightFactortaperFactor更改绘图,您可以在此视图中添加一个平移手势识别器,并在其操作中说如下内容:

@IBAction func pan(sender: UIPanGestureRecognizer) {
        let location  = sender.locationInView(self.view)
        circle.heightFactor = location.y / self.view.frame.size.height
        circle.taperFactor = circle.heightFactor * 0.5 - 0.20
    }

你可以改变heightFactortaperFactor达到不同的效果。这只是如何制作刷新控制器的一部分,但似乎是您问题的重点。

于 2016-05-17T19:44:36.700 回答