Android自定义动画框架让View实现Path动画
16lz
2021-01-24
这篇博客主要是从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); }}
然后AnimationPath.java记录了所有终点的位置
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); }}
更多相关文章
- android Animation初探一
- Android(安卓)service 实现过程
- Android(安卓)WebView工作中遇到的问题记录
- Android: activity的过渡动画
- Android电视关闭效果
- Android(安卓)View深入学习(三),View的绘制(Draw)过程
- Sina微博的开发心得-1 logo界面
- Android系统的开机画面显示过程分析(7)
- Android常用开源库