自定义控件——bezier曲线的使用

概述

关于自定义View的话,这里先给自己标注一下,用于以后方便查看,介绍一个技术博主的技术博客HenCoder
今天尝试用贝瑟尔曲线自定义一个控件,效果如下:


是不是有种似曾相识的感觉,没错啦,就是QQ消息拖拽的效果。
如果还有人不太了解贝瑟尔曲线,可以拼命戳我,里面介绍的很详细。

具体实现:

首先看下这个图:

可以看出这里涉及到一些三角函数方面的数学知识,我们都可以用代码的形式把它表述出来。以下:

  1. 先有一个拖动大圆c1的圆心,和一个固定小圆c0圆心。我点击屏幕的地方就是两个圆的圆心,获取圆心:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()){
    case MotionEvent.ACTION_DOWN:
    iniPoint(event.getX(),event.getY());//获取圆心
    break;
    case MotionEvent.ACTION_MOVE:
    upPoint(event.getX(),event.getY());//移动的时候拖动圆一直在动,固定圆不动
    break;
    case MotionEvent.ACTION_UP:
    break;
    }
    invalidate();
    return true;
    }
    /**
    * 按下便初始化该点
    * @param x
    * @param y
    */
    private void iniPoint(float x, float y) {
    PressPoint = new PointF(x,y);
    unPressPoint = new PointF(x,y);
    }
    /**
    * 移动该点
    * @param x
    * @param y
    */
    private void upPoint(float x, float y) {//移动的时候拖动圆一直在动,固定圆不动
    PressPoint.x = x;
    PressPoint.y = y;
    }
  2. 拖拽大圆的半径不变的,我们可以写死,不动小圆的半径时根据两圆心的距离的变化而变化的,然后在onDrew()方法里绘制这两个圆

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Override
    protected void onDraw(Canvas canvas) {
    if(PressPoint==null&&unPressPoint==null){
    return;
    }
    //绘制拖拽圆
    canvas.drawCircle(PressPoint.x,PressPoint.y,mPressRadius,mPaint);//mPressRadius是写死的常量
    //计算两圆心c1和c0的距离,在坐标系中计算两个点的距离,公式是x的平方加上y的平方之和,然后开根号,表达式如下:
    int distence = (int) Math.sqrt((PressPoint.x-unPressPoint.x)*(PressPoint.x-unPressPoint.x)+(PressPoint.y-unPressPoint.y)*(PressPoint.y-unPressPoint.y));
    //固定小圆的半径变化形式,两圆越远,小圆半径越小
    mUnPressRadius = MIX_UNPRESSRADIUS-distence/20;

    Path bazier = getBazierPath();//路径
    //绘制固定圆和贝瑟尔曲线
    if(bazier!=null){
    canvas.drawCircle(unPressPoint.x,unPressPoint.y,mUnPressRadius,mPaint);//固定小圆
    canvas.drawPath(bazier,mPaint);//曲线
    }
    }
  3. 接下来最重要的一点是获取四个点(p0,p1,p2,p3)的坐标,先求出a的角度,小圆半径有了,然后就能得到x,y的值了,我们知道x,y和角度a的关系是:tan a = x/y。

求角度a

1
2
3
4
//角度a,对着图来看这个公式
float a = (float) Math.atan((PressPoint.y-unPressPoint.y)/(PressPoint.x-unPressPoint.x));
//x = mUnPressRadius*Math.sin(a),半径乘以sin(a)
//y = mUnPressRadius*Math.cos(a)

求p0(x,y),这里需要注意一下的是,屏幕的坐标系x轴向右是正方向,但是y轴的是向下才是正的。所以,

1
2
p0x = c0x+x
p0y = c0y-y

同理我们就能求出p1,p2,p3点了

1
2
3
4
5
6
7
8
9
//四个点的位置
float p0x = (float) (mUnPressRadius*Math.sin(a)+unPressPoint.x);
float p0y = (float) (unPressPoint.y-mUnPressRadius*Math.cos(a));
float p1x = (float) (mPressRadius*Math.sin(a)+PressPoint.x);
float p1y = (float) (PressPoint.y-mPressRadius*Math.cos(a));
float p2x = (float) (PressPoint.x-mPressRadius*Math.sin(a));
float p2y = (float) (PressPoint.y+mPressRadius*Math.cos(a));
float p3x = (float) (unPressPoint.x-mUnPressRadius*Math.sin(a));
float p3y = (float) (unPressPoint.y+mUnPressRadius*Math.cos(a));

  1. 控制点

    1
    2
    //获取控制点,取两个圆心的中点为控制点
    PointF contrlPoint = new PointF((PressPoint.x+unPressPoint.x)/2,(PressPoint.y+unPressPoint.y)/2);
  2. 所有东西都准备好就可以把路径画出来了,就是把p0,p1,p2,p3围起来。

    1
    2
    3
    4
    bazier.moveTo(p0x,p0y);//移动到p0点
    bazier.quadTo(contrlPoint.x,contrlPoint.y,p1x,p1y);//曲线路径
    bazier.lineTo(p2x,p2y);
    bazier.quadTo(contrlPoint.x,contrlPoint.y,p3x,p3y);
  3. 最后我们在布局文件中使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="acase.zhoukang.com.studydemo.MainActivity">
    <acase.zhoukang.com.studydemo.view.BezierView
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
    </RelativeLayout>

总结

关键点是在于求p0,p1,p2,p3四个点的位置,源码戳我