自定义视图与贝塞尔曲线

APK下载地址

1.贝塞尔曲线

以下公式中:

B(t)为t时间下 点的坐标;

P0为起点,Pn为终点,Pi为控制点

一阶贝塞尔曲线(线段)

意义:由 P0 至 P1 的连续点, 描述的一条线段

二阶贝塞尔曲线(抛物线)

原理:由 P0 至 P1 的连续点 Q0,描述一条线段。

由 P1 至 P2 的连续点 Q1,描述一条线段。

由 Q0 至 Q1 的连续点 B(t),描述一条二次贝塞尔曲线。

经验:P1-P0为曲线在P0处的切线。

三阶贝塞尔曲线:

通用公式:

高阶贝塞尔曲线

4阶曲线:

5阶曲线:

1.1.生成贝塞尔曲线-迭代法

下面我们用代码来生成贝塞尔曲线,来展示如何贝塞尔曲线的生成原理:

package com.che.testapp.view;

import android.animation.FloatEvaluator;
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.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
 * 生成贝塞尔曲线-迭代法
 * <p/>
 * 作者:余天然 on 16/6/14 下午2:10
 */
public class BezierGenerater1 extends View {

    private Paint paint;
    private int centerX, centerY;
    private List<PointF> points;
    private FloatEvaluator evaluator;
    private float fraction;
    private Map<Integer, Integer> colors;
    private List<PointF> destPoints;

    public BezierGenerater1(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        evaluator = new FloatEvaluator();
        startAnim();
    }

    //初始化数据点和控制点的位置
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = h / 2;
        points = new ArrayList<>();
        points.add(new PointF(centerX - 150, centerY));
        points.add(new PointF(centerX - 50, centerY - 300));
        points.add(new PointF(centerX + 50, centerY + 300));
        points.add(new PointF(centerX + 150, centerY));
        colors = new HashMap<>();
        for (int i = 0; i < points.size(); i++) {
            colors.put(i, getRanColor());
        }
        destPoints = new ArrayList<>();
        destPoints.add(points.get(0));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //静态的
        drawPoint(canvas, points, Color.BLACK);
        drawLine(canvas, points, Color.GRAY);
        //动态的
        List<PointF> subData = getSubData(points, fraction);
        drawData(canvas, subData, fraction);
    }

    // 绘制数据点
    private void drawPoint(Canvas canvas, List<PointF> data, int color) {
        paint.setColor(color);
        paint.setStrokeWidth(20);
        for (int i = 0; i < data.size(); i++) {
            PointF pointF = data.get(i);
            canvas.drawPoint(pointF.x, pointF.y, paint);
        }
    }

    //绘制基准线
    private void drawLine(Canvas canvas, List<PointF> data, int color) {
        paint.setColor(color);
        paint.setStrokeWidth(4);
        for (int i = 0; i < data.size() - 1; i++) {
            PointF start = data.get(i);
            PointF end = data.get(i + 1);
            canvas.drawLine(start.x, start.y, end.x, end.y, paint);
        }
    }

    //绘制路径
    private void drawPath(Canvas canvas, List<PointF> data) {
        Path path = new Path();
        PointF start = data.get(0);
        path.moveTo(start.x, start.y);
        for (int i = 1; i < data.size() - 1; i++) {
            PointF point = data.get(i);
            path.lineTo(point.x, point.y);
        }
        paint.setColor(Color.RED);
        paint.setStrokeWidth(4);
        canvas.drawPath(path, paint);
    }

    //迭代绘制集合
    private void drawData(Canvas canvas, List<PointF> data, float fraction) {
        if (data.size() == 1) {
            drawPoint(canvas, data, Color.BLACK);
            destPoints.add(data.get(0));
            drawPath(canvas, destPoints);
        } else {
            drawLine(canvas, data, colors.get(data.size() - 1));
            //迭代
            List<PointF> subData = getSubData(data, fraction);
            drawData(canvas, subData, fraction);
        }
    }

    private void startAnim() {
        ValueAnimator animator = ValueAnimator.ofFloat(0, 100);
        animator.setInterpolator(new LinearInterpolator());
        animator.setDuration(5000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                fraction = animation.getAnimatedFraction();
                invalidate();
            }

        });
        animator.start();
    }

    //生成随机颜色
    private int getRanColor() {
        return 0xff000000 | new Random().nextInt(0x00ffffff);
    }

    //获取子数据源
    private List<PointF> getSubData(List<PointF> data, float fraction) {
        List<PointF> subData = new ArrayList<>();
        for (int i = 0; i < data.size() - 1; i++) {
            PointF start = data.get(i);
            PointF end = data.get(i + 1);
            float x = evaluator.evaluate(fraction, start.x, end.x);
            float y = evaluator.evaluate(fraction, start.y, end.y);
            subData.add(new PointF(x, y));
        }
        return subData;
    }
}

