贝塞尔曲线实现音乐播放动画效果

2018-03-05  本文已影响0人  刘孙猫咪
device-2018-01-09-214920.gif

触发按钮点击事件的时候,创建贝塞尔曲线路径,根据路径进行运动,而并不是进行绘制,从而实现该效果;

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffe8e8e8"
    tools:context="com.playain.MainActivity">

    <ImageView
        android:id="@+id/album_cover"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="@android:color/darker_gray"
        android:scaleType="centerCrop"/>
    <android.support.v7.widget.Toolbar
        android:layout_width="match_parent"
        android:layout_height="126dp"
        android:minHeight="?attr/actionBarSize"
        android:layout_below="@+id/album_cover"
        android:paddingLeft="72dp"
        android:elevation="4dp"
        android:background="@color/colorPrimary">
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_gravity="center_vertical">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="荷塘月色"
                android:textSize="30sp"
                android:fontFamily="sans-serif"
                android:textColor="#FFF"/>
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="凤凰传奇"
                android:textSize="18sp"
                android:fontFamily="sans-serif"
                android:textColor="@android:color/black"
                android:gravity="center_vertical"/>
        </LinearLayout>
    </android.support.v7.widget.Toolbar>
    <FrameLayout
        android:id="@+id/fab_container"
        android:layout_width="match_parent"
        android:layout_height="128dp"
        android:layout_marginTop="-30dp"
        android:elevation="10dp"
        android:layout_below="@+id/album_cover">
        <LinearLayout
            android:id="@+id/media_controls_container"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_gravity="center">
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleX="0"
                android:scaleY="0"
                android:src="@drawable/ic_rewind_white_24dp"/>
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleX="0"
                android:scaleY="0"
                android:layout_marginRight="72dp"
                android:layout_marginLeft="72dp"
                android:src="@drawable/ic_play_white_24dp"/>
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleX="0"
                android:scaleY="0"
                android:src="@drawable/ic_fast_forward_white_24dp"/>
        </LinearLayout>
        <ImageButton
            android:id="@+id/fab"
            android:transitionName="button_fab"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:layout_gravity="top|right"
            android:layout_marginRight="@dimen/activity_horizontal_margin"
            android:background="@drawable/ripple"
            android:src="@drawable/ic_fast_forward_white_24dp"
            android:elevation="5dp"
            android:onClick="onFabPressed"/>
    </FrameLayout>
</RelativeLayout>

布局文件就是普通的写法,没有什么难的地方,在点击按钮的时候调用onFabPressed(v);方法,开始动画;

/**
     * 开始动画
     *
     * @param v
     */
    private void onFabPressed(View v) {
        //获取按钮的x坐标
        final float startX = mFab.getX();
        //使用自定的path进行动画路径移动
        AnimatorPath path = new AnimatorPath();
        path.moveTo(0, 0);
        path.curveTo(-200, 200, -400, 100, -450, 0);
//        path.lineTo(-600,50);
        //创建属性动画对象
        ObjectAnimator anim = ObjectAnimator.ofObject(this, "fabLoc",
                new PathEvaluator(), path.getPoints().toArray());
        //设置插值器
        anim.setInterpolator(new AccelerateInterpolator());
        anim.setDuration(ANIMATION_DURATION);
        anim.start();
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //到了path路径中某个位置时就开始扩散动画
                if (Math.abs(startX - mFab.getX()) > MINIMUN_X_DISTANCE) {
                    if (!mRevealFlag) {
                        ImageButton fab = (ImageButton) mFab;
                        fab.setImageDrawable(new BitmapDrawable());
                        mFabContainer.setY(mFabContainer.getY() + mFabSize / 2);
                        //fab的放大扩散动画
                        mFab.animate()
                                .scaleXBy(SCALE_FACTOR)
                                .scaleYBy(SCALE_FACTOR)
                                .setListener(mEndRevealListener)
                                .setDuration(ANIMATION_DURATION);
                        mRevealFlag = true;
                    }
                }
            }
        });
    }

AnimatorPath的话是一个模仿系统Path写的一个动画路径类,主要用来记录一系列动画路径轨迹,并提供绘制方法;

public class AnimatorPath {
    //一系列的轨迹记录动作
    ArrayList<PathPoint> mPoints = new ArrayList<>();

    public void moveTo(float x, float y) {
        mPoints.add(PathPoint.moveTo(x,y));
    }

    /**
     * 绘制直线
     *
     * @param x
     * @param y
     */
    public void lineTo(float x, float y) {
        mPoints.add(PathPoint.lineTo(x,y));
    }

