Interpolator+TypeEvaluator实现贝塞尔曲线插值器
来源:互联网 发布:ipad可以淘宝直播吗 编辑:程序博客网 时间:2024/05/23 23:19
官方提供的Android动画相当丰富,但有时我们需要实现一些特殊的运动轨迹,如缓动曲线时就会束手无策,接下来我们实现一个基于贝
塞尔曲线的自定义动画轨迹
一、ValueAnimator原理
1、Interpolator
Interpolator即插值器,其作用为根据时间计算属性值的变化,它实现了TimeInterpolator接口
Android系统预置的Interpolator的实现类包括:
理解工作原理最好的方法就是:Read the F*cking source code
TimeInterpolator源码
/** * A time interpolator defines the rate of change of an animation. This allows animations * to have non-linear motion, such as acceleration and deceleration. */public interface TimeInterpolator { /** * Maps a value representing the elapsed fraction of an animation to a value that represents * the interpolated fraction. This interpolated value is then multiplied by the change in * value of an animation to derive the animated value at the current elapsed animation time. * * @param input A value between 0 and 1.0 indicating our current point * in the animation where 0 represents the start and 1.0 represents * the end * @return The interpolation value. This value can be more than 1.0 for * interpolators which overshoot their targets, or less than 0 for * interpolators that undershoot their targets. */ float getInterpolation(float input);}
TimeInterpolator接口中规定了一个方法,其接收一个float值,从0开始,至1结束
通过查看线性变化的插值器——LinearInterpolator,可以看出
@HasNativeInterpolatorpublic class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { public LinearInterpolator() { } public LinearInterpolator(Context context, AttributeSet attrs) { } public float getInterpolation(float input) { return input; } /** @hide */ @Override public long createNativeInterpolator() { return NativeInterpolatorFactoryHelper.createLinearInterpolator(); }}
getInterpolation()返回值与input相等
加速运动插值器——AcclerateDecelerateInterpolator
@HasNativeInterpolatorpublic class AccelerateDecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { public AccelerateDecelerateInterpolator() { } @SuppressWarnings({"UnusedDeclaration"}) public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) { } public float getInterpolation(float input) { return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f; } /** @hide */ @Override public long createNativeInterpolator() { return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator(); }}
返回值为类cos函数0至1段曲线的值
同理,其他系统预置插值器均是函数变化的,参考:插值器函数
2、TypeEvaluator
TypeEvaluator即估值器,其作用是根据当前属性改变进度计算改变后的属性,如ValueAnimator.ofFloat()中为了实现初始值到结束值的平滑过渡,系统内置了FloatEvaluator
FloatEvaluator源码
public class FloatEvaluator implements TypeEvaluator<Number> { /** * This function returns the result of linearly interpolating the start and end values, with * <code>fraction</code> representing the proportion between the start and end values. The * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>, * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>, * and <code>t</code> is <code>fraction</code>. * * @param fraction The fraction from the starting to the ending values * @param startValue The start value; should be of type <code>float</code> or * <code>Float</code> * @param endValue The end value; should be of type <code>float</code> or <code>Float</code> * @return A linear interpolation between the start and end values, given the * <code>fraction</code> parameter. */ public Float evaluate(float fraction, Number startValue, Number endValue) { float startFloat = startValue.floatValue(); return startFloat + fraction * (endValue.floatValue() - startFloat); }}
FloatEvaluator重写了TypeEvaluator的evaluate方法,其参数分别为fraction 动画完成度 、 startValue 开始值 、 endValue 结束
值 并根据三个值计算改变后的属性
public class IntEvaluator implements TypeEvaluator<Integer> { /** * This function returns the result of linearly interpolating the start and end values, with * <code>fraction</code> representing the proportion between the start and end values. The * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>, * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>, * and <code>t</code> is <code>fraction</code>. * * @param fraction The fraction from the starting to the ending values * @param startValue The start value; should be of type <code>int</code> or * <code>Integer</code> * @param endValue The end value; should be of type <code>int</code> or <code>Integer</code> * @return A linear interpolation between the start and end values, given the * <code>fraction</code> parameter. */ public Integer evaluate(float fraction, Integer startValue, Integer endValue) { int startInt = startValue; return (int)(startInt + fraction * (endValue - startInt)); }}
观察IntEvaluator可以看出evaluate计算方法基本相似
3、ValueAnimator机制总结
1、根据时间t及时长duration的比例计算出fraction,传给Interpolator
2、Interpolator根据自己的插值器函数计算出新的fraction,传给TypeEvaluator
3、TypeEvalutor计算出animatedValue
4、控件根据animatedValue,在AnimatorUpdateListener中改变属性值
二、实现Bezier曲线轨迹
贝塞尔曲线相关:贝塞尔曲线开发的艺术
这里我们要使用三阶贝塞尔曲线:
P0为startValue 、 P3为endValue 、 P1、P2为控制点
通过重写TypeEvaluator中evaluate方法实现Bezier曲线轨迹
public class BezierEvaluator implements TypeEvaluator<PointF> { private PointF point1;//控制点1 private PointF point2;//控制点2 public BezierEvaluator(PointF point1, PointF point2) { this.point1 = point1; this.point2 = point2; } @Override public PointF evaluate(float fraction, PointF startValue, PointF endValue) { final float t = fraction; float minusT = 1.0f - t; PointF point = new PointF(); PointF point0 = startValue; PointF point3 = endValue; point.x = minusT * minusT * minusT * (point0.x) + 3 * minusT * minusT * t * (point1.x) + 3 * minusT * t * t * (point2.x) + t * t * t * (point3.x); point.y = minusT * minusT * minusT * (point0.y) + 3 * minusT * minusT * t * (point1.y) + 3 * minusT * t * t * (point2.y) + t * t * t * (point3.y); return point; }}
BezierEvaluator的应用
新建类继承自View
public class TestView extends View implements View.OnTouchListener{ private Paint bPaint; private Paint pPaint; private Paint lPaint; private PointF pointF; int temp = 0; private List<PointF> list = new ArrayList<>(); public TestView(Context context) { super(context); init(); } public TestView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { bPaint = new Paint(Paint.ANTI_ALIAS_FLAG); bPaint.setStyle(Paint.Style.FILL_AND_STROKE); bPaint.setColor(Color.BLUE); pPaint = new Paint(Paint.ANTI_ALIAS_FLAG); pPaint.setStyle(Paint.Style.FILL_AND_STROKE); pPaint.setColor(Color.GRAY); lPaint = new Paint(Paint.ANTI_ALIAS_FLAG); lPaint.setStyle(Paint.Style.STROKE); lPaint.setStrokeWidth(1); lPaint.setColor(Color.GRAY); lPaint.setTextSize(20); this.setOnTouchListener(this); } public void startAnimator() { PointF p0 = list.get(0); PointF p1 = list.get(1); PointF p2 = list.get(2); PointF p3 = list.get(3); ValueAnimator animator = ValueAnimator.ofObject( new BezierEvaluator(new PointF(p1.x , p1.y), new PointF(p2.x , p2.y)) ,new PointF(p0.x , p0.y), new PointF(p3.x , p3.y)); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { pointF = (PointF) animation.getAnimatedValue(); invalidate(); } }); animator.setDuration(5000); animator.start(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for(int i = 0 ; i < list.size() ; i ++){ PointF p = list.get(i); canvas.drawCircle(p.x , p.y , 5 ,pPaint); canvas.drawText("[" + (int)p.x + "," + (int)p.y + "]" , 50 , 50 + i * 30 , lPaint); } temp ++; Path path = new Path(); switch (list.size()){ case 2: canvas.drawLine(list.get(0).x , list.get(0).y , list.get(1).x , list.get(1).y , lPaint); break; case 3: path.moveTo(list.get(0).x , list.get(0).y); path.quadTo(list.get(1).x , list.get(1).y , list.get(2).x , list.get(2).y); canvas.drawPath(path , lPaint); break; case 4: path.moveTo(list.get(0).x , list.get(0).y); path.cubicTo(list.get(1).x , list.get(1).y , list.get(2).x , list.get(2).y , list.get(3).x , list.get(3).y); canvas.drawPath(path , lPaint); if(pointF == null){ canvas.drawCircle(list.get(0).x , list.get(0).y , 40 ,bPaint); startAnimator(); }else{ canvas.drawCircle(pointF.x , pointF.y , 40 , bPaint); } break; } } @Override public boolean onTouch(View v, MotionEvent event) { if(list.size() < 4){ PointF pointF = new PointF(); pointF.x = event.getX(); pointF.y = event.getY(); list.add(pointF); }else if(list.size() == 4){ list.clear(); temp = 0; pointF = null; } invalidate(); return false; }}
实现效果
根据以上我们可以做出更实用的东西
public class HeartImageView extends ImageView { private Bitmap image; public HeartImageView(Context context) { this(context, null); } public HeartImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public HeartImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); image = BitmapFactory.decodeResource(getResources(), R.drawable.heart); } public void setColor(int color) { setImageBitmap(createColor(color)); } private Bitmap createColor(int color) { int heartWidth= image.getWidth(); int heartHeight= image.getHeight(); Bitmap newBitmap=Bitmap.createBitmap(heartWidth, heartHeight, Bitmap.Config.ARGB_8888); Canvas canvas=new Canvas(newBitmap); Paint paint=new Paint(); paint.setAntiAlias(true); canvas.drawBitmap(image, 0, 0, paint); canvas.drawColor(color, PorterDuff.Mode.SRC_ATOP); canvas.setBitmap(null); return newBitmap; }}
public class HeartLayout extends RelativeLayout { private Context context; private int width; private int height; private int[] colors={Color.RED, Color.YELLOW, Color.GRAY, Color.GREEN, Color.BLUE}; public HeartLayout(Context context) { this(context , null); } public HeartLayout(Context context, AttributeSet attrs) { this(context, attrs , 0); } public HeartLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); width=getWidth(); height=getHeight(); } public void addHeart() { RelativeLayout.LayoutParams params=new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); params.addRule(RelativeLayout.CENTER_HORIZONTAL); final HeartImageView imageView=new HeartImageView(context); imageView.setColor(colors[new Random().nextInt(colors.length)]); imageView.setVisibility(INVISIBLE); addView(imageView, params); imageView.post(new Runnable() { @Override public void run() { int point1x=new Random().nextInt((width)); int point2x=new Random().nextInt((width)); int point1y=new Random().nextInt(height/2)+height/2; int point2y=point1y-new Random().nextInt(point1y); int endX=new Random().nextInt(width/2); int endY=point2y-new Random().nextInt(point2y); ValueAnimator translateAnimator=ValueAnimator.ofObject( new BezierEvaluator(new PointF(point1x, point1y), new PointF(point2x, point2y)), new PointF(width/2-imageView.getWidth()/2, height-imageView.getHeight()), new PointF(endX, endY)); translateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { PointF pointF= (PointF) animation.getAnimatedValue(); imageView.setX(pointF.x); imageView.setY(pointF.y); } }); translateAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); removeView(imageView); } }); TimeInterpolator[] timeInterpolator={new LinearInterpolator(), new AccelerateDecelerateInterpolator(), new DecelerateInterpolator(), new AccelerateInterpolator()}; translateAnimator.setInterpolator(timeInterpolator[new Random().nextInt(timeInterpolator.length)]); ObjectAnimator translateAlphaAnimator=ObjectAnimator.ofFloat(imageView, View.ALPHA, 1f, 0f); translateAlphaAnimator.setInterpolator(new DecelerateInterpolator()); AnimatorSet translateAnimatorSet=new AnimatorSet(); translateAnimatorSet.playTogether(translateAnimator, translateAlphaAnimator); translateAnimatorSet.setDuration(2000); ObjectAnimator scaleXAnimator=ObjectAnimator.ofFloat(imageView, View.SCALE_X, 0.5f, 1f); ObjectAnimator scaleYAnimator=ObjectAnimator.ofFloat(imageView, View.SCALE_Y, 0.5f, 1f); ObjectAnimator alphaAnimator=ObjectAnimator.ofFloat(imageView, View.ALPHA, 0.5f, 1f); AnimatorSet enterAnimatorSet=new AnimatorSet(); enterAnimatorSet.playTogether(scaleXAnimator, scaleYAnimator, alphaAnimator); enterAnimatorSet.setDuration(500); enterAnimatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); imageView.setVisibility(VISIBLE); } }); AnimatorSet allAnimator=new AnimatorSet(); allAnimator.playSequentially(enterAnimatorSet, translateAnimatorSet); allAnimator.start(); } }); }}
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final HeartLayout layout = (HeartLayout) findViewById(R.id.heart); layout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { layout.addHeart(); return false; } }); }}
- Interpolator+TypeEvaluator实现贝塞尔曲线插值器
- Android 动画-Interpolator和TypeEvaluator
- Android属性动画(三)——TypeEvaluator(估值器)和Interpolator(插值器)
- 浅析 Android 动画:自定义 Interpolator 与 TypeEvaluator
- 属性动画Animator玩法/自定义估值器TypeEvaluator实现抛物线曲线动画
- 属性动画高级用法之TypeEvaluator和Interpolator
- android动画之interpolator和typeEvaluator用法详解
- android动画加速器(插值器)interpolator和速度曲线
- 自定义插值器TypeEvaluator
- TypeEvaluator
- 三阶贝塞尔曲线Interpolator的应用
- 插值器Interpolator
- Android属性动画高阶用法-Interpolator,TypeEvaluatory以及贝塞尔曲线公式的使用
- 贝塞尔曲线的实现
- Android 实现贝塞尔曲线
- line renderer实现贝塞尔曲线
- android:贝塞尔曲线简单实现
- 三行代码实现贝塞尔曲线
- HTTP 头部解释
- 计算机的组成及其功能
- 视频解码和硬解码
- 五个海盗如何分100个金币呢?
- 写大数据简历的黄金法则及项目经验
- Interpolator+TypeEvaluator实现贝塞尔曲线插值器
- 乐视遭受200G的DDOS攻击有多大威力?
- 成功项目的特征
- Linux搭建SVN服务器
- Servlet 客户端 HTTP 请求
- <中秋节番外>编程之美之课后练习(一)
- 关于jtag
- 逆序对在OI中实际问题里的细节处理
- hdu5876Sparse Graph