Android(安卓)贝塞尔曲线的实现(一)
什么是贝塞尔曲线
简单的来说贝塞尔曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,带来视觉上的冲击,我们可以利用它来画出各种意想不到的曲线,
这里就不在列出贝塞尔曲线的繁琐定义及推导了,感兴趣的小伙伴可自行百度。
贝塞尔曲线分为一阶贝塞尔曲线,二阶贝塞尔曲线,三阶贝塞尔曲线甚至多阶。
对于Android开发十分友好,已经为我们封装好了简单的api供使用。
AndroidApi
二阶贝塞尔绘制API:
公式:
B(t)为当前的运动坐标 P0表示起始固定点,P1表示贝塞尔曲线控制点,P2表示结束固定点。
简单的来说我们确定固定点(起始固定点(startX,startY),终止固定点(endX,endY)),决定曲线变化的是控制点
(controllX,controllY),我们通过改变控制点来改变贝塞尔曲线的形状。
mBezierPath.moveTo(startX,startY); mBezierPath.quadTo(controllX,controllY,endX,endY); canvas.drawPath(mBezierPath, mCustonPaint);
三阶贝塞尔绘制API:
公式:
B(t)为当前的运动坐标 P0表示起始固定点,P1表示贝塞尔曲线左控制点,P2表示贝塞尔曲线右控制点,P3表示结束固定点。
简单的来说我们确定固定点(起始固定点(startX,startY),终止固定点(endX,endY)),决定曲线变化的是控制点(左控制点(eventLeftX,eventLeftY),右控制点)(eventRightX,eventRightY),我们通过改变控制点来改变贝塞尔曲线的形状。
path.moveTo(startX, startY); path.cubicTo(eventLeftX, eventLeftY, eventRightX, eventRightY, endX, endY); canvas.drawPath(path, mCustonPaint);
贝塞尔曲线原理
一阶贝塞尔曲线
原理图:
原理:
线性贝塞尔曲线函数中的 t 会经过由 P0 至P1 的 B(t) 所描述的曲线。例如当 t=0.25时,B(t) 即一条由点 P0 至 P1 路径的四分之一处。就像由 0 至 1 的连续 t,B(t) 描述一条由 P0 至 P1 的直线。
实现:
一阶贝塞尔曲线其实就是一条直线,实现相当简单这里不再演示。
二阶贝塞尔曲线
原理图:
原理:
为建构二次贝塞尔曲线,可以中介点 Q0 和 Q1 作为由 0 至 1 的 t:
由 P0 至 P1 的连续点 Q0,描述一条线性贝塞尔曲线。
由 P1 至 P2 的连续点 Q1,描述一条线性贝塞尔曲线。
由 Q0 至 Q1 的连续点 B(t),描述一条二次贝塞尔曲线。
实现:
下面我们利用Android的api来绘制贝塞尔曲线
效果图如下:
代码:
实现出来非常简单,这里列出核心代码
@Override protected void onDraw(Canvas canvas) { Path path = new Path(); path.reset(); path.moveTo(startX, startY); path.quadTo(eventX, eventY, endX, endY); canvas.drawPath(path, mCustonPaint); canvas.drawCircle(startX, startY, 25, mCustomCirlePaint);//左固定点圆 canvas.drawCircle(endX, endY, 25, mCustomCirlePaint);//右固定点圆 canvas.drawCircle(eventX, eventY, 25, mCustomCirlePaint);//控制点圆 canvas.drawLine(startX, startY, eventX, eventY, mCustomLinePaint);//路径连线 canvas.drawLine(eventX, eventY, endX, endY, mCustomLinePaint);//路径连线 }@Overridepublic boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: eventX = (int) event.getX(); eventY = (int) event.getY(); postInvalidate(); break; } return true;}
三阶贝塞尔曲线
原理图:
原理:
阶贝塞尔曲线其实就是在二阶的基础上,再增加一条边线。
实现:
下面我们利用Android的api来绘制贝塞尔曲线
效果图如下:
核心代码实现:
@Override protected void onDraw(Canvas canvas) { Path path = new Path(); path.moveTo(startX, startY); path.cubicTo(eventLeftX, eventLeftY, eventRightX, eventRightY, endX, endY); canvas.drawPath(path, mCustonPaint); canvas.drawCircle(startX, startY, 25, mCustomCirlePaint);//左固定点圆 canvas.drawCircle(endX, endY, 25, mCustomCirlePaint);//右固定点圆 canvas.drawCircle(eventLeftX, eventLeftY, 25, mCustomCirlePaint);//左控制点圆 canvas.drawCircle(eventRightX, eventRightY, 25, mCustomCirlePaint);//右控制点圆 canvas.drawLine(startX, startY, eventLeftX, eventLeftY, mCustomLinePaint);//路径连线 canvas.drawLine(eventRightX, eventRightY, endX, endY, mCustomLinePaint);//路径连线 canvas.drawLine(eventLeftX, eventLeftY, eventRightX, eventRightY, mCustomLinePaint);//路径连线 path.reset(); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: if (isMoveLeft) { eventLeftX = (int) event.getX(); eventLeftY = (int) event.getY(); } else { eventRightX = (int) event.getX(); eventRightY = (int) event.getY(); } postInvalidate(); break; } return true; }
到这里我们就实现了简单贝塞尔曲线。
详细代码
二阶贝塞尔曲线
/** * @description:二阶贝塞尔曲线实现 * @Author MRyan * @Date 2020/3/4 10:56 * @Version 1.0 */public class BaseBezier2 extends View { private int mCustomSize; private int mCustomColor; private Paint mCustonPaint; private Paint mCustomCirlePaint; private int mSize; private int weight; private int height; private int startX; private int startY; private int endX; private int endY; private int eventX; private int eventY; private Paint mCustomLinePaint; private Boolean isClean = false; public BaseBezier2(Context context) { super(context); } public BaseBezier2(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initAttr(context, attrs); initPaint(); } public BaseBezier2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, 0); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); weight = MeasureSpec.getSize(widthMeasureSpec); height = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(weight, height); startX = weight / 2 - 500; startY = height / 2; endX = weight / 2 + 500; endY = height / 2; eventX = weight / 2; eventY = height / 2 - 500; } @Override protected void onDraw(Canvas canvas) { Path path = new Path(); path.reset(); path.moveTo(startX, startY); path.quadTo(eventX, eventY, endX, endY); canvas.drawPath(path, mCustonPaint); canvas.drawCircle(startX, startY, 25, mCustomCirlePaint);//左固定点圆 canvas.drawCircle(endX, endY, 25, mCustomCirlePaint);//右固定点圆 canvas.drawCircle(eventX, eventY, 25, mCustomCirlePaint);//控制点圆 canvas.drawLine(startX, startY, eventX, eventY, mCustomLinePaint);//路径连线 canvas.drawLine(eventX, eventY, endX, endY, mCustomLinePaint);//路径连线 } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: eventX = (int) event.getX(); eventY = (int) event.getY(); postInvalidate(); break; } return true; } private void initPaint() { mCustonPaint = new Paint(); mCustonPaint.setColor(mCustomColor); mCustonPaint.setAntiAlias(true); mCustonPaint.setStrokeWidth(20); mCustonPaint.setStyle(Paint.Style.STROKE); mCustomCirlePaint = new Paint(); mCustomCirlePaint.setColor(Color.parseColor("#ffffff")); mCustomCirlePaint.setAntiAlias(true); mCustomCirlePaint.setStrokeWidth(17); mCustomCirlePaint.setStyle(Paint.Style.STROKE); mCustomLinePaint = new Paint(); mCustomLinePaint.setColor(Color.parseColor("#ffffff")); mCustomLinePaint.setAntiAlias(true); mCustomLinePaint.setStrokeWidth(13); } private void initAttr(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BaseBezier2); mCustomColor = typedArray.getColor(R.styleable.BaseBezier2_custom_color2, mCustomColor); mCustomSize = (int) typedArray.getDimension(R.styleable.BaseBezier2_custom_size2, mCustomSize); typedArray.recycle(); } public void cleanSub() { if (onIsCleanSubListener != null) { onIsCleanSubListener.onCleanSub(isClean); } isClean = false; mCustomLinePaint.setAlpha(255); postInvalidate(); } public void Sub() { if (onIsCleanSubListener != null) { onIsCleanSubListener.onCleanSub(isClean); } isClean = true; mCustomLinePaint.setAlpha(0); postInvalidate(); } /** * 是否清除辅助线 */ public void iscleanSub() { if (isClean) { cleanSub(); } else { Sub(); } } /** * 暴露接口 */ public onIsCleanSubListener2 onIsCleanSubListener; public interface onIsCleanSubListener2 { void onCleanSub(Boolean isclean); } public void setOnIsCleanSubListener(onIsCleanSubListener2 onIsCleanSubListener) { this.onIsCleanSubListener = onIsCleanSubListener; }}
三阶贝塞尔曲线
/** * @description:三阶贝塞尔曲线实现 * @Author MRyan * @Date 2020/3/4 10:56 * @Version 1.0 */public class BaseBezier3 extends View { private int mCustomSize; private int mCustomColor; private Paint mCustonPaint; private Paint mCustomCirlePaint; private int mSize; private int weight; private int height; private int startX; private int startY; private int endX; private int endY; private int eventLeftX; private int eventLeftY; private int eventRightX; private int eventRightY; private Paint mCustomLinePaint; private boolean isMoveLeft = true; private boolean isClean = false; public BaseBezier3(Context context) { super(context); } public BaseBezier3(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initAttr(context, attrs); initPaint(); } public BaseBezier3(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, 0); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); weight = MeasureSpec.getSize(widthMeasureSpec); height = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(weight, height); startX = weight / 2 - 500; startY = height / 2; endX = weight / 2 + 500; endY = height / 2; eventLeftX = weight / 2 / 2; eventLeftY = height / 2 - 500; eventRightX = weight / 4 * 3; eventRightY = height / 2 - 500; } @Override protected void onDraw(Canvas canvas) { Path path = new Path(); path.moveTo(startX, startY); path.cubicTo(eventLeftX, eventLeftY, eventRightX, eventRightY, endX, endY); canvas.drawPath(path, mCustonPaint); canvas.drawCircle(startX, startY, 25, mCustomCirlePaint);//左固定点圆 canvas.drawCircle(endX, endY, 25, mCustomCirlePaint);//右固定点圆 canvas.drawCircle(eventLeftX, eventLeftY, 25, mCustomCirlePaint);//左控制点圆 canvas.drawCircle(eventRightX, eventRightY, 25, mCustomCirlePaint);//右控制点圆 canvas.drawLine(startX, startY, eventLeftX, eventLeftY, mCustomLinePaint);//路径连线 canvas.drawLine(eventRightX, eventRightY, endX, endY, mCustomLinePaint);//路径连线 canvas.drawLine(eventLeftX, eventLeftY, eventRightX, eventRightY, mCustomLinePaint);//路径连线 path.reset(); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: if (isMoveLeft) { eventLeftX = (int) event.getX(); eventLeftY = (int) event.getY(); } else { eventRightX = (int) event.getX(); eventRightY = (int) event.getY(); } postInvalidate(); break; } return true; } //移动左控制点 public void moveLeft() { isMoveLeft = true; } //移动右控制点 public void moveRight() { isMoveLeft = false; } private void initPaint() { mCustonPaint = new Paint(); mCustonPaint.setColor(mCustomColor); mCustonPaint.setAntiAlias(true); mCustonPaint.setStrokeWidth(20); mCustonPaint.setStyle(Paint.Style.STROKE); mCustomCirlePaint = new Paint(); mCustomCirlePaint.setColor(Color.parseColor("#ffffff")); mCustomCirlePaint.setAntiAlias(true); mCustomCirlePaint.setStrokeWidth(17); mCustomCirlePaint.setStyle(Paint.Style.STROKE); mCustomLinePaint = new Paint(); mCustomLinePaint.setColor(Color.parseColor("#ffffff")); mCustomLinePaint.setAntiAlias(true); mCustomLinePaint.setStrokeWidth(13); } private void initAttr(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BaseBezier3); mCustomColor = typedArray.getColor(R.styleable.BaseBezier3_custom_color3, mCustomColor); mCustomSize = (int) typedArray.getDimension(R.styleable.BaseBezier3_custom_size3, mCustomSize); typedArray.recycle(); } public void cleanSub() { if (onIsCleanSubListener != null) { onIsCleanSubListener.onCleanSub(isClean); } isClean = false; mCustomLinePaint.setAlpha(255); postInvalidate(); } public void Sub() { if (onIsCleanSubListener != null) { onIsCleanSubListener.onCleanSub(isClean); } isClean = true; mCustomLinePaint.setAlpha(0); postInvalidate(); } /** * 是否清除辅助线 */ public void iscleanSub() { if (isClean) { cleanSub(); } else { Sub(); } } /** * 暴露接口 */ public onIsCleanSubListener onIsCleanSubListener; public interface onIsCleanSubListener { void onCleanSub(Boolean isclean); } public void setOnIsCleanSubListener(BaseBezier3.onIsCleanSubListener onIsCleanSubListener) { this.onIsCleanSubListener = onIsCleanSubListener; }}
通过贝塞尔曲线可以实现很多好看的效果,例如
下一节我们一起实现这两个动画效果,我们下一节再见。
附上项目源码
项目源码
更多相关文章
- 啥是佩奇?打造Android界佩奇
- android图形系统详解四:控制硬加速
- 微软推超酷应用on{x} 能远程控制Android手机
- Android——贝塞尔曲线的水波浪效果实现
- 2011初入游戏之道
- Vysor让你在电脑上完全控制android手机屏幕镜像
- Android(安卓)模拟器屏幕定制(修改控制器大小)
- Okhttp 多网络通信选择实现方式
- mac下Android(安卓)studio如何使用SVN进行版本控制?