1.2.生成贝塞尔曲线-De Casteljau算法

package com.che.testapp.view;

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.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;

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

/**
 * 生成贝塞尔曲线-De Casteljau算法
 * <p/>
 * 作者:余天然 on 16/6/14 下午2:10
 */
public class BezierGenerater2 extends View {

    private Paint paint;
    private int centerX, centerY;
    private List<PointF> points;
    private float fraction;
    private List<PointF> destPoints;

    public BezierGenerater2(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
    }

    //初始化数据点和控制点的位置
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = h / 2;
        points = new ArrayList<>();
        points.add(new PointF(centerX - 150, centerY));
        points.add(new PointF(centerX - 50, centerY - 300));
        points.add(new PointF(centerX + 50, centerY + 300));
        points.add(new PointF(centerX + 150, centerY));
        destPoints = new ArrayList<>();
        destPoints.add(points.get(0));
        startAnim();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //静态的
        drawPoint(canvas, points, Color.BLACK);
        drawLine(canvas, points, Color.GRAY);
        //动态的
        drawPath(canvas, destPoints);
    }

    // 绘制数据点
    private void drawPoint(Canvas canvas, List<PointF> data, int color) {
        paint.setColor(color);
        paint.setStrokeWidth(20);
        for (int i = 0; i < data.size(); i++) {
            PointF pointF = data.get(i);
            canvas.drawPoint(pointF.x, pointF.y, paint);
        }
    }

    //绘制基准线
    private void drawLine(Canvas canvas, List<PointF> data, int color) {
        paint.setColor(color);
        paint.setStrokeWidth(4);
        for (int i = 0; i < data.size() - 1; i++) {
            PointF start = data.get(i);
            PointF end = data.get(i + 1);
            canvas.drawLine(start.x, start.y, end.x, end.y, paint);
        }
    }

    //绘制路径
    private void drawPath(Canvas canvas, List<PointF> data) {
        Path path = new Path();
        PointF start = data.get(0);
        path.moveTo(start.x, start.y);
        for (int i = 1; i < data.size() - 1; i++) {
            PointF point = data.get(i);
            path.lineTo(point.x, point.y);
        }
        paint.setColor(Color.RED);
        paint.setStrokeWidth(4);
        canvas.drawPath(path, paint);
    }

    private void startAnim() {
        ValueAnimator animator = ValueAnimator.ofFloat(0, 100);
        animator.setInterpolator(new LinearInterpolator());
        animator.setDuration(5000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                fraction = animation.getAnimatedFraction();
                PointF pointF = deCasteljau(fraction, points);
                destPoints.add(pointF);
                invalidate();
            }
        });
        animator.start();
    }

    //深复制
    private List<PointF> copyData(List<PointF> points) {
        List<PointF> data = new ArrayList<>();
        for (int i = 0; i < points.size(); i++) {
            PointF point = points.get(i);
            data.add(new PointF(point.x, point.y));
        }
        return data;
    }

    //De Casteljau算法
    public PointF deCasteljau(float fraction, List<PointF> points) {
        List<PointF> data = copyData(points);
        final int n = data.size();
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < n - i; j++) {
                data.get(j).x = (1 - fraction) * data.get(j).x + fraction * data.get(j + 1).x;
                data.get(j).y = (1 - fraction) * data.get(j).y + fraction * data.get(j + 1).y;
            }
        }
        return data.get(0);
    }

}

1.3.二阶贝塞尔曲线

上面只是展示了贝塞尔曲线是如何生成的,但是实际上,我们使用的时候,是不必我们自己去生成的,直接调用系统api即可,path的quadTo,cubicTo就分别是二阶和三阶的贝塞尔曲线。至于更高阶的,一般用不到,就算真的需要的,用N个三阶的叠加即可。

