【问题标题】:Create a Needle GaugeView from iOS (Swift) to Android(Kotlin)创建从 iOS (Swift) 到 Android (Kotlin) 的 Needle GaugeView
【发布时间】:2022-10-14 20:06:08
【问题描述】:

在将 iOS 中用 Swift 编写的 Gauge View 翻译成用 Kotlin 在自定义视图中编写的 Android 时需要帮助。 Video & assets

import UIKit

class GaugeView: UIView {

    var outerBezelColor = UIColor.gray20!
    var outerBezelWidth: CGFloat = 2
    var innerBezelColor = UIColor.baseWhite
    var innerBezelWidth: CGFloat = 5
    var insideColor = UIColor.baseWhite

    var segmentWidth: CGFloat = 0
    var segmentColors = [UIColor.gray20!]

    var totalAngle: CGFloat = 270
    var rotation: CGFloat = -135

    let mainBg = UIImageView()
    
    var needleColor = UIColor.clear
    var needleWidth: CGFloat = 23
    let needle = UIView()
    let polygon = UIImageView()
    
    let valueLabel = UILabel()
    var valueFont = UIFont(name: "PlusJakartaSans-ExtraBold", size: 32)
    var valueColor = UIColor.gray80
    
    let statusLabel = UILabel()
    var statusFont = UIFont(name: "PlusJakartaSans-Regular", size: 16)
    var statusColor = UIColor.gray70
    
    var value: Int = 0 {
        didSet {
            // update the value label to show the exact number
            valueLabel.text = String(value)

            // figure out where the needle is, between 0 and 1
            let needlePosition = CGFloat(value) / 100

            // create a lerp from the start angle (rotation) through to the end angle (rotation + totalAngle)
            let lerpFrom = rotation
            let lerpTo = rotation + totalAngle

            // lerp from the start to the end position, based on the needle's position
            let needleRotation = lerpFrom + (lerpTo - lerpFrom) * needlePosition
            needle.transform = CGAffineTransform(rotationAngle: deg2rad(needleRotation))
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setUp()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setUp()
    }
    
    func setUp() {
        needle.backgroundColor = needleColor
        needle.translatesAutoresizingMaskIntoConstraints = false

        // make the needle a third of our height
        needle.bounds = CGRect(x: 0, y: 0, width: needleWidth, height: bounds.height / 3)
        
        mainBg.bounds = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width/1.2, height: UIScreen.main.bounds.size.width/1.2)
        mainBg.image = UIImage(named: "credit-score-meter")
        mainBg.contentMode = .scaleAspectFit
        mainBg.center = CGPoint(x: bounds.midX, y: bounds.midY)
        
        polygon.bounds = CGRect(x: 0, y: 0, width: 23, height: 23)
        polygon.image = UIImage(named: "polygon")
        polygon.center = CGPoint(x: needle.bounds.midX, y: 0)

        // align it so that it is positioned and rotated from the bottom center
        needle.layer.anchorPoint = CGPoint(x: 0.5, y: 1)

        // now center the needle over our center point
        needle.center = CGPoint(x: bounds.midX, y: bounds.midY)
        addSubview(mainBg)
        addSubview(needle)
        needle.addSubview(polygon)
        
        valueLabel.font = valueFont
        valueLabel.text = "0"
        valueLabel.textColor = valueColor
        valueLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(valueLabel)
        
        statusLabel.font = statusFont
        statusLabel.text = "VERY GOOD"
        statusLabel.textColor = statusColor
        statusLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(statusLabel)

        NSLayoutConstraint.activate([
            valueLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
            valueLabel.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -20)
        ])
        
        NSLayoutConstraint.activate([
            statusLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
            statusLabel.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 20)
        ])
    }
    
    override func draw(_ rect: CGRect) {
        guard let ctx = UIGraphicsGetCurrentContext() else { return }
        drawSegments(in: rect, context: ctx)
    }
    
    func deg2rad(_ number: CGFloat) -> CGFloat {
        return number * .pi / 180
    }
    
    func drawSegments(in rect: CGRect, context ctx: CGContext) {
        // 1: Save the current drawing configuration
        ctx.saveGState()

        // 2: Move to the center of our drawing rectangle and rotate so that we're pointing at the start of the first segment
        ctx.translateBy(x: rect.midX, y: rect.midY)
        ctx.rotate(by: deg2rad(rotation) - (.pi / 2))

        // 3: Set up the user's line width
        ctx.setLineWidth(segmentWidth)

        // 4: Calculate the size of each segment in the total gauge
        let segmentAngle = deg2rad(totalAngle / CGFloat(segmentColors.count))

        // 5: Calculate how wide the segment arcs should be
        let segmentRadius = (((rect.width - segmentWidth) / 2) - outerBezelWidth) - innerBezelWidth

        // 6: Draw each segment
        for (index, segment) in segmentColors.enumerated() {
            // figure out where the segment starts in our arc
            let start = CGFloat(index) * segmentAngle

            // activate its color
            segment.set()

            // add a path for the segment
            ctx.addArc(center: .zero, radius: segmentRadius, startAngle: start, endAngle: start + segmentAngle, clockwise: false)

            // and stroke it using the activated color
            ctx.drawPath(using: .stroke)
        }

        // 7: Reset the graphics state
        ctx.restoreGState()
    }
}

我想做的是使用 FrameLayout 在其上绘制小灰线段。我将尝试添加的下一件事是图像视图和针视图,但我不确定如何适当地旋转它。

class CreditScore(context: Context, attrs: AttributeSet): FrameLayout(context, attrs) {

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
    }
}

【问题讨论】:

  • @nglauber 你能帮我使用jetpack compose吗

标签: android ios swift kotlin android-custom-view


【解决方案1】:

我根本不会使用这种方法。不要使用 Framelayout 并尝试使用嵌入式 ImageView。只需使用以 View 作为父级的自定义 View,并使用 Canvas.drawBitmap 自己绘制背景。至于旋转针 - 在绘制针时应用旋转矩阵。要创建旋转矩阵,只需执行

val matrix = Matrix()
martix.setRotate(degrees, pivotX, pivotY)
canvas.drawBitmap(needleBitmap, matrix, paint)

这将绘制旋转度数的针图像。枢轴点将在您指定的位置(基本上应该是针的底部)

对于您的视图,您可以使用预先计算所有灰色段的背景(矢量可绘制非常适合此),或者您可以自己绘制它们 - 基本上每个段有 1 个 drawRect 命令。

【讨论】:

    【解决方案2】:

    我建议你试试这个 lib for Android/Kotlin

    Dynamic Speedometer, Gauge for Android

    它可以轻松定制,看起来类似于您的 iOS 实现

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-04
      • 2021-10-29
      • 1970-01-01
      • 1970-01-01
      • 2022-09-23
      相关资源
      最近更新 更多