贝塞尔曲线学习

初步学习:

看图:




相信大家已经见到过此图很多了,最近学习了系列文章,写下这个demo,加了个自己的动画。

有了上面的动画,和贝塞尔曲线,如果想让一个小球沿着路径运动如下图:



上个原理图:


下面是实现的代码:

package test.vko.cn.ttapplication.weight;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.BounceInterpolator;

import java.util.ArrayList;
import java.util.List;

import test.vko.cn.ttapplication.commonutils.GeometryUtil;

/**
 * Created by JiaRH on 2015/11/6.
 */
public class BenzierView extends View {
    private Paint mPaint;
    private int color = Color.parseColor("#990000");
    private Path mPath;
    PointF A;//起点
    PointF B;//操作点1
    PointF C;//操作点2
    PointF D;//终点
    PointF M;//锚点
    private List<PointF> points;
    /**
     * 存储过圆心的直线与该圆的两个交点
     */
    private PointF[] pointFs1 = new PointF[2];
    private PointF[] pointFs2 = new PointF[2];
    private float radius = 100f;

    private boolean isStrech;

    public BenzierView(Context context) {
        this(context, null);
    }

    public BenzierView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BenzierView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaintAndPath();
        initPointfs();
    }

    private void initPointfs() {
        A = new PointF(100f, 100f);
        B = new PointF(800f, 100f);
        C = new PointF(100f, 800f);
        D = new PointF(800f, 800f);

        if (points == null) {
            points = new ArrayList<>();

        }

        points.add(A);
        points.add(B);
        points.add(C);
        points.add(D);
    }

    private void initPaintAndPath() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.RED);
//        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(2.0f);
        mPath = new Path();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawCircle(canvas);
        mPath.moveTo(points.get(0).x, points.get(0).y);
//        cubicLine(canvas);
        drawBenZier();
        canvas.drawPath(mPath, mPaint);
        invalidate();

    }

    private void drawBenZier() {
        pointFs1 = GeometryUtil.getIntersectionPoints(A, radius, 1d);
        pointFs2 = GeometryUtil.getIntersectionPoints(D, radius, 1d);
        mPath.reset();
        mPath.moveTo(pointFs1[1].x, pointFs1[1].y);//移动到E
//        M = GeometryUtil.getPointByPercent(pointFs1[0], pointFs2[0], .5f);//选取HG的中点为锚点
//        M = GeometryUtil.getPointByPercent(A, D, .5f);//选取AD的中点为锚点
        M = GeometryUtil.getPointByPercent(pointFs1[1], pointFs2[0], .5f);//选取EG的中点为锚点
        mPath.quadTo(M.x, M.y, pointFs2[1].x, pointFs2[1].y);//画线EF
        mPath.lineTo(pointFs2[0].x, pointFs2[0].y);//线段FG
//        M = GeometryUtil.getPointByPercent(pointFs2[0], pointFs1[1], .5f);//选取的中点为锚点
        mPath.quadTo(M.x, M.y, pointFs1[0].x, pointFs1[0].y);//画线GH
        mPath.lineTo(pointFs1[1].x, pointFs1[1].y);//线段HE
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int downX, downY;

        float currentx=getWidth()/2, currentY=getHeight()/2;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getRawX();
                downY = (int) event.getRawY();

                Rect rect = new Rect();
                rect.left = 0;
                rect.right = (int) radius * 2 + rect.left;
                rect.top = 0;
                rect.bottom = (int) radius * 2 + rect.top;

                if (rect.contains(downX, downY)) {
                    isStrech = false;
                } else {
                    isStrech = true;
                }

                break;
            case MotionEvent.ACTION_MOVE:
                if (isStrech) {
                    currentx = points.get(3).x = event.getX();
                    currentY = points.get(3).y = event.getY();
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
//                points.get(3).x = getWidth() / 2;
//                points.get(3).y = getHeight() / 2;
                startAni(currentx,currentY);
                break;
            default:
                break;
        }
        invalidate();
        return true;
    }

    private void startAni(float xs,float ys) {
        ValueAnimator x = ValueAnimator.ofFloat(xs-300 , xs+300);
        x.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                points.get(3).x = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        x.setRepeatMode(0);
        x.setDuration(3000);
        x.setInterpolator(new BounceInterpolator());
        x.start();
        ValueAnimator y = ValueAnimator.ofFloat(ys-300 , ys+300);
        x.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                points.get(3).y = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        y.setRepeatMode(0);
        y.setDuration(3000);
        y.setInterpolator(new BounceInterpolator());
        y.start();


    }

    private void drawCircle(Canvas canvas) {
        canvas.drawCircle(points.get(0).x, points.get(0).y, radius, mPaint);
        canvas.drawCircle(points.get(3).x, points.get(3).y, radius, mPaint);

    }

    private void cubicLine(Canvas canvas) {
//两个锚点的测试
        mPath.cubicTo(points.get(1).x, points.get(1).y, points.get(2).x, points.get(2).y, points.get(3).x, points.get(3).y);
    }
}
<strong><span style="font-size:24px;">有个工具类是网上摘的:(感谢原作者)</span></strong>
package test.vko.cn.ttapplication.commonutils;

