English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

AndroidのカスタムViewでチェックアニメーション機能を実現

まずは效果图を紹介します

動画

静画像

1. 回顾

【Androidカスタムビュー:精巧なチェックマークアニメーション】前の記事では、コントロールの効果を実現したと言えますが、しかし...しかし...数日後、自分の書いたコードを見直してみると、考え方はまだあるものの、一部のコードはすぐには理解できませんでした...

天よ、これは直ちにリファクタリングしなければなりません。ちょうど、ChangQinという簡友がこのコントロールを模倣して書いていたので、私も同じように実現できると思いました。

2. 深思

コントロールの描画に関する考え方については、前の記事を参照してください。ここでは、前の記事で分析したコントロールの問題点や、どの部分を改善する必要があるかを分析します。

例えば、 円環の進捗を描画 このステップを見ると

//カウンタ
private int ringCounter = 0;
@Override
protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 if (!isChecked) {
  ...
  return;
 }
 //円弧の進捗を描画し、描画ごとに自動的に増やします12単位、つまり円弧がまた回っています12度
 //ここでは、12単位を最初に固定し、後でカスタマイズを実現するために設定を行うことができます
 ringCounter += 12;
 if (ringCounter >= 360) {
  ringCounter = 360;
 }
 canvas.drawArc(mRectF, 90, ringCounter, false, mPaintRing);
 ...
 //強制的に再描画
 postInvalidate();
}

ここでは、ringCounterというカウンタを定義しています。描画時には、以下に基づいて描画します12単位で自増し、以下に達します360に設定することで、進捗の変化をシミュレートできます。

少し考えてみましょう

自増の単位を変更してアニメーション速度の変化を制御するのは難しいです。満足できるように調整するのは難しい場合、アニメーションの速度を速くまたは遅くする根本は時間を制御することです。もし可能なら、時間を使用してアニメーションの速度を制御しますそれはとても便利ですアニメーションは、以下のように分類されます4ステップごとに実行する場合、各ステップのアニメーションを手書きのカウンタで実現する場合、定義する必要があります4メンバー変数またはそれ以上多くのメンバー変数がコードをさらに混乱させるだけですアニメーションに加えると補間器手書きのカウンタは、上記の分析を見ると、受け入れられません

3. 変えろ、変えろ

それでは、どのようにして上記の問題を改善するか、答えはカスタムの属性アニメーションを使用することです。したがって、この記事の主な内容は、属性アニメーションを使用して手書きのカウンタを置き換え、コードのロジックをできるだけ明確に保つことです。特にonDraw()メソッド内のコードに注目してください。

属性アニメーションを使用する利点の一つは、指定された値の範囲で、あなたが望む値を一連生成してくれることです。それに補間器と組み合わせると、予想外の効果も期待できます。次のページでは、アニメーションを実行する部分を一つ一つ順番にリファクタリングします。

3.1 円環進度バーを描画します。

まず、カスタマイズされたObjectAnimatorを使用して進度をシミュレートします。

//ringProgressはカスタマイズされた属性名で、生成する数値の範囲は0です。 - 360は、円の角度です。
ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360);
//アニメーションが実行される時間を定義します。これは、アニメーション速度を制御するために使用していた自増の単位の良い代替品です。
mRingAnimator.setDuration(mRingAnimatorDuration);
//一時的に補間器は必要ありません。
mRingAnimator.setInterpolator(null);

カスタマイズされた属性アニメーションには、setterとgetterの設定も必要です。なぜなら、アニメーションが実行されている間に、setterが適切な値を変更するために呼び出されるからです。

private int getRingProgress() {
 return ringProgress;
}
private void setRingProgress(int ringProgress) {
 //アニメーションが実行されている間、setterが呼び出されます。
 //ここでは、アニメーションが生成する数値を記録し、変数に保存して、ondraw時に使用します。
 this.ringProgress = ringProgress;
 //再描画を忘れずに。
 postInvalidate();
}

最後に、onDraw()内で描画します。

//円弧進度を描画するためにcanvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);

3.2 円心に向かって収縮するアニメーションを描画します。

同様に、属性アニメーションを作成します。

//ここでカスタマイズする属性は、円の収縮半径です。
ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0);
//減速の補間器を追加します。
mCircleAnimator.setInterpolator(new DecelerateInterpolator());
mCircleAnimator.setDuration(mCircleAnimatorDuration);

setter/getterも同様で説明しません。

最後にonDraw()内で描画します。

//背景を描画します。
mPaintCircle.setColor(checkBaseColor);
canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radius : 0, mPaintCircle);
//進度円環が描画されたら、収縮する円を描画します。
if (ringProgress == 360) {
 mPaintCircle.setColor(checkTickColor);
 canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle);
}

3.3 鋸歯状の効果を描画し、拡大して再び収縮する効果を描画します。

