ポイントからフェースへ、Buttonのアトリビュートをアニメートするには

12660 ワード

属性アニメーションはAPI 11に追加された新しい特性であり、実際には今でも新しいものはありません.属性アニメーションは任意のviewの属性をアニメーション化することができ、アニメーションを実現する原理は、与えられた時間内に属性を1つの値から別の値に変えることである.そのため、viewにこの属性があれば、属性アニメーションは何でもできると言えます.
ここではButtonについて簡単なプロパティアニメーションを行います.このButtonの幅を変更します.Tween Animationも使えますが、明らかに要求を満たすことができないところはTween AnimationでScaleアニメーション、つまりスケールしかできません.このbuttonをズームして幅を大きくすることができますが、このときボタンの文字もズームや変形します.同時に重要なのは、Tween Animationがviewの本来の位置と大きさを変えないことです.このボタンは大きくなっているように見えますが、アニメーション実行前のボタンをクリックしても上書きされていない位置は効果がありません.
だからどうしてもアトリビュートアニメーションを使います.ここでは最も簡単な方法:ObjectAnimatorを使用してこのアニメーションを作成します.
ObjectAnimator.ofInt(mAnimateButton, "width", mAnimateButton.getWidth(), 1000)
   .setDuration(1000)
   .start();

簡単に見えるボタンのアニメーションが実現しました.しかし、運転中に問題が発生します.なぜなら、属性アニメーションは実行時に指定された属性を変更する必要があるため、ここではwidthの値である.属性に対応するgetWidthsetWidthのメソッドを使用します.getWidthは、アニメーションの初期値が与えられていない場合に、この方法を用いて初期値を得る.setWidthは、所与の時間内に、アニメーションの効果を達成するために属性値を変更するために絶えず使用される.注意、この方法は一度だけではありません.
しかし、ButtongetWidthsetWidthの2つの方法のコードを見てみましょう.
    /**
 * Return the width of the your view.
 *
 * @return The width of your view, in pixels.
 */
    @ViewDebug.ExportedProperty(category = "layout")
    public final int getWidth() {
        return mRight - mLeft;
    }
    /**
 * Makes the TextView exactly this many pixels wide.
 * You could do the same thing by specifying this number in the
 * LayoutParams.
 *
 * @see #setMaxWidth(int)
 * @see #setMinWidth(int)
 * @see #getMinWidth()
 * @see #getMaxWidth()
 *
 * @attr ref android.R.styleable#TextView_width
 */
    @android.view.RemotableViewMethod
    public void setWidth(int pixels) {
        mMaxWidth = mMinWidth = pixels;
        mMaxWidthMode = mMinWidthMode = PIXELS;

        requestLayout();
        invalidate();
    }
