Pre-Lデバイス用のRippleDrawable

画像



良い一日!



Google IO / 2014をフォローした人は、新しいマテリアルデザインと新機能を知っています。 それらの1つは、押されたときの脈動効果です。 昨日、古いデバイスに移植することにしました。



Android Lは新しい効果に変わりました-脈動、タッチに応じてデフォルトで使用されます。 つまり、画面をタッチすると、親レイヤーのサイズとともに大きなフェード楕円が表示され、接触点で円が成長します。 このアニメーションに触発されて自分のプロジェクトで使用できるようになりました。







Google Designのアニメーションの例。



ヘルパークラスCircleを使用してRippleDrawableクラスを作成しましょう。これは、円を描くのに役立ちます。



class RippleDrawable extends Drawable{ final static class Circle{ float cx; // x    float cy; // y    float radius; //   /** *   * * @param canvas Canvas   * @param paint Paint       */ public void draw(Canvas canvas, Paint paint){ canvas.drawCircle(cx, cy, radius, paint); } } }
      
      







タッチポイントを保存するには、Circle補助要素が必要です。 ここで、2つの円が必要です。親全体を覆う背景円と、タッチポイントを表示する小さい円です。 そうそう、定数を宣言します。デフォルトのアニメーション値は250ミリ秒、デフォルトの円の半径は150ピクセルです。 背景の円を増やす回数、メモ、すべての数字は目で見て取られます。



  class RippleDrawable extends Drawable{ final static int DEFAULT_ANIM_DURATION = 250; final static float END_RIPPLE_TOUCH_RADIUS = 150f; final static float END_SCALE = 1.3f; //    Circle mTouchRipple; //   Circle mBackgroundRipple; //    "  " Paint mRipplePaint = new Paint(Paint.ANTI_ALIAS_FLAG); //     Paint mRippleBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
      
      







Paint.ANTI_ALIAS_FLAGフラグはスムージングを目的としているため、円はある種の塗りつぶしではなく円であるため、別の方法で変数を初期化し、ペイントスタイルが「塗りつぶし」であることを示してから、コンストラクタで呼び出します。



 void initRippleElements(){ mTouchRipple = new Circle(); mBackgroundRipple = new Circle(); mRipplePaint.setStyle(Paint.Style.FILL); mRippleBackgroundPaint.setStyle(Paint.Style.FILL); }
      
      







おそらく、最も興味深いタッチ処理に進み、OnTouchListenerインターフェイスをクラスに追加します。



  class RippleDrawable extends Drawable implements OnTouchListener{ ... @Override public boolean onTouch(View v, MotionEvent event) { //    final int action = event.getAction(); //        switch (action){ //    case MotionEvent.ACTION_DOWN: onFingerDown(v, event.getX(), event.getY()); //      View      return v.onTouchEvent(event); //      (  ) case MotionEvent.ACTION_MOVE: onFingerMove(event.getX(), event.getY()); break; //     case MotionEvent.ACTION_UP: onFingerUp(); break; } return false; } ...
      
      





画面をタッチするとき、最初にタッチ座標を円で保存し、ビューのサイズ(背景の円の場合)を保存してから、アニメーションが開始されていない場合は開始します。 ちなみに、両方の円には不透明度がありますが、背景の円では100、小さい円では160から40と定義しました。 すべての数値は再び天井から取得されました(鋭い目)(誰かが理解していない場合、数値は0〜255 argbです)。