package com.che.testapp.view;

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.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * 二阶贝塞尔曲线
 * <p/>
 * 两个数据点和一个控制点
 * 公式:B(t) = (1 - t)^2 * P0 + 2t * (1 - t) * P1 + t^2 * P2, t ∈ [0,1]
 * 作者:余天然 on 16/6/14 上午10:27
 */
public class Bezier2 extends View {
    private Paint paint;
    private int centerX, centerY;
    private PointF start, end, control;

    public Bezier2(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);

        start = new PointF(0, 0);
        end = new PointF(0, 0);
        control = new PointF(0, 0);
    }

    //初始化数据点和控制点的位置
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = h / 2;
        start.x = centerX - 200;
        start.y = centerY;
        end.x = centerX + 200;
        end.y = centerY;
        control.x = centerX;
        control.y = centerY - 100;
    }

    //根据触摸位置更新控制点,并提示重绘
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        control.x = event.getX();
        control.y = event.getY();
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawPoint(canvas);
        drawGuideLine(canvas);
        drawBezierCurve(canvas);
    }

    // 绘制数据点和控制点
    private void drawPoint(Canvas canvas) {
        paint.setColor(Color.GRAY);
        paint.setStrokeWidth(20);
        canvas.drawPoint(start.x, start.y, paint);
        canvas.drawPoint(end.x, end.y, paint);
        canvas.drawPoint(control.x, control.y, paint);
    }

    //绘制辅助线
    private void drawGuideLine(Canvas canvas) {
        paint.setStrokeWidth(4);
        canvas.drawLine(start.x, start.y, control.x, control.y, paint);
        canvas.drawLine(control.x, control.y, end.x, end.y, paint);
    }

    //绘制贝塞尔曲线
    private void drawBezierCurve(Canvas canvas) {
        paint.setColor(Color.RED);
        paint.setStrokeWidth(4);
        Path path = new Path();
        path.moveTo(start.x, start.y);
        path.quadTo(control.x, control.y, end.x, end.y);
        canvas.drawPath(path, paint);
    }
}

1.4.三阶贝塞尔曲线

package com.che.testapp.view;

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.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

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

/**
 * 三阶贝塞尔曲线
 * <p/>
 * 2个数据点和2个控制点
 * 公式:B(t) = P0 * (1-t)^3 + 3 * P1 * t * (1-t)^2 + 3 * P2 * t^2 * (1-t) + P3 * t^3 代
 * 作者:余天然 on 16/6/14 上午10:27
 */
public class Bezier3 extends View {

    private Paint paint;
    private int centerX, centerY;
    private List<PointF> points;

    public Bezier3(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
    }

    //初始化数据点和控制点的位置
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = h / 2;
        points = new ArrayList<>();
        points.add(new PointF(centerX - 150, centerY));
        points.add(new PointF(centerX - 50, centerY - 300));
        points.add(new PointF(centerX + 50, centerY + 300));
        points.add(new PointF(centerX + 150, centerY));
    }

    //根据触摸位置更新控制点,并提示重绘
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        PointF control = new PointF(0, 0);
        control.x = event.getX();
        control.y = event.getY();

        double distance1 = getDistance(control, points.get(1));
        double distance2 = getDistance(control, points.get(2));

        if (distance1 < distance2) {
            points.set(1, control);
        } else {
            points.set(2, control);
        }
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawPoint(canvas, points, Color.BLACK);
        drawLine(canvas, points, Color.GRAY);
        drawBezierCurve(canvas);
    }

    // 绘制数据点
    private void drawPoint(Canvas canvas, List<PointF> data, int color) {
        paint.setColor(color);
        paint.setStrokeWidth(20);
        for (int i = 0; i < data.size(); i++) {
            PointF pointF = data.get(i);
            canvas.drawPoint(pointF.x, pointF.y, paint);
        }
    }

    //绘制基准线
    private void drawLine(Canvas canvas, List<PointF> data, int color) {
        paint.setColor(color);
        paint.setStrokeWidth(4);
        for (int i = 0; i < data.size() - 1; i++) {
            PointF start = data.get(i);
            PointF end = data.get(i + 1);
            canvas.drawLine(start.x, start.y, end.x, end.y, paint);
        }
    }

    //绘制贝塞尔曲线
    private void drawBezierCurve(Canvas canvas) {
        paint.setColor(Color.RED);
        paint.setStrokeWidth(4);
        Path path = new Path();
        path.moveTo(points.get(0).x, points.get(0).y);
        path.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);
        canvas.drawPath(path, paint);
    }

    //获取两点的距离
    private double getDistance(PointF start, PointF end) {
        return Math.sqrt((end.y - start.y) * (end.y - start.y) + (end.x - start.x) * (end.x - start.x));
    }
}