setWidthの場合、ボタンlayout paramの幅を所定の値で変更しなかったことは明らかである.
この場合、Googleは3つの解決方法を提供しました.
  • あなたのviewにgetとsetメソッドを追加します.しかし、これはあなたにこの権限が必要です.
  • は、ターゲットviewをクラスでパッケージし、getメソッドとsetメソッドを間接的にこのviewに追加する.
  • は、ValueAnimatorAnimatorUpdateListenerでアニメーションをリスニングし、各タイムスライスのプロパティを自分で変更します.
  • Buttonにgetとsetを追加する方法は現実的ではないので、後の2つしか選択できません.次の2つの方法を紹介します.

    間接的にget、setメソッドを与える


    この方法は簡単に見えますが、クラスを間接的にget、setを与える方法を定義すると、次のようになります.
      class ViewWrapper {
            View mTargetView;
    
            public ViewWrapper(View v) {
                mTargetView = v;
            }
    
            public void setWidth(int width) {
                mTargetView.getLayoutParams().width = width; // 1
                mTargetView.requestLayout();
            }
    
            public int getWidth() {
                int width = mTargetView.getLayoutParams().width; // 2
                return width;
            }
        }
  • アニメーションがlayout paramsの幅を変更する必要がある以上、このsetメソッドではlayout paramsの幅を変更します.
  • はlayout paramsの幅を返します.この値はviewのアニメーション前の幅です.

  • ボタンをクリックして、この幅を修正するアニメーションを開始します.
     @Override
        public void onClick(View v) {
            Log.d("##ViewWrapperActivity", "width is " + v.getWidth());
    
            // 1
            ViewWrapper viewWrapper = new ViewWrapper(v);  
            // 2
            ObjectAnimator animator = ObjectAnimator.ofInt(viewWrapper, "width", /*viewWrapper.getWidth(),*/ 1500);  
            // 3
            animator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                    Log.d("##ANIM", "started");
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    Log.d("##ANIM", "stopped");
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
    
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
    
                }
            });
            // 4
            animator.setDuration(3000).start();
        }
  • は包装類でviewを包装しています.ここはボタンです.
  • はアニメーションを開始し、アニメーションのオブジェクトはパッケージクラスオブジェクトになります.ここでは、アトリビュートアニメーションの定義を変更できます.アトリビュートアニメーションは、任意のオブジェクトに対してアトリビュートを変更できます.ここでのパッケージオブジェクトは明らかにviewではありません.
  • ここにリスナーが追加され、アニメーションのリスニングが開始されたか終了したかが表示されます.
  • アニメーションを開始します.ボタンの幅を3秒で変更し、初期値から1500ピクセル幅に変更します.

  • 完璧に見えますが、このセグメントコードを実行します.ボタンをクリックします.まあ、このアニメーションはおかしくて、「完全」を実行していません.少し動いたが、幅1500ピクセルには達しなかった.アニメーションリスナーAnimatorListenerのメソッドonAnimationEndは既に実行されており、実行完了のロゴも掲げられているが、幅は常に届かない.したがって、アニメーションの実行は「完全」ではありません.
    では、これはなぜですか.まず正しいコードを与えて、皆さんは参考にして考えてみてください.
    public class ViewWrapperActivity extends Activity implements View.OnClickListener {
    
        private Button mAnimateButton;
        // 1
        private ViewWrapper mWrapper;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_view_wrapper);
    
            mAnimateButton = (Button) findViewById(R.id.animate_button);
            mAnimateButton.setOnClickListener(this);
            // 2
            mWrapper = new ViewWrapper(mAnimateButton);
        }
    
        @Override
        public void onClick(View v) {
            Log.d("##ViewWrapperActivity", "width is " + v.getWidth());
    
            ObjectAnimator animator = ObjectAnimator.ofInt(mWrapper, "width", /*viewWrapper.getWidth(),*/ 1500);
            animator.setInterpolator(new LinearInterpolator());
            animator.addListener(new Animator.AnimatorListener() {
                // ...
            });
            animator.setDuration(3000).start();
        }
    }
  • パッケージクラスオブジェクトクラスメンバーを宣言します.
  • は、onCreateの方法でパッケージクラスオブジェクトを初期化する.

  • これにより、一度にアニメーションを指定した幅にすることができます.具体的にはなぜですか?また后ろのコメントで一绪に讨论することを歓迎します.;)

    ValueAnimatorとAnimatorUpdateListenerの組み合わせでアニメーションを実現


    これは簡単です.コードを直接見てください.
        private void performAnimation(final View targetView, final int start, final int end) {
            // 1
            ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
            // 2
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                private final static String ANIM_TAG = "##Value animator";
                private IntEvaluator mIntEvaluator = new IntEvaluator();
    
                @Override
                public void onAnimationUpdate(ValueAnimator animator) {
                    int currentValue = (Integer) animator.getAnimatedValue();
                    Log.d(ANIM_TAG, "current value: " + currentValue);
                    // 3
                    float fraction = animator.getAnimatedFraction();
                    targetView.getLayoutParams().width = mIntEvaluator.evaluate(fraction, start, end);
                    targetView.requestLayout();
                }
            });
            // 4
            valueAnimator.setDuration(1000).start();
        }
  • ValueAnimatorでアニメーションを作ります.ValueAnimatorは実質的に何もしません.だから、後のAnimatorUpdateListenerが太い仕事をする必要があります.ここで指定した1から100までは実質的な役割はありません.ボタンの幅を1から100に変えるわけではありません.後ろのコードはこの点をはっきりと表現しています.
  • AnimatorUpdateListenerを追加します.最も重要なのは方法public void onAnimationUpdate(ValueAnimator animator)でアニメーションを作ることです.このメソッドは、タイムスライスごとに呼び出されます.このメソッドを呼び出すたびに、このボタンの幅に新しい値を設定します.
  • の第3のステップのアルゴリズムは、現在のアニメーションがアニメーション時間全体に占めるタイムスライスの割合を取得することであり、ここではfractionである.次に、このパーセントに基づいて、現在のタイムスライスに対応するボタン幅を計算します.現在の幅=初期幅+fraction*(終了幅-初期幅).これは、コードmIntEvaluator.evaluate(fraction, start, end)の役割を説明する.

  • 完全なコードはここを見てください.ここまで全部説明します.レンガを撮ることを歓迎して、討論を歓迎します!