    /**
     * 绘制曲线 三届贝塞尔
     *
     * @param c0x
     * @param c0y
     */
    public void curveTo(float c0x, float c0y, float c1x, float c1y, float c2x, float c2y) {
        mPoints.add(PathPoint.curveTo(c0x,c0y,c1x,c1y,c2x,c2y));
    }
    public Collection<PathPoint> getPoints(){
        return mPoints;
    }
}

在创建ObjectAnimator对象的时候发现需要出入一个估值器,PathEvaluator则是一个自定义的路径估值器,它实现了TypeEvaluator<PathPoint>,会根据泛型PathPoint出入的路径,在evaluate方法中利用贝塞尔曲线公式进行贝塞尔曲线的绘制;

/**
 * Created by Administrator on 2018/1/9.
 * 估值器
 */

public class PathEvaluator implements TypeEvaluator<PathPoint> {
    @Override
    public PathPoint evaluate(float t, PathPoint startValue, PathPoint endValue) {
        float x, y;
        //三届贝塞尔曲线
        if (endValue.mOperation == PathPoint.CURVE) {
            float oneMinusT = 1 - t;
            x = oneMinusT * oneMinusT * oneMinusT * startValue.mX +
                    3 * oneMinusT * oneMinusT * t * endValue.mControl0X +
                    3 * oneMinusT * t * t * endValue.mControl1X +
                    t * t * t * endValue.mX;
            y = oneMinusT * oneMinusT * oneMinusT * startValue.mY +
                    3 * oneMinusT * oneMinusT * t * endValue.mControl0Y +
                    3 * oneMinusT * t * t * endValue.mControl1Y +
                    t * t * t * endValue.mY;

        } else if (endValue.mOperation == PathPoint.LINE) {
            x = startValue.mX + t * (endValue.mX - startValue.mX);
            y = startValue.mY + t * (endValue.mY - startValue.mY);
        } else {
            x = endValue.mX;
            y = endValue.mY;
        }
        return PathPoint.moveTo(x, y);
    }
}
public class PathPoint {
    public static final int MOVE = 0;
    public static final int LINE = 1;
    public static final int CURVE = 2;
    float mX, mY;
    float mControl0X, mControl0Y;
    float mControl1X, mControl1Y;
    int mOperation;

    /**
     * Line/Move
     * @param operation
     * @param x
     * @param y
     */
    private PathPoint(int operation, float x, float y) {
        mOperation = operation;
        mX = x;
        mY = y;
    }

    /**
     * CURVE
     * @param c0X
     * @param c0Y
     * @param c1x
     * @param c1y
     * @param x
     * @param y
     */
    private PathPoint(float c0X, float c0Y, float c1x, float c1y, float x, float y) {
        mControl0X = c0X;
        mControl0Y = c0Y;
        mControl1X = c1x;
        mControl1Y = c1y;
        mX = x;
        mY = y;
        mOperation = CURVE;
    }

    public static PathPoint moveTo(float x, float y) {
        return new PathPoint(MOVE, x, y);
    }

    /**
     * 绘制直线
     *
     * @param x
     * @param y
     */
    public static PathPoint lineTo(float x, float y) {
        return new PathPoint(LINE, x, y);
    }

    /**
     * 绘制曲线 三届贝塞尔
     *
     * @param c0x
     * @param c0y
     */
    public static PathPoint curveTo(float c0x, float c0y, float c1x, float c1y, float x, float y) {
        return new PathPoint(c0x, c0y, c1x, c1y, x, y);
    }
}

再对动画设置相应的插值器,并对动画的执行进行监听,设置不同对应view的动画效果就可以了;

private AnimatorListenerAdapter mEndRevealListener = new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            mFab.setVisibility(View.INVISIBLE);
            mFabContainer.setBackgroundColor(getResources().getColor(R.color.colorAccent));
            //reveal动画完毕后,接着每一个子控件都有个缩放动画(依次顺序出来)
            for (int i = 0; i < mControlsContainer.getChildCount(); i++) {
                View v = mControlsContainer.getChildAt(i);
                ViewPropertyAnimator animator = v.animate()
                        .scaleX(1)
                        .scaleY(1)
                        .setDuration(ANIMATION_DURATION);
                animator.setStartDelay(i * 50);
                animation.start();
            }
        }
    };

源码地址:
https://pan.baidu.com/s/1c2WHOfm

上一篇 下一篇

猜你喜欢

热点阅读