1.5.用贝塞尔曲线拟合圆形

四段3阶贝塞尔曲线

每一段拟合90度扇形

公式:B(t) = P0 * (1-t)^3 + 3 * P1 * t * (1-t)^2 + 3 * P2 * t^2 * (1-t) + P3 * t^3

显然,当t=0.5的时候,应该正好在45度的位置上,于是:

sin(pi/4) = 0.125(3x1 - 2) + 0.25(3 - 6x1 ) + 0.5(3x1 )

解出x1=0.55228475或

根据对称性,可得y1=x1;

package com.che.testapp.view;

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.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

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

/**
 * 贝塞尔曲线-拟合圆形
 * <p/>
 * 作者:余天然 on 16/6/14 上午10:27
 */
public class BezierCircle extends View {

    private Paint paint;
    private int centerX, centerY;
    private List<PointF> points;
    private float magicNumber = 0.55228475f;//(4*(pow(2,1/2)-1)/3
    private float radius = 200;

    public BezierCircle(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
    }

    //初始化数据点和控制点的位置
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = h / 2;
        points = new ArrayList<>();
        points.add(new PointF(0, radius));
        points.add(new PointF(radius * magicNumber, radius));
        points.add(new PointF(radius, radius * magicNumber));
        points.add(new PointF(radius, 0));
    }

    //根据触摸位置更新控制点,并提示重绘
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        PointF control = new PointF(0, 0);
        control.x = event.getX()-centerX;
        control.y = event.getY()-centerY;

        double distance1 = getDistance(control, points.get(1));
        double distance2 = getDistance(control, points.get(2));

        if (distance1 < distance2) {
            points.set(1, control);
        } else {
            points.set(2, control);
        }
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        canvas.translate(centerX, centerY);
        drawSector(canvas, 0);
        drawSector(canvas, 90);
        drawSector(canvas, 180);
        drawSector(canvas, 270);
        canvas.restore();
    }

    //绘制90度扇形
    private void drawSector(Canvas canvas, float degree) {
        canvas.save();
        canvas.rotate(degree);
        drawPoint(canvas, points, Color.BLACK);
        drawLine(canvas, points, Color.GRAY);
        drawBezierCurve(canvas);
        canvas.restore();
    }

    // 绘制数据点
    private void drawPoint(Canvas canvas, List<PointF> data, int color) {
        paint.setColor(color);
        paint.setStrokeWidth(20);
        for (int i = 0; i < data.size(); i++) {
            PointF pointF = data.get(i);
            canvas.drawPoint(pointF.x, pointF.y, paint);
        }
    }

    //绘制基准线
    private void drawLine(Canvas canvas, List<PointF> data, int color) {
        paint.setColor(color);
        paint.setStrokeWidth(4);
        for (int i = 0; i < data.size() - 1; i++) {
            PointF start = data.get(i);
            PointF end = data.get(i + 1);
            canvas.drawLine(start.x, start.y, end.x, end.y, paint);
        }
    }

    //绘制贝塞尔曲线
    private void drawBezierCurve(Canvas canvas) {
        paint.setColor(Color.RED);
        paint.setStrokeWidth(4);
        Path path = new Path();
        path.moveTo(points.get(0).x, points.get(0).y);
        path.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);
        canvas.drawPath(path, paint);
    }

    //获取两点的距离
    private double getDistance(PointF start, PointF end) {
        return Math.sqrt((end.y - start.y) * (end.y - start.y) + (end.x - start.x) * (end.x - start.x));
    }
}

2.自定义视图

贝塞尔曲线就是PS里面的魔棒工具,UI上的很多曲线,其实都是用贝塞尔曲线画的,既然如此,我们只需要知道UI是怎么画的,让她标注好数据点的位置,那我们就可以原样画出UI效果了。