これらは独立した効果ですが、ここでは同時に実行するので、まとめて説明します。

まずは属性アニメーションを定義します。

//透明度の徐々に変化するチェックマーク
ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255);
mAlphaAnimator.setDuration(200);
//最後の拡大と回弹のアニメーションは、ペン幅を変更して実現
//そしてペン幅の変化範囲は、
//まずは初期宽度から始まり、初期宽度のn倍まで広がり、最後に初期の宽度に戻ります。
ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES);
mScaleAnimator.setInterpolator(null);
mScaleAnimator.setDuration(mScaleAnimatorDuration);
//チェックマークと拡大のアニメーションを同時に実行
AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet();
mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);

getter/setter

private int getTickAlpha() {
 return 0;
}
private void setTickAlpha(int tickAlpha) {
 //透明度を設定する場合、変数を保存する必要はありません
 //直接透明度の値をペンに設定します
 mPaintTick.setAlpha(tickAlpha);
 postInvalidate();
}
private float getRingStrokeWidth() {
 return mPaintRing.getStrokeWidth();
}
private void setRingStrokeWidth(float strokeWidth) {
 //ペン幅を設定する場合、変数を保存する必要はありません
 //直接ペン幅をペンに設定します
 mPaintRing.setStrokeWidth(strokeWidth);
 postInvalidate();
}

最後に、同様にonDraw()で描画します

if (circleRadius == 0) {
 canvas.drawLines(mPoints, mPaintTick);
 canvas.drawArc(mRectF, 0, 360, false, mPaintRing);
}

3.4 アニメーションを順次実行します

複数のアニメーションを実行する場合、AnimatorSetを使用できます。playTogether()は同時に実行し、playSequentially()は一つずつ、ステップバイステップで実行します。

mFinalAnimatorSet = new AnimatorSet();
mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);

最後にonDraw()でアニメーションを実行します

//ここでは、アニメーションが一度だけ実行されることをプログラムに伝える識別子を定義しています
if (!isAnimationRunning) {
 isAnimationRunning = true;
 //アニメーションの実行
 mFinalAnimatorSet.start();
}

3.5 各メソッドには単一の責務があるべきです

属性アニメーションの定義方法をonDraw()に置くと、個人的にはごちゃごちゃしてしまい、さらに詳しく見ると、これらの属性アニメーションは動的に変化する必要がないので、なぜ最初に一度に初期化しないのかと感じます

したがって、属性アニメーションのコードを抽出し、コンストラクタ内で初期化します

public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 ...
 initAnimatorCounter();
}
/**
 * ObjectAnimatorを使用してカウンタを初期化する
 */
private void initAnimatorCounter() {
 //円環進捗
 ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360);
 ...
 //収縮アニメーション
 ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0);
 ...
 //透明度の徐々に変化するチェックマーク
 ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255);
 ...
 //最後の拡大と回弹のアニメーションは、ペン幅を変更して実現
 ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES);
 ...
 //チェックマークと拡大のアニメーションを同時に実行
 AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet();
 mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);
 mFinalAnimatorSet = new AnimatorSet();
 mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);
}

最後に、onDraw()メソッドは単なる描画を担当し、何も管理しません

@Override
protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 if (!isChecked) {
  canvas.drawArc(mRectF, 90, 360, false, mPaintRing);
  canvas.drawLines(mPoints, mPaintTick);
  return;
 }
 //円弧進捗を描画
 canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);
 //黄色の背景を描画
 mPaintCircle.setColor(checkBaseColor);
 canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radius : 0, mPaintCircle);
 //縮小する白色の円を描画
 if (ringProgress == 360) {
  mPaintCircle.setColor(checkTickColor);
  canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle);
 }
 //勾を描画し、拡大縮小のアニメーション
 if (circleRadius == 0) {
  canvas.drawLines(mPoints, mPaintTick);
  canvas.drawArc(mRectF, 0, 360, false, mPaintRing);
 }
 //ObjectAnimator アニメーションをカウンターレプラース
 if (!isAnimationRunning) {
  isAnimationRunning = true;
  mFinalAnimatorSet.start();
 }
}

最終的な効果は同じですが、コードのロジックが一目でわかります。

だから、個人的には、開発中に自分のコードを定期的にレビューすることは、自分自身や将来のメンテナンスにとって非常に有益だと思います。

That's all~皆さんに読んでいただきありがとうございます。最後にプロジェクトのgithubアドレスをもう一度ご紹介します。

> Githubアドレス:TickView、精巧なチェックアニメーションhttps://github.com/ChengangFeng/TickView

これで、編集者が皆さんに整理したカスタムビューでチェックアニメーション機能に関する全ての内容が揃いました。皆さんでテストしてみてください。何か質問があれば、下のコメント欄で相談してください。ナイアガラガイドのサポートに感謝します。

おすすめ