贝塞尔曲线实现音乐播放动画效果
2018-03-05 本文已影响0人
刘孙猫咪

触发按钮点击事件的时候,创建贝塞尔曲线路径,根据路径进行运动,而并不是进行绘制,从而实现该效果;
<?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();
}
}
};