贝塞尔曲线的用处,就是动态绘制平滑曲线!

并且可以实时控制曲线状态,并可以通过改变控制点的状态实时让曲线进行平滑的状态变化。

2.1.气泡拖拽效果

效果图:

上面的图是盗来的,因为我还不会在手机上录制gif,下面的我的demo的截图:

这里:

p1是初始圆的圆心,p2是拖动圆的圆心

X是p1p2线段的中点

AB:与p1p2线段垂直,长度为初始圆的直径

CD:与p1p2线段垂直,长度为拖动圆的直径

绘制流程:

1.绘制p1和p2的圆形

2.根据A,X,D绘制一条2阶贝塞尔曲线

3.根据B,X,C绘制另一条2阶贝塞尔曲线

4.设置path的模式为填充模式

package com.che.testapp.view;

import android.animation.FloatEvaluator;
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.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.OvershootInterpolator;

/**
 * 贝塞尔曲线-气泡拖拽
 * <p/>
 * 二条2阶贝塞尔曲线
 * 作者:余天然 on 16/6/14 上午10:27
 */
public class BezierDraggableFlag extends View {
    private Paint paint;
    private int raduis = 40;//气泡的半径
    private double distance;//记录拉动的距离
    private double raduisTmp;//临时的半径
    private int maxDistance = 800;//拉断的距离
    private PointF p1, p2;//圆心
    private PointF pA, pB, pC, pD, pX;//控制点

    private double distanceBackup;
    private PointF p2Backup;

    public BezierDraggableFlag(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    //初始化圆心的位置
    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);

        p1 = new PointF(600, 400);
        p2 = new PointF(600, 400);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                p2.x = event.getX();
                p2.y = event.getY();
                distance = PointUtil.getDistance(p1, p2);
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                if (distance < maxDistance) {
                    p2Backup = new PointF(p2.x, p2.y);
                    distanceBackup = distance;
                    startAnim();
                }
                break;
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (distance < maxDistance) {
            calculatePoint();
            drawBefore(canvas);
        } else {
            drawAfter(canvas);
        }
    }

    //计算控制点的位置
    private void calculatePoint() {
        double raduisScale = 1 - distance / maxDistance;
        raduisTmp = raduis * raduisScale;

        pA = PointUtil.getRotatePoint(p1, p2, raduisTmp, Math.PI / 2);
        pB = PointUtil.getRotatePoint(p1, p2, raduisTmp, -Math.PI / 2);
        pC = PointUtil.getRotatePoint(p2, p1, raduis, -Math.PI / 2);
        pD = PointUtil.getRotatePoint(p2, p1, raduis, Math.PI / 2);
        pX = PointUtil.getCenterPoint(p1, p2);
    }

    //拉断之后的绘制
    private void drawAfter(Canvas canvas) {
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(4);
        canvas.drawCircle(p2.x, p2.y, raduis, paint);
    }

    //拉断之前的绘制
    private void drawBefore(Canvas canvas) {
        //端点
//        paint.setColor(Color.GRAY);
//        paint.setStyle(Paint.Style.STROKE);
//        paint.setStrokeWidth(20);
//        canvas.drawPoint(p1.x, p1.y, paint);
//        canvas.drawPoint(p2.x, p2.y, paint);
//        canvas.drawPoint(pX.x, pX.y, paint);

        //辅助线
        paint.setColor(Color.GRAY);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(4);
        canvas.drawLine(p1.x, p1.y, p2.x, p2.y, paint);

        //文字
//        paint.setColor(Color.BLACK);
//        paint.setStyle(Paint.Style.FILL);
//        paint.setStrokeWidth(2);
//        paint.setTextSize(30);
//        canvas.drawText("A", pA.x, pA.y, paint);
//        canvas.drawText("B", pB.x, pB.y, paint);
//        canvas.drawText("C", pC.x, pC.y, paint);
//        canvas.drawText("D", pD.x, pD.y, paint);
//        canvas.drawText("X", pX.x, pX.y, paint);
//        canvas.drawText("p1", p1.x, p1.y, paint);
//        canvas.drawText("p2", p2.x, p2.y, paint);

        //圆形
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(4);
        canvas.drawCircle(p1.x, p1.y, (float) raduisTmp, paint);
        canvas.drawCircle(p2.x, p2.y, raduis, paint);

        //贝塞尔曲线
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(4);
        Path path = new Path();
        path.moveTo(pA.x, pA.y);
        path.quadTo(pX.x, pX.y, pD.x, pD.y);
        path.lineTo(pC.x, pC.y);
        path.quadTo(pX.x, pX.y, pB.x, pB.y);
        path.lineTo(pA.x, pA.y);
        canvas.drawPath(path, paint);
    }

    //放开之后,如果没有拉断,就恢复初始状态
    private void startAnim() {
        final FloatEvaluator evaluator = new FloatEvaluator();
        ValueAnimator animator = ValueAnimator.ofFloat(0, 100);
        animator.setInterpolator(new OvershootInterpolator());
        animator.setDuration(100);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float fraction = animation.getAnimatedFraction();
                p2.x = evaluator.evaluate(fraction, p2Backup.x, p1.x);
                p2.y = evaluator.evaluate(fraction, p2Backup.y, p1.y);
                distance = evaluator.evaluate(fraction, distance, 0);
                invalidate();
            }

        });
        animator.start();
    }

}

