こんにちは、私の名前はビクターです。Exyteで働いています。 最近、私たちはオープンソースで社内開発を投稿しました-ベクターグラフィックスとそのアニメーションMacawを扱うためのライブラリです。 実際のプロジェクトでそれを使用した印象を共有し、ネイティブAPIに対する利点についてお話したいと思います。
開発者として、多くの場合、カスタムコントロールを作成し、単純な効果でも同じルーチンを繰り返す必要があります。
- UIViewから継承してdrawRectをオーバーライドします
- 非推奨のCore Graphics APIを使用してシーンを説明します
カスタムコントロールを作成して、例として使用してみましょう。
let circlePath1 = UIBezierPath( arcCenter: center, radius: r, startAngle: offset, endAngle: offset + CGFloat(angle), clockwise: true) let circlePath2 = UIBezierPath( arcCenter: center, radius: r, startAngle: offset, endAngle: offset + CGFloat(angle), clockwise: false) UIColor.init(colorLiteralRed: 0.784, green: 0.784, blue: 0.784, alpha: 1.0).setStroke() UIColor.clear.setFill() circlePath1.lineWidth = 10.0 circlePath1.stroke() UIColor.white.setStroke() circlePath2.lineWidth = 10.0 circlePath2.stroke()
私たちはこのルーチンを取り除くことにし、コンゴウインコを作成しました。 それを使用して、上記のシーンをシンプルで機能的なスタイルで説明できます。
let circle = Circle(cx: Double(center.x), cy: Double(center.y), r: r) self.node = [ circle.arc(shift: -1.0 * M_PI_2, extent: angle) .stroke(fill: Color.rgb(r: 200, g: 200, b: 200), width: 10.0), circle.arc(shift: -1.0 * M_PI_2 + angle, extent: 2 * M_PI - angle) .stroke(fill: Color.white, width: 10.0) ].group()
左:Core Graphics、右:コンゴウインコ
ご覧のとおり、シーンはモデルの形で表示され、簡単に再利用または変更できます。 たとえば、グループノードを使用すると、相対的な特性(位置、透明度など)を持つオブジェクトの大きな階層を作成できます。
let sceneNode = [ shape1, shape2, ... [ subshape1, subshape2 .... [...].group() ].group(place: Transform.move(dx: 10.0, dy: 10.0).scale(sx: 0.5, sy: 0.5), opacity: 0.9) ].group()
「クリーンな」Core Graphics APIを使用して、そのようなシーンを作成するのにどれだけの労力がかかるか想像することさえ困難です。 この美しさを使用するには、MacawViewを継承するか、MacawViewをコンテナとして使用します。 その後、「マジック」がすぐに使用できるようになります。たとえば、モデルの変更により、コンテンツの再描画が自動的にカットされます。
しかし、美しい効果を作成するには、もう1つ、アニメーションが必要です。 Core Graphicsを使用して、2つの方法があります:
- CAShapeLayerとCABasicAnimationの組み合わせ。 とても簡単ですが、コードはまだ非推奨に見えます:
let scaleTransform = CGAffineTransform.init(scaleX: 0.1, y: 0.1) let scaleAnimation = CABasicAnimation(keyPath: "transform") scaleAnimation.toValue = CATransform3DMakeAffineTransform(scaleTransform) scaleAnimation.duration = 1.0 scaleAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) scaleAnimation.autoreverses = true self.layer.add(scaleAnimation, forKey: "scale_animation")
- CADisplayLinkからのコールバックを使用して、UIViewのコンテンツを手動で再描画します。 しかし、開発者として、私は単純な効果のためにそのような極端に行きたくありません。
コンゴウインコを使用すると、この種のアニメーションはより良くなります。
let scaleAnimation = self.node.placeVar.animation(to: GeomUtils.centerScale(node: self.node, sx: 0.1, sy: 0.1), during: 2.0).easing(.easeOut) scaleAnimation.autoreversed().play()
はい、これがすべてのコードです。 純粋なCore Graphicsと比較してオーバーヘッドはありますか? 内部では、MacawはCAKeyframeAnimationを使用し、アニメーションフレームの計算には(モデルの複雑さに応じて)少し時間がかかります。 残りはCore Graphicsへの同じ呼び出しです。
さて、シーンコンテンツのアニメーションについてはどうでしょう。 モデルオブジェクトの状態をアニメートしたり、モデルツリー全体をアニメートしたりすることは可能ですか? 残念ながら、このプロセスを何らかの形で最適化する方法はありません。唯一の解決策は、モデル全体を手動で再描画することです。 幸いなことに、Macawにはこのための非常に便利なAPIがあります。
オブジェクトのツリーを簡単に作成できるように、制御コードをリファクタリングしましょう。
contentNode(angle: Double) -> [Node] { ... let circle = Circle(cx: Double(center.x), cy: Double(center.y), r: r) let text = Text(text: "\(value)", font: Font(name: "System", size: 38), fill: Color.white) let textCenter = GeomUtils.center(node: text) text.place = Transform.move(dx: Double(center.x) - textCenter.x, dy: Double(center.y) - textCenter.y) return [ text, circle.arc(shift: -1.0 * M_PI_2, extent: angle) .stroke(fill: Color.rgb(r: 200, g: 200, b: 200), width: 10.0), circle.arc(shift: -1.0 * M_PI_2 + angle, extent: 2 * M_PI - angle) .stroke(fill: Color.white, width: 10.0) ] } ... self.node = contentNode(angle: angle).group()
これで、モデル内のサブツリーコンテンツを各フレームに置き換えることで、コンテンツアニメーションを作成できます。
guard let rootNode = self.node as? Group else { return } rootNode.contentsVar.animate({ t -> [Node] in return self.contentNode(angle: 2 * M_PI * t) }, during: 2.0)
シーンが複雑な場合でも、アニメーション中に変更しているサブツリーの領域のみが再描画されます。
ご覧のとおり、Macawを使用すると、「クリーン」なコアグラフィックスのパフォーマンスを実現できますが、コードはより読みやすく、保守が容易です。 多くは解析されていませんが、このレビューがあなたのプロジェクトで私たちのライブラリを使うきっかけになることを願っています。 私たちは常に改善に取り組んでおり、あなたのアドバイスや支援を喜んで受けます。