English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
ネットで見つけたIOSコンポーネントPendulumViewが、钟摆のアニメーション効果を実現しています。原生のプログレスバーは見た目が悪いので、カスタムViewを作成してこのような効果を実現したいと考えています。今後はページのロードプログレスバーとしても使用できるでしょう。
余計なことは言わず、まず效果图を紹介します。
下部の黒いエッジは録画中に間違って録画されたもので、無視することができます。
カスタムViewであることを考えると、標準のプロセスに従う。最初のステップは、カスタム属性を作成する。
カスタム属性
属性ファイルを作成する
Androidプロジェクトのres->valuesディレクトリにattrs.xmlファイルを作成し、以下の内容にする:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="PendulumView"> <attr name="globeNum" format="integer"/> <attr name="globeColor" format="color"/> <attr name="globeRadius" format="dimension"/> <attr name="swingRadius" format="dimension"/> </declare-styleable> </resources>
その中でdeclare-styleableのname属性は、コードでこの属性ファイルを参照するために使用される。name属性は、一般的には私たちがカスタムViewのクラス名を書くことが多いが、直感的である。
stylealeを使用すると、システムは私たちが多くの常量(int[]配列、インデックス常量など)の記述を自動的に行い、開発作業を簡素化してくれる。例えば、以下のコードで使用されているR.styleable.PendulumView_golbeNumなどは、システムが自動生成してくれる。
globeNum属性表示小球数量,globeColor表示小球颜色,globeRadius表示小球半径,swingRadius表示摆动半径
读取属性值
在自定view的构造方法中通过TypedArray读取属性值
通过AttributeSet同样可以获取属性值,但是如果属性值是引用类型,则得到的只是ID,仍需继续通过解析ID获取真正的属性值,而TypedArray直接帮助我们完成了上述工作。
public PendulumView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //使用TypedArray读取自定义的属性值 TypedArray ta = context.getResources().obtainAttributes(attrs, R.styleable.PendulumView); int count = ta.getIndexCount(); for (int i = 0; i < count; i++) { int attr = ta.getIndex(i); switch (attr) { case R.styleable.PendulumView_globeNum: mGlobeNum = ta.getInt(attr, 5); break; case R.styleable.PendulumView_globeRadius: mGlobeRadius = ta.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 16, getResources().getDisplayMetrics())); break; case R.styleable.PendulumView_globeColor: mGlobeColor = ta.getColor(attr, Color.BLUE); break; case R.styleable.PendulumView_swingRadius: mSwingRadius = ta.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 16, getResources().getDisplayMetrics())); break; } } ta.recycle(); //次の読み取り時に問題が発生しないように mPaint = new Paint(); mPaint.setColor(mGlobeColor); }
OnMeasure()メソッドをオーバーライドします
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); //高さはボールの半径です+揺れの半径 int height = mGlobeRadius + mSwingRadius; //幅は2*揺れの半径+(ボールの数-1)*ボールの直径 int width = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1) + mSwingRadius; //測定モードがEXACTLYの場合は、推奨値を使用します。それ以外の場合(一般的にはwrap_contentの処理)、独自に計算した幅と高さを使用します setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize : width, (heightMode == MeasureSpec.EXACTLY) ? heightSize : height); }
その中で
int height = mGlobeRadius + mSwingRadius;
<pre name="code" class="java">int width = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1) + mSwingRadius;
AT_MOSTの測定モードを処理するために使用されます。一般的には、Viewの幅と高さの設定がwrap_contentにカスタムされています。この場合、ボールの数、半径、揺れの半径などを計算してViewの幅と高さを決定します。以下の図を参照してください:
ボールの数で5例えば、Viewのサイズは図の赤い矩形エリアです。
onDraw()メソッドをオーバーライド
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //左右のボールを除いた他のボールを描画 for (int i = 0; i < mGlobeNum - 2; i++) { canvas.drawCircle(mSwingRadius + (i + 1) * 2 * mGlobeRadius, mSwingRadius, mGlobeRadius, mPaint); } if (mLeftPoint == null || mRightPoint == null) { //最も左右のボールの座標を初期化 mLeftPoint = new Point(mSwingRadius, mSwingRadius); mRightPoint = new Point(mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1), mSwingRadius); //振動アニメーションを開始 startPendulumAnimation(); } //左右のボールを描画 canvas.drawCircle(mLeftPoint.x, mLeftPoint.y, mGlobeRadius, mPaint); canvas.drawCircle(mRightPoint.x, mRightPoint.y, mGlobeRadius, mPaint); }
onDraw()メソッドはカスタムビューの鍵となります。このメソッドの内部でビューの表示効果を描画します。コードはまず最も左端および右端のボールを除いた他のボールを描画し、次に左右のボールの座標値を判断します。初めて描画する場合、座標値は空であるため、両ボールの座標を初期化し、アニメーションを開始します。最後にmLeftPoint、mRightPointのx、y値を使用して左右のボールを描画します。
mLeftPoint、mRightPointはandroid.graphics.Pointオブジェクトであり、それらを使用して左右の小さなボールのx、y座標情報を保存しています。
属性アニメーションを使用
public void startPendulumAnimation() { //属性アニメーションを使用 final ValueAnimator anim = ValueAnimator.ofObject(new TypeEvaluator() { @Override public Object evaluate(float fraction, Object startValue, Object endValue) { //パラメータfractionはアニメーションの完了度を表し、それに基づいて現在のアニメーションの値を計算 double angle = Math.toRadians(90 * fraction); int x = (int) ((mSwingRadius - mGlobeRadius) * Math.sin(angle)); int y = (int) ((mSwingRadius - mGlobeRadius) * Math.cos(angle)); Point point = new Point(x, y); return point; } }, new Point(), new Point()); anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Point point = (Point) animation.getAnimatedValue(); //現在のfraction値を取得します float fraction = anim.getAnimatedFraction(); //fractionが先に減少してから増加するかどうかを判断し、つまり上向きに振動する準備ができているかどうかを判断 //ボールが上向きに振動するたびにボールを切り替える if (lastSlope && fraction > mLastFraction) { isNext = !isNext; } //左右のボールのx、y座標値を不断に変更することでアニメーション効果を実現します //isNextを使用して、左側のボールか右側のボールが動くべきかを判断します if (isNext) { //左側のボールが振動しているとき、右側のボールは初期位置に置かれます mRightPoint.x = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1); mRightPoint.y = mSwingRadius; mLeftPoint.x = mSwingRadius - point.x; mLeftPoint.y = mGlobeRadius + point.y; } else { //右側のボールが振動しているとき、左側のボールは初期位置に置かれます mLeftPoint.x = mSwingRadius; mRightPoint.y = mSwingRadius; mRightPoint.x = mSwingRadius + (mGlobeNum - 1) * mGlobeRadius * 2 + point.x; mRightPoint.y = mGlobeRadius + point.y; } invalidate(); lastSlope = fraction < mLastFraction; mLastFraction = fraction; } }); //無限ループ再生を設定します anim.setRepeatCount(ValueAnimator.INFINITE); //ループモードを逆再生に設定します anim.setRepeatMode(ValueAnimator.REVERSE); anim.setDuration(200); //補間器を設定し、アニメーションの変化速度を制御します anim.setInterpolator(new DecelerateInterpolator()); anim.start(); }
ここでValueAnimator.ofObjectメソッドを使用するのは、Pointオブジェクトを操作できるようにするためです。さらに、ofObjectメソッドを使用してカスタムのTypeEvaluatorオブジェクトを使用し、fraction値を得ることができます。この値は0から-1変化する小数を取得します。したがって、このメソッドの後2つの引数startValue(new Point()), endValue(new Point())には実際の意味がありません。直接書かなくても良いですが、ここでは理解を助けるために書かれています。同様に、直接ValueAnimator.ofFloat(0f, 1f)メソッドで0から-1変化する小数。
final ValueAnimator anim = ValueAnimator.ofObject(new TypeEvaluator() { @Override public Object evaluate(float fraction, Object startValue, Object endValue) { //パラメータfractionはアニメーションの完了度を表し、それに基づいて現在のアニメーションの値を計算 double angle = Math.toRadians(90 * fraction); int x = (int) ((mSwingRadius - mGlobeRadius) * Math.sin(angle)); int y = (int) ((mSwingRadius - mGlobeRadius) * Math.cos(angle)); Point point = new Point(x, y); return point; } }, new Point(), new Point());
fractionを通じて、ボールが振動する際の角度の変化値を計算し、0-90度
mSwingRadius-mGlobeRadiusは図の緑色の線の長さを表し、振動の経路、ボールの中心の経路は(mSwingRadius-mGlobeRadius)を半径とする弧線、変化するX値は(mSwingRadius-mGlobeRadius)*sin(angle),変化するy値は(mSwingRadius-mGlobeRadius)*cos(angle)
対応するボールの実際の中心座標は(mSwingRadius-x、mGlobeRadius+y)
右側のボールの動きは左側と似ていますが、方向が異なります。右側のボールの実際の中心座標(mSwingRadius + (mGlobeNum - 1) * mGlobeRadius * 2 + x、mGlobeRadius+y)
左右のボールの縦座標は同じですが、横座標が異なります。
float fraction = anim.getAnimatedFraction(); //fractionが先に減少してから増加するかどうかを判断し、つまり上向きに振動する準備ができているかどうかを判断 //ボールが上向きに振動するたびにボールを切り替える if (lastSlope && fraction > mLastFraction) { isNext = !isNext; } //前回のfractionが減少しているかどうかを記録 lastSlope = fraction < mLastFraction; //前回のfractionを記録 mLastFraction = fraction;
これらの二つのコードは、動きを切り替えるタイミングを計算するために使用されており、このアニメーションはループ再生が設定されており、ループモードは逆再生となっているため、アニメーションの一周期はボールが投げ上げられ、落下するプロセスとなります。その過程で、fractionの値は0から1、そして1を0にします。では、アニメーションの新しいサイクルの始まりはいつでしょうか?それはボールが投げ上げられる直前の瞬間であり、その瞬間に動きを変更することで、左側のボールが落ちた後に右側のボールが投げ上げられ、右側のボールが落ちた後に左側のボールが投げ上げられるアニメーション効果を実現します。
この時間点をどうやって捉えることができますか?
ボールが投げ上げられる際にはfraction値が増加し続け、ボールが落ちる際にはfraction値が減少し続けます。ボールが投げ上げられる直前の瞬間、fractionが減少から増加に変化する瞬間が、fractionが増加し続ける瞬間です。コードでは前回のfractionが減少しているかどうかを記録し、今回のfractionが増加しているかどうかを比較し、両方の条件が成立した場合には動きを変更するボールを切り替えます。
anim.setDuration(200); //補間器を設定し、アニメーションの変化速度を制御します anim.setInterpolator(new DecelerateInterpolator()); anim.start();
アニメーションの持続時間を設定します200ミリ秒、読者はこの値を変更することでボールの揺れの速度を変更することができます。
アニメーションの補間器を設定します。ボールが投げ上げられるのは減速する過程であり、落ちるのは加速する過程であるため、DecelerateInterpolatorを使用して減速効果を実現し、逆再生時には加速効果を提供します。
アニメーションを起動し、ペンデュラム効果のカスタムViewプロセスバーが実現しました!すぐに実行して、効果を見てください!
これで本文のすべての内容が終わります。皆様の学習に役立つことを願っています。また、呐喊教程を多くのサポートをお願いします。
声明:本文の内容はインターネットから取得しており、著作権者に帰属します。インターネットユーザーが自発的に貢献し、自己でアップロードしたものであり、本サイトは所有権を持ちません。また、人工的に編集はされていません。著作権侵害が疑われる内容がある場合は、以下のメールアドレスにご連絡ください:notice#oldtoolbag.com(メールを送信する際には、#を@に変更してください。侵害を報告する場合は、関連する証拠を提供し、一旦確認されると、本サイトは直ちに侵害される疑われる内容を削除します。)