这里用到了一个工具类:

package com.che.testapp.view;

import android.graphics.PointF;

/**
 * 点计算工具类
 * <p/>
 * 作者:余天然 on 16/6/15 下午2:33
 */
public class PointUtil {

    //获取旋转后的点
    public static PointF getRotatePoint(PointF p1, PointF p2, double raduis, double radians) {
        double oldRadians = getPointDegree(p1, p2);
        double newRadians = oldRadians + radians;
        float x = (float) (raduis * Math.cos(newRadians));
        float y = (float) (raduis * Math.sin(newRadians));
        return new PointF(p1.x + x, p1.y + y);
    }

    //获取中间的点
    public static PointF getCenterPoint(PointF p1, PointF p2) {
        float x = (p1.x + p2.x) / 2;
        float y = (p1.y + p2.y) / 2;
        return new PointF(x, y);
    }

    //获取两点的角度-返回的是弧度制
    public static double getPointDegree(PointF p1, PointF p2) {
        double scale = (p2.y - p1.y) / (p2.x - p1.x);
        return Math.atan(scale);
    }

    //获取两点的距离
    public static double getDistance(PointF start, PointF end) {
        return Math.sqrt((end.y - start.y) * (end.y - start.y) + (end.x - start.x) * (end.x - start.x));
    }
}

2.2.指示器效果

目标效果:

这个以后再来玩,其实有了上面的气泡拖拽效果,实现这个效果很简单的,有兴趣的朋友完全可以根据我上面的效果来实现它。


参考目录:

1. 贝塞尔曲线 总结

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

3. 贝塞尔曲线扫盲

4. Path之贝塞尔曲线

5. 使用贝塞尔曲线拟合圆

6. 基于贝塞尔曲线的Android动画差值器

时间: 2024-10-18 09:22:10

自定义视图与贝塞尔曲线的相关文章

Android自定义View进阶 - 贝塞尔曲线

Path之贝塞尔曲线 作者微博: @GcsSloop [本系列相关文章] 在上一篇文章Path之基本图形中我们了解了Path的基本使用方法,本次了解Path中非常非常非常重要的内容-贝塞尔曲线. 一.Path常用方法表 为了兼容性(偷懒) 本表格中去除了在API21(即安卓版本5.0)以上才添加的方法.忍不住吐槽一下,为啥看起来有些顺手就能写的重载方法要等到API21才添加上啊.宝宝此刻内心也是崩溃的. 作用 相关方法 备注 移动起点 moveTo 移动下一次操作的起点位置 设置终点 setLa

贝塞尔曲线:原理、自定义贝塞尔曲线View、使用!!!

一.原理 转自:http://www.2cto.com/kf/201401/275838.html Android动画学习Demo(3) 沿着贝塞尔曲线移动的Property Animation Property Animation中最重要,最基础的一个类就是ValueAnimator了.Property Animation利用ValueAnimator来跟踪记录对象属性已经变化了多长时间及当前这个时间点的值. 而在ValueAnimator中,又封装了两个类: 1)TimeInterpolat

Android 自定义View高级特效,神奇的贝塞尔曲线