  int mViewSize = 0; void onFingerDown(View v, float x, float y){ mTouchRipple.cx = mBackgroundRipple.cx = x; mTouchRipple.cy = mBackgroundRipple.cy = y; mTouchRipple.radius = mBackgroundRipple.radius = 0f; mViewSize = Math.max(v.getWidth(), v.getHeight()); //       if(mCurrentAnimator == null){ //         //       mRippleBackgroundPaint.setAlpha(RIPPLE_BACKGROUND_ALPHA); //  ,   CREATE_TOUCH_RIPPLE     //     mCurrentAnimator = ObjectAnimator.ofFloat(this, CREATE_TOUCH_RIPPLE, 0f, 1f); mCurrentAnimator.setDuration(DEFAULT_ANIM_DURATION); } //          if(!mCurrentAnimator.isRunning()){ mCurrentAnimator.start(); } } //  ,   ObjectAnimator float mAnimationValue; /** * ObjectAnimator    * * @param value    0  1 */ void createTouchRipple(float value){ mAnimationValue = value; // step by step  ,   40px mTouchRipple.radius = 40f + (mAnimationValue * (END_RIPPLE_TOUCH_RADIUS - 40f)); mBackgroundRipple.radius = mAnimationValue * (mViewSize * END_SCALE); //       , //      opacity , //         int min = RIPPLE_TOUCH_MIN_ALPHA; int max = RIPPLE_TOUCH_MAX_ALPHA; int alpha = min + (int) (mAnimationValue * (max - min)); mRipplePaint.setAlpha((max + min) - alpha); //  invalidateSelf(); }
      
      







ユーザーがタッチすると、ユーザーと背景の2つの円が表示されますが、それらは消えず、指が動いても動かないので、修正します。



  void onFingerMove(float x, float y){ mTouchRipple.cx = x; mTouchRipple.cy = y; invalidateSelf(); }
      
      







チェックしてください、今は円が動いていますよね?



トリガーから、つまり画面から指を離すときのロジック。 アニメーションが開始された場合、それを終了して最終状態にする必要があります。その後、アニメーションを開始し、円を消します。ユーザーの円が同時に増加し、消えます。



 void onFingerUp(){ //   if(mCurrentAnimator != null) { mCurrentAnimator.end(); mCurrentAnimator = null; createTouchRipple(1f); } //  ,      mCurrentAnimator = ObjectAnimator.ofFloat(this, DESTROY_TOUCH_RIPPLE, 0f, 1f); mCurrentAnimator.setDuration(DEFAULT_ANIM_DURATION); mCurrentAnimator.addListener(new SimpleAnimationListener(){ @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mCurrentAnimator = null; } }); mCurrentAnimator.start(); } void destroyTouchRipple(float value){ //    mAnimationValue = value; //       mTouchRipple.radius = END_RIPPLE_TOUCH_RADIUS + (mAnimationValue * (mViewSize * END_SCALE)); //         mRipplePaint.setAlpha((int) (RIPPLE_TOUCH_MIN_ALPHA - (mAnimationValue * RIPPLE_TOUCH_MIN_ALPHA))); mRippleBackgroundPaint.setAlpha ((int) (RIPPLE_BACKGROUND_ALPHA - (mAnimationValue * RIPPLE_BACKGROUND_ALPHA))); //      ? invalidateSelf(); }
      
      







アニメーションの準備ができました。安全に確認できます。



ソースコード
 import android.animation.Animator; import android.animation.ObjectAnimator; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.Property; import android.view.MotionEvent; import android.view.View; public class RippleDrawable extends Drawable implements View.OnTouchListener{ final static Property<RippleDrawable, Float> CREATE_TOUCH_RIPPLE = new FloatProperty<RippleDrawable>("createTouchRipple") { @Override public void setValue(RippleDrawable object, float value) { object.createTouchRipple(value); } @Override public Float get(RippleDrawable object) { return object.getAnimationState(); } }; final static Property<RippleDrawable, Float> DESTROY_TOUCH_RIPPLE = new FloatProperty<RippleDrawable>("destroyTouchRipple") { @Override public void setValue(RippleDrawable object, float value) { object.destroyTouchRipple(value); } @Override public Float get(RippleDrawable object) { return object.getAnimationState(); } }; final static int DEFAULT_ANIM_DURATION = 250; final static float END_RIPPLE_TOUCH_RADIUS = 150f; final static float END_SCALE = 1.3f; final static int RIPPLE_TOUCH_MIN_ALPHA = 40; final static int RIPPLE_TOUCH_MAX_ALPHA = 120; final static int RIPPLE_BACKGROUND_ALPHA = 100; Paint mRipplePaint = new Paint(Paint.ANTI_ALIAS_FLAG); Paint mRippleBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); Circle mTouchRipple; Circle mBackgroundRipple; ObjectAnimator mCurrentAnimator; Drawable mOriginalBackground; public RippleDrawable() { initRippleElements(); } public static void createRipple(View v, int primaryColor){ RippleDrawable rippleDrawable = new RippleDrawable(); rippleDrawable.setDrawable(v.getBackground()); rippleDrawable.setColor(primaryColor); rippleDrawable.setBounds(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom()); v.setOnTouchListener(rippleDrawable); if(Build.VERSION.SDK_INT >= 16) { v.setBackground(rippleDrawable); }else{ v.setBackgroundDrawable(rippleDrawable); } } public static void createRipple(int x, int y, View v, int primaryColor){ if(!(v.getBackground() instanceof RippleDrawable)) { createRipple(v, primaryColor); } RippleDrawable drawable = (RippleDrawable) v.getBackground(); drawable.setColor(primaryColor); drawable.onFingerDown(v, x, y); } /** * Set colors of ripples * * @param primaryColor color of ripples */ public void setColor(int primaryColor){ mRippleBackgroundPaint.setColor(primaryColor); mRippleBackgroundPaint.setAlpha(RIPPLE_BACKGROUND_ALPHA); mRipplePaint.setColor(primaryColor); invalidateSelf(); } /** * set first layer you background drawable * * @param drawable original background */ public void setDrawable(Drawable drawable){ mOriginalBackground = drawable; invalidateSelf(); } void initRippleElements(){ mTouchRipple = new Circle(); mBackgroundRipple = new Circle(); mRipplePaint.setStyle(Paint.Style.FILL); mRippleBackgroundPaint.setStyle(Paint.Style.FILL); } @Override public void draw(Canvas canvas) { if(mOriginalBackground != null){ mOriginalBackground.setBounds(getBounds()); mOriginalBackground.draw(canvas); } mBackgroundRipple.draw(canvas, mRippleBackgroundPaint); mTouchRipple.draw(canvas, mRipplePaint); } @Override public void setAlpha(int alpha) {} @Override public void setColorFilter(ColorFilter cf) {} @Override public int getOpacity() { return 0; } @Override public boolean onTouch(View v, MotionEvent event) { //    final int action = event.getAction(); //        switch (action){ //    case MotionEvent.ACTION_DOWN: onFingerDown(v, event.getX(), event.getY()); //      View      return v.onTouchEvent(event); //      (  ) case MotionEvent.ACTION_MOVE: onFingerMove(event.getX(), event.getY()); break; //     case MotionEvent.ACTION_UP: onFingerUp(); break; } return false; } int mViewSize = 0; void onFingerDown(View v, float x, float y){ mTouchRipple.cx = mBackgroundRipple.cx = x; mTouchRipple.cy = mBackgroundRipple.cy = y; mTouchRipple.radius = mBackgroundRipple.radius = 0f; mViewSize = Math.max(v.getWidth(), v.getHeight()); //       if(mCurrentAnimator == null){ //         //       mRippleBackgroundPaint.setAlpha(RIPPLE_BACKGROUND_ALPHA); //  ,   CREATE_TOUCH_RIPPLE     //     mCurrentAnimator = ObjectAnimator.ofFloat(this, CREATE_TOUCH_RIPPLE, 0f, 1f); mCurrentAnimator.setDuration(DEFAULT_ANIM_DURATION); } //          if(!mCurrentAnimator.isRunning()){ mCurrentAnimator.start(); } } float mAnimationValue; /** * ObjectAnimator    * * @param value    0  1 */ void createTouchRipple(float value){ mAnimationValue = value; // step by step  ,   40px mTouchRipple.radius = 40f + (mAnimationValue * (END_RIPPLE_TOUCH_RADIUS - 40f)); mBackgroundRipple.radius = mAnimationValue * (mViewSize * END_SCALE); //       , //      opacity , //         int min = RIPPLE_TOUCH_MIN_ALPHA; int max = RIPPLE_TOUCH_MAX_ALPHA; int alpha = min + (int) (mAnimationValue * (max - min)); mRipplePaint.setAlpha((max + min) - alpha); //  invalidateSelf(); } void destroyTouchRipple(float value){ //    mAnimationValue = value; //       mTouchRipple.radius = END_RIPPLE_TOUCH_RADIUS + (mAnimationValue * (mViewSize * END_SCALE)); //         mRipplePaint.setAlpha((int) (RIPPLE_TOUCH_MIN_ALPHA - (mAnimationValue * RIPPLE_TOUCH_MIN_ALPHA))); mRippleBackgroundPaint.setAlpha ((int) (RIPPLE_BACKGROUND_ALPHA - (mAnimationValue * RIPPLE_BACKGROUND_ALPHA))); //      ? invalidateSelf(); } float getAnimationState(){ return mAnimationValue; } void onFingerUp(){ //   if(mCurrentAnimator != null) { mCurrentAnimator.end(); mCurrentAnimator = null; createTouchRipple(1f); } //  ,      mCurrentAnimator = ObjectAnimator.ofFloat(this, DESTROY_TOUCH_RIPPLE, 0f, 1f); mCurrentAnimator.setDuration(DEFAULT_ANIM_DURATION); mCurrentAnimator.addListener(new SimpleAnimationListener(){ @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mCurrentAnimator = null; } }); mCurrentAnimator.start(); } void onFingerMove(float x, float y){ mTouchRipple.cx = x; mTouchRipple.cy = y; invalidateSelf(); } @Override public boolean setState(int[] stateSet) { if(mOriginalBackground != null){ return mOriginalBackground.setState(stateSet); } return super.setState(stateSet); } @Override public int[] getState() { if(mOriginalBackground != null){ return mOriginalBackground.getState(); } return super.getState(); } final static class Circle{ float cx; float cy; float radius; public void draw(Canvas canvas, Paint paint){ canvas.drawCircle(cx, cy, radius, paint); } } }
      
      









要約すると:







Githubのプロジェクト



All Articles