import android.graphics.PointF;

/**
 * 几何图形工具
 */
public class GeometryUtil {
	
	/**
	 * As meaning of method name.
	 * 获得两点之间的距离
	 * @param p0
	 * @param p1
	 * @return
	 */
	public static float getDistanceBetween2Points(PointF p0, PointF p1) {
		float distance = (float) Math.sqrt(Math.pow(p0.y - p1.y, 2) + Math.pow(p0.x - p1.x, 2));
		return distance;
	}
	
	/**
	 * Get middle point between p1 and p2.
	 * 获得两点连线的中点
	 * @param p1
	 * @param p2
	 * @return
	 */
	public static PointF getMiddlePoint(PointF p1, PointF p2) {
		return new PointF((p1.x + p2.x) / 2.0f, (p1.y + p2.y) / 2.0f);
	}
	
	/**
	 * Get point between p1 and p2 by percent.
	 * 根据百分比获取两点之间的某个点坐标
	 * @param p1
	 * @param p2
	 * @param percent
	 * @return
	 */
	public static PointF getPointByPercent(PointF p1, PointF p2, float percent) {
		return new PointF(evaluateValue(percent, p1.x , p2.x), evaluateValue(percent, p1.y , p2.y));
	}
	
	/**
	 * 根据分度值,计算从start到end中,fraction位置的值。fraction范围为0 -> 1
	 * @param fraction
	 * @param start
	 * @param end
	 * @return
	 */
	public static float evaluateValue(float fraction, Number start, Number end){
		return start.floatValue() + (end.floatValue() - start.floatValue()) * fraction;
	}
	
	
	/**
	 * Get the point of intersection between circle and line.
	 * 获取 通过指定圆心,斜率为lineK的直线与圆的交点。
	 * 
	 * @param pMiddle The circle center point.
	 * @param radius The circle radius.
	 * @param lineK The slope of line which cross the pMiddle.
	 * @return
	 */
	public static PointF[] getIntersectionPoints(PointF pMiddle, float radius, Double lineK) {
		PointF[] points = new PointF[2];
		
		float radian, xOffset = 0, yOffset = 0; 
		if(lineK != null){
			radian= (float) Math.atan(lineK);//得到该角的角度
			xOffset = (float) (Math.sin(radian) * radius);//得到对边的长
			yOffset = (float) (Math.cos(radian) * radius);//得到邻边的长
		}else {
			xOffset = radius;
			yOffset = 0;
		}
		points[0] = new PointF(pMiddle.x + xOffset, pMiddle.y - yOffset);//右上
		points[1] = new PointF(pMiddle.x - xOffset, pMiddle.y + yOffset);//左下
		
		return points;
	}
}


看看上上面的运动小球实现方法:

主要看用到的PathMeasure,及其getPosTan()方法


 
 

package test.vko.cn.ttapplication.weight;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.BounceInterpolator;
import android.view.animation.LinearInterpolator;

import java.util.ArrayList;
import java.util.List;

import test.vko.cn.ttapplication.commonutils.GeometryUtil;

/**
 * Created by JiaRH on 2015/11/6.
 */
public class BenzierView extends View {
    private Paint mPaint;
    private int color = Color.parseColor("#990000");
    private Path mPath;
    PointF A;//起点
    PointF B;//操作点1
    PointF C;//操作点2
    PointF D;//终点
    PointF M;//锚点
    private List<PointF> points;
    /**
     * 存储过圆心的直线与该圆的两个交点
     */
    private PointF[] pointFs1 = new PointF[2];
    private PointF[] pointFs2 = new PointF[2];
    private float radius = 100f;

    private boolean isStrech;
 <span style="color:#33ff33;">  </span><span style="font-size:24px;color:#333333;"> /**
     * 记录当前运动点的位置
     */
    private float[] mCurrentPosition = new float[2];
    private PathMeasure mPathMeature;
    public BenzierView(Context context) {
        this(context, null);
    }

    public BenzierView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BenzierView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaintAndPath();
        initPointfs();
    }

    private void initPointfs() {
        A = new PointF(100f, 100f);
        B = new PointF(800f, 100f);
        C = new PointF(100f, 800f);
        D = new PointF(800f, 800f);

        if (points == null) {
            points = new ArrayList<>();

        }

        points.add(A);
        points.add(B);
        points.add(C);
        points.add(D);
 /**
   * 获取直径与圆的两个交点
   */
        pointFs1 = GeometryUtil.getIntersectionPoints(A, radius, 1d);

/**
 * 初始化小球的位置
 */
        mCurrentPosition[0] = pointFs1[1].x;
        mCurrentPosition[1] = pointFs1[1].y;

    }

    private void initPaintAndPath() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.STROKE);