效果图 效果图中我们实现了一个简单的随手指滑动的二阶贝塞尔曲线,还有一个复杂点的,穿越所有已知点的贝塞尔曲线.学会使用贝塞尔曲线后可以实现例如QQ红点滑动删除啦,360动态球啦,bulabulabula~ 什么是贝塞尔曲线? 贝赛尔曲线(Bézier曲线)是电脑图形学中相当重要的参数曲线.更高维度的广泛化贝塞尔曲线就称作贝塞尔曲面,其中贝塞尔三角是一种特殊的实例.贝塞尔曲线于1962年,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计.贝

安卓自定义 View 进阶:贝塞尔曲线

在上一篇文章Path之基本图形中我们了解了Path的基本使用方法,本次了解Path中非常非常非常重要的内容-贝塞尔曲线. 一.Path常用方法表 为了兼容性(偷懒) 本表格中去除了在API21(即安卓版本5.0)以上才添加的方法.忍不住吐槽一下,为啥看起来有些顺手就能写的重载方法要等到API21才添加上啊.宝宝此刻内心也是崩溃的. 作用相关方法备注 移动起点moveTo移动下一次操作的起点位置 设置终点setLastPoint重置当前path中最后一个点位置,如果在绘制之前调用,效果和moveT

iOS开发 贝塞尔曲线UIBezierPath

使用UIBezierPath类可以创建基于矢量的路径,这个类在UIKit中.此类是Core Graphics框架关于path的一个封装.使用此类可以定义简单的形状,如椭圆或者矩形,或者有多个直线和曲线段组成的形状. 1.Bezier Path 基础 UIBezierPath对象是CGPathRef数据类型的封装.path如果是基于矢量形状的,都用直线和曲线段去创建.我们使用直线段去创建矩形和多边形,使用曲线段去创建弧(arc),圆或者其他复杂的曲线形状.每一段都包括一个或者多个点,绘图命令定义如

ios 继承UIView实现自定义视图——实现画图

主要的原理包括: 继承UIView ,重载drawrect和重载触摸事件 待实现的功能还有,路径数组保存等. 用可变数据保存path路径 画曲线是通过二次贝塞尔曲线实现的 这里可以得到画图的UIImage对象 UIGraphicsBeginImageContext(self.bounds.size); [self.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage *result=UIGraphicsGetImageFrom

iOS开发 贝塞尔曲线的使用总结

iOS开发 贝塞尔曲线UIBezierPath 最近项目中需要用到用贝塞尔曲线去绘制路径 ,然后往路径里面填充图片,找到这篇文章挺好,记录下来 自己学习! 转至 http://blog.csdn.net/guo_hongjun1611/article/details/7839371 使用UIBezierPath类可以创建基于矢量的路径,这个类在UIKit中.此类是Core Graphics框架关于path的一个封装.使用此类可以定义简单的形状,如椭圆或者矩形,或者有多个直线和曲线段组成的形状.

Android自定义视图三:给自定义视图添加“流畅”的动画

在第二部分我们实现了一个简单的折线图.这里假设你已经读了前篇.下面我们将继续为这个折线图添砖加瓦. 我在想给这个图的上方添加三个按钮,这样用户可以点选不同的按钮来查看不同类别的数据.比如,用户可以查看走路的.跑步的和骑车的.用户点不同的按钮,我们就跟还不同的运动数据显示在图形里. 我们实现了按钮点击后,设置不同的坐标点数据,然后运行APP.你会发现,虽然方法setChartData()已经被调用了,但是图形一点变化都没有.为什么呢?因为我们没有通知折线图"重绘".这可以通过调用inva

Android中自定义视图View之---前奏篇

前言 好长时间没写blog了,心里感觉有点空荡荡的,今天有时间就来写一个关于自定义视图的的blog吧.关于这篇blog,网上已经有很多案例了,其实没什么难度的.但是我们在开发的过程中有时候会用到一些自定义的View以达到我们所需要的效果.其实网上的很多案例我们看完之后,发现这部分没什么难度的,我总结了两点: 1.准备纸和笔,计算坐标 2.在onDraw方法中开始画图,invalidate方法刷新,onTouchEvent方法监听触摸事件 对于绘图相关的知识,之前在弄JavaSE相关的知识的时候,