円弧を動かして、バッテリーが50%とか、ターゲットの90%などの表現をしたい場合には静止画ではなくアニメーションを付けてみたくなります。ImageViewを使ったパラパラ漫画的なアニメーションがありますが、Canvasではもっとスムーズなアニメーションを作れます。
ボタンを押すと88%まで回転する例です
2024.1.1
Animation
ButtonやImageViewをアニメーションさせるにはProperty Animationやandroid.view.animationを使うと簡単ですが、込み入ったアニメーション(円弧のアニメとか)は難しいところがあります。
Aimationクラスを継承してCanvasで描画する方法はある程度複雑な動きもカスタムで作りだせます。
Simple な Animation
簡単なAnimationを継承したクラスを使ってみます。
Canvasにある矩形(画像)が落下するアニメーションを考えてみます。
矩形は (left, top, right, bottom)で位置決めしますので、このY軸に相当するtop, bottomの位置を変化させて、矩形が下に落ちていくアニメーションにすればいいわけです。
アニメーションを実現させるためにタイマーを使う方法もありますが、Animation classにはsetDurationで時間を設定するメソッドがあります。
1 2 |
// アニメーションの起動期間を5秒に設定 testAnimation.setDuration(5000); |
applyTransformationを使ってインターバルと位置を調整します。
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
package com.example.testcanvasanimationsimple; import android.os.Bundle; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.View; public class MainActivity extends AppCompatActivity { private int yval = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); // setContentView(R.layout.activity_main); // ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { // Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); // v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); // return insets; // }); TestView testView = new TestView(this); setContentView(testView); // 最終position int endPosition = 1000; TestAnimation testAnimation = new TestAnimation(testView, endPosition); // アニメーションの起動期間を設定 testAnimation.setDuration(5000); testView.startAnimation(testAnimation); } public class TestView extends View { Paint paint = new Paint(); public TestView(Context context) { super(context); } @Override protected void onDraw(Canvas canvas) { paint.setColor(Color.argb(255, 125, 125, 125)); // (left, top, right, bottom) 左上(400, 100)を起点に幅200の矩形 canvas.drawRect(400, 100+yval, 600, 300+yval, paint); } public int getPosition() { return yval; } public void setPositon(int pos) { yval = pos; } } } |
最終到達位置を引数endPositionとして入れます
testAnimation = new TestAnimation(testView, endPosition);
アニメーションの起動期間を設定しています
testAnimation.setDuration(5000);
アニメーションの開始です
testView.startAnimation(testAnimation);
Animationを継承したクラスを作成します。
TestAnimation.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package com.example.testcanvasanimationsimple; import android.view.animation.Animation; import android.view.animation.Transformation; public class TestAnimation extends Animation { private final int currentPosition; private final int endPosition; private final MainActivity.TestView testView; TestAnimation(MainActivity.TestView testView, int pos) { currentPosition = testView.getPosition(); endPosition = pos; this.testView = testView; } @Override protected void applyTransformation( float interpolatedTime, Transformation transformation) { // interpolatedTime: 0.f -> 1.0f int pp = (int)((endPosition-currentPosition)*interpolatedTime); // 矩形のY軸位置をセット testView.setPositon(pp); testView.requestLayout(); } } |
矩形が落下するアニメーションができました。
applyTransformation(float interpolatedTime…
このinterpolatedTimeが0から1.0まで設定した時間で増加していきますので
そのプログレスに応じたY軸の位置を設定することになります
Arc のアニメーション作成
次は円弧が段々増加するアニメーションです。
View を継承した Canvas クラスを作成し、アニメーションさせる円弧を設定します。別にアニメーションさせるためにAnimationを継承したAnimationArcクラスを作成。
1 |
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint); |
ここではpackage 名は「com.example.testcanvasanimationarc」です
Arc.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
//package com.example.testcanvasanimationarc; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; public class Arc extends View{ private final Paint paint; private final RectF rect; // Animation 開始地点をセット private static final int AngleTarget = 270; // 初期 Angle private float angle = 10; public Arc(Context context, AttributeSet attrs) { super(context, attrs); // Arcの幅 int strokeWidth = 60; paint = new Paint(); paint.setAntiAlias(true); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(strokeWidth); // Arcの色 paint.setColor(Color.argb(255, 0, 0, 255)); // Arcの範囲 rect = new RectF(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 背景、半透明 canvas.drawColor(Color.argb(64, 0, 0, 255)); // Canvas 中心点 float x = (float)getWidth()/2; float y = (float)getHeight()/2; // 半径 float radius = (float)getWidth()/4; // 円弧の領域設定 rect.set(x - radius, y - radius, x + radius, y + radius); // 円弧の描画 canvas.drawArc(rect, AngleTarget, angle, false, paint); } // AnimationAへ現在のangleを返す public float getAngle() { return angle; } // AnimationAから新しいangleが設定される public void setAngle(float angle) { this.angle = angle; } } |
Animation を継承したクラスを作ります。ここでアニメーションの開始、終了、描画する対称を規定しています。
AnimationArc.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
//package com.example.testcanvasanimationarc; import android.view.animation.Animation; import android.view.animation.Transformation; public class AnimationArc extends Animation { private final Arc arc; // アニメーション角度 private final float oldAngle; private final float newAngle; AnimationArc(Arc arc, int newAngle) { this.oldAngle = arc.getAngle(); this.newAngle = newAngle; this.arc = arc; } @Override protected void applyTransformation( float interpolatedTime, Transformation transformation) { float angle = oldAngle + ((newAngle - oldAngle) * interpolatedTime); arc.setAngle(angle); arc.requestLayout(); } } |
MainActivity でアニメーションに期間などの情報を入れボタンで起動するようにします
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
//package com.example.testcanvasanimationarc; import android.os.Bundle; import android.widget.Button; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; public class MainActivity extends AppCompatActivity { private Arc arc; private int endAngle = 0; private final int animationPeriod = 2000; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); // 88%に角度を合わせる endAngle = 88*360/100; arc = findViewById(R.id.arc); Button buttonStart = findViewById(R.id.button_start); buttonStart.setOnClickListener(v -> { AnimationArc animation = new AnimationArc(arc, endAngle); // アニメーションの起動期間を設定 animation.setDuration(animationPeriod); arc.startAnimation(animation); }); Button buttonReset = findViewById(R.id.button_reset); buttonReset.setOnClickListener(v -> { AnimationArc animation = new AnimationArc(arc, 0); animation.setDuration(0); arc.startAnimation(animation); }); } } |
レイアウトにカスタムCanvasを挿入して表示させます
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.example.testcanvasanimationarc.Arc android:id="@+id/arc" android:layout_width="200dp" android:layout_height="200dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button_start" android:text="@string/start" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.88" app:layout_constraintHorizontal_bias="0.3" /> <Button android:id="@+id/button_reset" android:text="@string/reset" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.88" app:layout_constraintHorizontal_bias="0.7" /> </androidx.constraintlayout.widget.ConstraintLayout> |
リソース
strings.xml
1 2 3 4 5 6 |
<resources> <string name="app_name">TestCanvasAnimationArc</string> <string name="description">a sample image</string> <string name="start">Start</string> <string name="reset">Reset</string> </resources> |
関連ページ:
- Canvas と Paint で円や矩形を描く
- Custom Canvas をレイアウトに挿入する
- Canvas を Clear して再描画
- Canvas で画像と文字を表示する
- Canvas Animation で円弧を動かす
References:
Canvas | Android Developers
Canvas and Drawables | Android Developers