这篇博客主要是从canvas.drawPath()的效果来让View去实现moveTo,LineTo,CubicTo,Quato的Path动画
1:首先看一下Path的moveTo,LineTo,CubicTo的效果
package voice.yuekong.com.animationtest;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.view.View;
/**
* Created by Zhongqi.Shao on 2016/12/21.
*/
public class AnimationView extends View {
public AnimationView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(7);
paint.setColor(Color.RED);
Path path = new Path();
path.moveTo(100, 100);
path.cubicTo(200, 150, 300, 300, 400, 200);
canvas.drawPath(path, paint);
}
}
效果如图:
2:为了更方便实现View的lineTo,moveTo,cubicTo动画一般实现View的平移,贝塞尔曲线运动我们都会到ObjectAnimator smallCircleAnim = ObjectAnimator.ofObject(mSmallCircleView, "Center", null, Path);让View沿着Path路线走,这次实现的目标就是让View通过属性动画改变自身属性translationX和translationY来实现moveTo,cubicTo,lineTo动画。最终会用这种形式来完成View的Path动画
AnimatorPath animatorPath = new AnimatorPath();
animatorPath.moveto(0, 0);
animatorPath.cubicto(dp2px(-200), dp2px(200), dp2px(-300), dp2px(100), dp2px(-200), dp2px(0));
ObjectAnimator objAnimator = ObjectAnimator.ofObject(this, "PointPath", new MyEvalueTor(), animatorPath.getPoints().toArray());
Path动画最终都是改变View的translationX和translationY来改变位置,如果我们想让View移动到具体位置Endpoint(X,Y)简称moveTo或者想让View缓慢平移到具体位置EndPoint(X,Y)简称lineTo或者让View按照贝塞尔曲线移动到具体位置EndPoint(X,Y)简称CubicTo,只需要记录每个过程的终点的偏移量和这段过程的动作,然后通过ObjectAnimator的估值器TypeEvaluator去计算出每个过程的点的位置,具体代码如下:PathPoint.java是每个运动过程的终点偏移量和贝塞尔三阶过程必须要的控制点位置
package voice.yuekong.com.animationtest;
/**
* Created by Zhongqi.Shao on 2016/12/22.
*/
public class PointPath {
public static final int MOVE = 0x01;
public static final int LINE_TO = 0x02;
public static final int CUBIC_TO = 0x03;
//偏移量
public float mX;
public float mY;
public float cubicX1;
public float cubicY1;
public float cubicX2;
public float cubicY2;
public int mAction;
public PointPath(int action, float x, float y) {
mAction = action;
mX = x;
mY = y;
}
public PointPath(float x1, float y1, float x2, float y2, float endX, float endY) {
mAction = CUBIC_TO;
cubicX1 = x1;
cubicY1 = y1;
cubicX2 = x2;
cubicY2 = y2;
mX = endX;
mY = endY;
}
public static PointPath moveTo(float x, float y) {
return new PointPath(MOVE, x, y);
}
public static PointPath lineTo(float x, float y) {
return new PointPath(LINE_TO, x, y);
}
public static PointPath cubicTo(float x1, float y1, float x2, float y2, float endX, float endY) {
return new PointPath(x1, y1, x2, y2, endX, endY);
}
}
package voice.yuekong.com.animationtest;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Zhongqi.Shao on 2016/12/22.
*/
public class AnimatorPath {
private List pointPathList = new ArrayList();
public void moveto(float x, float y) {
pointPathList.add(PointPath.moveTo(x, y));
}
public void lineto(float x, float y) {
pointPathList.add(PointPath.lineTo(x, y));
}
public void cubicto(float x1, float y1, float x2, float y2, float endX, float endY) {
pointPathList.add(PointPath.cubicTo(x1, y1, x2, y2, endX, endY));
}
public List getPoints() {
return pointPathList;
}
}
MainActivity实现的就是将布局中的按钮按照框架提供的方法进行贝塞尔曲线三阶移动,移动过程中进行ScaleX和ScaleY的扩大动画从而实现水波纹ripple
的效果,动画结束后让另外3个View依次执行扩大动画从0-1的过程
package voice.yuekong.com.animationtest;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.animation.LinearInterpolator;
import android.widget.Button;
import android.widget.RelativeLayout;
/**
* Created by Zhongqi.Shao on 2016/12/22.
*/
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button mButton;
private String TAG_BTN = "TAG_BTN";
private boolean isReveal = false;
private RelativeLayout mContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.animation_layout);
mButton = (Button) findViewById(R.id.btn);
mButton.setOnClickListener(this);
mButton.setTag(TAG_BTN);
mContainer = (RelativeLayout) findViewById(R.id.rl_container);
}
@Override
public void onClick(View view) {
if (view.getTag().equals(TAG_BTN)) {
startAnimator(view);
}
}
private void startAnimator(final View view) {
final float startX = view.getX();
AnimatorPath animatorPath = new AnimatorPath();
animatorPath.moveto(0, 0);
animatorPath.cubicto(dp2px(-200), dp2px(200), dp2px(-300), dp2px(100), dp2px(-200), dp2px(0));
ObjectAnimator objAnimator = ObjectAnimator.ofObject(this, "PointPath", new MyEvalueTor(), animatorPath.getPoints().toArray());
objAnimator.setDuration(2000);
objAnimator.setInterpolator(new LinearInterpolator());
objAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
if (Math.abs(startX - view.getX()) > dp2px(5)) {
if (!isReveal) {
PropertyValuesHolder holderX = PropertyValuesHolder.ofFloat("scaleX", 1, 13);
PropertyValuesHolder holderY = PropertyValuesHolder.ofFloat("scaleY", 1, 13);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holderX, holderY);
animator.setDuration(1000);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
mContainer.setBackgroundColor(MainActivity.this.getResources().getColor(R.color.appColor));
mButton.setVisibility(View.INVISIBLE);
for (int i = 0; i < mContainer.getChildCount(); i++) {
View childView = mContainer.getChildAt(i);
if (childView.getId() == R.id.btn) {
continue;
}
ViewPropertyAnimator animatorView = childView.animate().scaleX(1).scaleY(1);
animatorView.setDuration(1000);
animator.setStartDelay(i * 100);
animator.start();
}
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animator.start();
}
isReveal = true;
}
}
});
objAnimator.start();
}
public void setPointPath(PointPath point) {
mButton.setTranslationX(point.mX);
mButton.setTranslationY(point.mY);
}
private int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
private int sp2px(int sp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
}
}
package voice.yuekong.com.animationtest;
import android.animation.TypeEvaluator;
/**
* Created by Zhongqi.Shao on 2016/12/22.
*/
public class MyEvalueTor implements TypeEvaluator {
@Override
public PointPath evaluate(float t, PointPath startPointPath, PointPath endPointPath) {
float x = 0f;
float y = 0f;
float oneMinus = 1 - t;
switch (endPointPath.mAction) {
case PointPath.MOVE:
x = endPointPath.mX;
y = endPointPath.mY;
break;
case PointPath.LINE_TO:
x = startPointPath.mX + (endPointPath.mX - startPointPath.mX) * t;
y = startPointPath.mY + (endPointPath.mY - startPointPath.mY) * t;
break;
case PointPath.CUBIC_TO:
x = oneMinus * oneMinus * oneMinus * startPointPath.mX +
3 * t * oneMinus * oneMinus * endPointPath.cubicX1 +
3 * t * t * oneMinus * endPointPath.cubicX2 +
endPointPath.mX * t * t * t;
y = oneMinus * oneMinus * oneMinus * startPointPath.mY +
3 * t * oneMinus * oneMinus * endPointPath.cubicY1 +
3 * t * t * oneMinus * endPointPath.cubicY2 +
endPointPath.mY * t * t * t;
break;
}
return PointPath.moveTo(x, y);
}
}