//        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(2.0f);
        mPath = new Path();


    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawCircle(canvas);
        mPath.moveTo(points.get(0).x, points.get(0).y);
//        cubicLine(canvas);
        drawBenZier();
        canvas.drawPath(mPath, mPaint);
        drawBall(canvas);
        postInvalidate();


    }

   private void drawBall(Canvas canvas) {

        mPaint.setStyle(Paint.Style.FILL);

        canvas.drawCircle(mCurrentPosition[0], mCurrentPosition[1], 30, mPaint);
    }

    private void drawBenZier() {
        pointFs2 = GeometryUtil.getIntersectionPoints(D, radius, 1d);
        mPath.reset();
        mPath.moveTo(pointFs1[1].x, pointFs1[1].y);//移动到E
//        M = GeometryUtil.getPointByPercent(pointFs1[0], pointFs2[0], .5f);//选取HG的中点为锚点
//        M = GeometryUtil.getPointByPercent(A, D, .5f);//选取AD的中点为锚点
        M = GeometryUtil.getPointByPercent(pointFs1[1], pointFs2[0], .5f);//选取EG的中点为锚点
        mPath.quadTo(M.x, M.y, pointFs2[1].x, pointFs2[1].y);//画线EF
        mPath.lineTo(pointFs2[0].x, pointFs2[0].y);//线段FG
//        M = GeometryUtil.getPointByPercent(pointFs2[0], pointFs1[1], .5f);//选取的中点为锚点
        mPath.quadTo(M.x, M.y, pointFs1[0].x, pointFs1[0].y);//画线GH
        mPath.lineTo(pointFs1[1].x, pointFs1[1].y);//线段HE


        mPathMeature = new PathMeasure(mPath, true);

    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int downX, downY;

        float currentx = getWidth() / 2, currentY = getHeight() / 2;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getRawX();
                downY = (int) event.getRawY();

                Rect rect = new Rect();
                rect.left = 0;
                rect.right = (int) radius * 2 + rect.left;
                rect.top = 0;
                rect.bottom = (int) radius * 2 + rect.top;

                if (rect.contains(downX, downY)) {
                    isStrech = false;
                } else {
                    isStrech = true;
                }

                break;
            case MotionEvent.ACTION_MOVE:
                if (isStrech) {
                    currentx = points.get(3).x = event.getX();
                    currentY = points.get(3).y = event.getY();
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
//                points.get(3).x = getWidth() / 2;
//                points.get(3).y = getHeight() / 2;
                startAni(currentx, currentY);
                break;
            default:
                break;
        }
        invalidate();
        return true;
    }

    private void startAni(float xs, float ys) {
        ValueAnimator x = ValueAnimator.ofFloat(xs - 300, xs + 300);
        x.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                points.get(3).x = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        x.setRepeatMode(0);
        x.setDuration(3000);
        x.setInterpolator(new BounceInterpolator());
        x.start();
        ValueAnimator y = ValueAnimator.ofFloat(ys - 300, ys + 300);
        x.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                points.get(3).y = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        y.setRepeatMode(0);
        y.setDuration(3000);
        y.setInterpolator(new BounceInterpolator());
        y.start();

        startBallAni();

    }

    private void startBallAni() {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeature.getLength());
        valueAnimator.setDuration(15000);
//        valueAnimator.setRepeatMode(valueAnimator.REVERSE);
        valueAnimator.setRepeatCount(Integer.MAX_VALUE);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                mPathMeature.getPosTan(value, mCurrentPosition, null);
                postInvalidate();
            }
        });
        valueAnimator.start();
    }

    private void drawCircle(Canvas canvas) {
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(points.get(0).x, points.get(0).y, radius, mPaint);
        canvas.drawCircle(points.get(3).x, points.get(3).y, radius, mPaint);

    }

    private void cubicLine(Canvas canvas) {
        mPath.cubicTo(points.get(1).x, points.get(1).y, points.get(2).x, points.get(2).y, points.get(3).x, points.get(3).y);
    }
}



 
 
 具体详细请阅读下面的参考文章: 
 

参考文章:

BezierDemo源码解析-实现qq消息气泡拖拽消失的效果

  1. http://bezier.method.ac/

  2. http://gavinliu.cn/2015/03/30/Android-Animation-%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF%E4%B9%8B%E7%BE%8E/

  3. http://spencermortensen.com/articles/bezier-circle/

  4. http://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves



你可能感兴趣的:(动画,android,贝塞尔,粘滞效果)