package com.naman14.timber.widgets; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.View; public class PlayPauseButton extends View { /** * √3 */ private final static double SQRT_3 = Math.sqrt(3); /** * Animationの速度の倍率 */ private final static int SPEED = 1; /** * 座標の基準を * ・中心を(0,0) * ・右上(1,1) 右下(1,ー1) 左上(ー1,1) 左下(ー1,ー1) * の座標時期に変換するためのClass */ private final Point mPoint; /** * 描画するPaint */ private Paint mPaint; /** * 左半分のPath */ private Path mLeftPath; /** * 右半分のPath */ private Path mRightPath; /** * 真ん中のAnimator */ private ValueAnimator mCenterEdgeAnimator; /** * 一番左側のAnimator */ private ValueAnimator mLeftEdgeAnimator; /** * 一番右側のAnimator */ private ValueAnimator mRightEdgeAnimator; /** * ボタンの状況 */ private boolean mPlayed; /** * ボタンの色 */ private int mBackgroundColor = Color.BLACK; /** * AnimatorのUpdateListener */ private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { invalidate(); } }; private OnControlStatusChangeListener mListener; /** * コンストラクタ * {@inheritDoc} */ public PlayPauseButton(Context context) { this(context, null, 0); } /** * コンストラクタ * {@inheritDoc} */ public PlayPauseButton(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * コンストラクタ * android:backgroundのデータを取得しボタンの色とする * {@inheritDoc} */ public PlayPauseButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mPoint = new Point(); initView(); } /** * Viewの初期化 */ private void initView() { setUpPaint(); setUpPath(); setUpAnimator(); } /** * Animatorの初期化 * それぞれのAnimatorに初期値をセットし、startさせている */ private void setUpAnimator() { if (mPlayed) { mCenterEdgeAnimator = ValueAnimator.ofFloat(1.f, 1.f); mLeftEdgeAnimator = ValueAnimator.ofFloat((float) (-0.2f * SQRT_3), (float) (-0.2f * SQRT_3)); mRightEdgeAnimator = ValueAnimator.ofFloat(1.f, 1.f); } else { mCenterEdgeAnimator = ValueAnimator.ofFloat(0.5f, 0.5f); mLeftEdgeAnimator = ValueAnimator.ofFloat(0.f, 0.f); mRightEdgeAnimator = ValueAnimator.ofFloat(0.f, 0.f); } mCenterEdgeAnimator.start(); mLeftEdgeAnimator.start(); mRightEdgeAnimator.start(); } /** * Paintの初期化 */ private void setUpPaint() { mPaint = new Paint(); mPaint.setColor(mBackgroundColor); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); } /** * Pathの初期化 */ private void setUpPath() { mLeftPath = new Path(); mRightPath = new Path(); } /** * 描画処理 * {@inheritDoc} */ @Override protected void onDraw(Canvas canvas) { mPoint.setHeight(canvas.getHeight()); mPoint.setWidth(canvas.getWidth()); mLeftPath.reset(); mRightPath.reset(); //左半分Pathの設定 mLeftPath.moveTo(mPoint.getX(-0.5 * SQRT_3), mPoint.getY(1.f)); mLeftPath.lineTo(mPoint.getY((Float) mLeftEdgeAnimator.getAnimatedValue()) + 0.7f, mPoint.getY((Float) mCenterEdgeAnimator.getAnimatedValue())); mLeftPath.lineTo(mPoint.getY((Float) mLeftEdgeAnimator.getAnimatedValue()) + 0.7f, mPoint.getY(-1 * (Float) mCenterEdgeAnimator.getAnimatedValue())); mLeftPath.lineTo(mPoint.getX(-0.5 * SQRT_3), mPoint.getY(-1.f)); //右半分Pathの設定 mRightPath.moveTo(mPoint.getY(-1 * (Float) mLeftEdgeAnimator.getAnimatedValue()), mPoint.getY((Float) mCenterEdgeAnimator.getAnimatedValue())); mRightPath.lineTo(mPoint.getX(0.5 * SQRT_3), mPoint.getY((Float) mRightEdgeAnimator.getAnimatedValue())); mRightPath.lineTo(mPoint.getX(0.5 * SQRT_3), mPoint.getY(-1 * (Float) mRightEdgeAnimator.getAnimatedValue())); mRightPath.lineTo(mPoint.getY(-1 * (Float) mLeftEdgeAnimator.getAnimatedValue()), mPoint.getY(-1 * (Float) mCenterEdgeAnimator.getAnimatedValue())); canvas.drawPath(mLeftPath, mPaint); canvas.drawPath(mRightPath, mPaint); } /** * アニメーションを開始する */ public void startAnimation() { mCenterEdgeAnimator = ValueAnimator.ofFloat(1.f, 0.5f); mCenterEdgeAnimator.setDuration(100 * SPEED); mCenterEdgeAnimator.addUpdateListener(mAnimatorUpdateListener); mLeftEdgeAnimator = ValueAnimator.ofFloat((float) (-0.2 * SQRT_3), 0.f); mLeftEdgeAnimator.setDuration(100 * SPEED); mLeftEdgeAnimator.addUpdateListener(mAnimatorUpdateListener); mRightEdgeAnimator = ValueAnimator.ofFloat(1.f, 0.f); mRightEdgeAnimator.setDuration(150 * SPEED); mRightEdgeAnimator.addUpdateListener(mAnimatorUpdateListener); if (!mPlayed) { mCenterEdgeAnimator.start(); mLeftEdgeAnimator.start(); mRightEdgeAnimator.start(); } else { mCenterEdgeAnimator.reverse(); mLeftEdgeAnimator.reverse(); mRightEdgeAnimator.reverse(); } } public void setOnControlStatusChangeListener(OnControlStatusChangeListener listener) { mListener = listener; } // /** // * ACTION_DOWNされたらアニメーションのスタートとか // * {@inheritDoc} // */ // @Override public boolean onTouchEvent(@NonNull MotionEvent event) { // switch (event.getAction()) { // case MotionEvent.ACTION_DOWN: // setPlayed(!mPlayed); // startAnimation(); // if (mListener != null) { // mListener.onStatusChange(this, mPlayed); // } // break; // } // return false; // } /** * 回転状況を {@link PlayPauseButton.SavedState} に保存する * {@inheritDoc} */ @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState savedState = new SavedState(superState); savedState.played = isPlayed(); return savedState; } /** * Restoreしてきたやつから {@link PlayPauseButton.SavedState} * を取得し状況をセットする * {@inheritDoc} */ @Override public void onRestoreInstanceState(Parcelable state) { SavedState savedState = (SavedState) state; super.onRestoreInstanceState(savedState.getSuperState()); setPlayed(savedState.played); setUpAnimator(); invalidate(); } /** * android:background で親の {@link View} が背景をセットするので上書き * {@inheritDoc} */ @Override public void setBackground(Drawable background) { } /** * {@link PlayPauseButton#mPlayed} を返す * * @return {@link PlayPauseButton#mPlayed} */ public boolean isPlayed() { return mPlayed; } /** * {@link PlayPauseButton#mPlayed} をセットする * * @param played 状況 */ public void setPlayed(boolean played) { if (mPlayed != played) { mPlayed = played; invalidate(); } } /** * {@link PlayPauseButton#mBackgroundColor} をセットして再描画する * * @param color セットする色 */ public void setColor(int color) { mBackgroundColor = color; mPaint.setColor(mBackgroundColor); invalidate(); } public interface OnControlStatusChangeListener { void onStatusChange(View view, boolean state); } /** * 画面開店時の状態を保存しておくやつ */ static class SavedState extends BaseSavedState { public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; boolean played; SavedState(Parcelable superState) { super(superState); } private SavedState(Parcel in) { super(in); played = (Boolean) in.readValue(null); } @Override public void writeToParcel(@NonNull Parcel out, int flags) { super.writeToParcel(out, flags); out.writeValue(played); } } /** * 座標の基準を * ・中心を(0,0) * ・右上(1,1) 右下(1,ー1) 左上(ー1,1) 左下(ー1,ー1) * の座標時期に変換するためのClass */ static class Point { private int width; private int height; public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } public float getX(float x) { return (width / 2) * (x + 1); } public float getY(float y) { return (height / 2) * (y + 1); } public float getX(double x) { return getX((float) x); } public float getY(double y) { return getY((float) y); } } }