1. 程式人生 > 其它 >照片選擇區域功能的另一實現: 加動效

照片選擇區域功能的另一實現: 加動效

技術標籤:專案中來ios

前言:

之前寫了兩份相關的,

低仿掃描全能王的選擇區域功能

仿掃描全能王的選擇區域功能:拍照,旋轉

思路就是從 touch 相關手勢,識別點,直接繪製

不方便,簡單地新增動畫

本文介紹另一思路,通過控制元件

有參考 rzmn/CropView

思路具體

1, 拿到點,還是同樣的三個方法,

touchesBegan, touchesMoved, touchesEnded

2, 檢視層級
  • 功能檢視 cropView, 新增在 image view 上,

功能檢視 cropView 中, 識別手勢

  • 畫線,四條邊,

var areaQuadrangle = SEAreaView()

新增在功能檢視 cropView

2.1, 方便做動畫,四個角都是控制元件, 新增在功能檢視 cropView

class CornerView: UIView

拖動某個角,那個角落的圓圈變大;

放開那個角,那個角落的圓圈縮回

3,座標的對應關係

imageView 設定為 sizeToFit,

預設,imageView 的 size > 上面看到的 image 的 size

( 當然,image.size 圖片資料的 size 很大的 )

功能檢視 cropView 的 frame = 上面看到的 image 的 frame

那麼畫線,四條邊,var areaQuadrangle = SEAreaView()

的 frame = 功能檢視 cropView 的 bounds

座標: var cornerLocations : [CGPoint]?

這個是外部第一次傳入的,或者外部需要修改傳入的,

同時,他也是手勢移動 touchesMoved 識別到的

實現

配置

func configure(corners imageView: UIImageView) {
        // 配置功能檢視
        self.imageView = imageView
        self.imageView?.isUserInteractionEnabled = true
        imageView.addSubview(self)
        // 初始化
        for subview in subviews {
            if subview is SECornerView {
                subview.removeFromSuperview()
            }
        }
        // 配置四個角
        for _ in 0..<Setting.std.cornerCount {
            let corner = SECornerView(frame: CGRect(x: 0, y: 0, width: Setting.std.cornerSize, height: Setting.std.cornerSize))
            addSubview(corner)
            cornerViews.append(corner)
        }
        // 配置四條邊
        areaQuadrangle.backgroundColor = .clear
        addSubview(areaQuadrangle)
    }

功能檢視 cropView 的 frame = 上面看到的 image 的 frame

第一次進來,實現上面那句

因為一般在 viewDidLoad 裡面配置,這時候,imageView 的 frame 還沒確定下來

控制元件內部實現的合適時機是,func layoutSubviews(), 需要走一次

public override func layoutSubviews() {
        super.layoutSubviews()
        guard first else {
            return
        }
        if let imgsize = imageView?.image?.size, let imageBounds = imageView?.bounds {
            let f = AVMakeRect(aspectRatio: imgsize, insideRect: imageBounds)
            frame = f
        }
        first = false
        // 從 bounds, 算出內部四個角落的點
        let f = bounds
        let first = f.origin
        let rhsTop = CGPoint(x: first.x + f.width, y: first.y)
        let lhsHip = CGPoint(x: first.x, y: first.y + f.height)
        let end = CGPoint(x: rhsTop.x, y: lhsHip.y)
        let dots = [first, rhsTop, end, lhsHip]
        self.cornerLocations = dots
        areaQuadrangle.frame = bounds
        update(scale: nil)
        cornerOnTouch = nil
    }

AVMakeRect 方法挺好用的,直接把 imageView 內部 image sizeToFit 的 size, 算出來了

手勢移動識別

當然,至少還有兩個手勢


override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        guard let touchIdx = cornerOnTouch, touches.count == 1, let first = touches.first, let cornerLocations = cornerLocations, let img = imageView?.image else { return }
        // 拿到點,
        // 通過兩個點的距離 + 起始點 cornerLocations[touchIdx].x , 稍微繁瑣
        let from = first.previousLocation(in: self)
        let to = first.location(in: self)
        let derivative = CGPoint(x: to.x - from.x, y: to.y - from.y)
        
        let rawPt = CGPoint(x: cornerLocations[touchIdx].x + derivative.x, y: cornerLocations[touchIdx].y + derivative.y)
        // 確保不越界
        let newCenterOnImage = rawPt.normalized(size: img.size)
        // 更新資料
        self.cornerLocations?[touchIdx] = newCenterOnImage
        // 效果
        update(scale: nil)
    }

效果

func update(scale isBigger: Bool?) {
        guard let touchIdx = cornerOnTouch else {
            return
        }
        // 更新位置
        // 更新畫線
        pairPositionsAndViews()
        if let bigger = isBigger{
            switch bigger {
            case true:
                // 角落圓圈,動畫縮放
                cornerViews[touchIdx].scaleUp()
            case false:
                cornerViews[touchIdx].scaleDown()
            }
        }
        // 角落圓圈,塗色
        for corner in cornerViews {
            corner.layer.borderColor = (isPathValid ? Setting.std.goodAreaColor : Setting.std.badAreaColor).cgColor
        }
    }

更新位置


func pairPositionsAndViews() {
        // 控制元件更新位置,就是改中心點
        if let cornerPositions = self.cornerLocations {
            for i in 0 ..< Setting.std.cornerCount {
                self.cornerViews[i].center = CGPoint(x: cornerPositions[i].x, y: cornerPositions[i].y)
            }
        }
        // 更新畫線
        self.areaQuadrangle.fill(path: path)
    }
動畫

控制元件動畫,簡單


class CornerView: UIView {
    // 放大動畫
    func scaleUp() {
        UIView.animate(withDuration: 0.15, animations: {
            self.layer.borderWidth = 0.5
            self.transform = CGAffineTransform.identity.scaledBy(x: 2, y: 2)
        })
    }
    // 縮小動畫類似
}

交叉重聯

如果一個點拖動,越過對面的邊,導致交叉了

如果是凸四邊形,就重新連線

這段邏輯的觸發時機是 touchesEnded

override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        guard cornerOnTouch != nil, touches.count == 1 else {
            return
        }
        // 	交叉重聯
        sortPointClockwise()
        //  初始化
        update(scale: false)
        cornerOnTouch = nil
    }

交叉重聯,在篇首的部落格裡面有講解,這裡略

github repo