Path
路径,相当于是将画笔绘制的轨迹的一个集合
我曾经觉得path 就是随意的画画轨迹就完事了,至于贝塞尔曲线!!!? 一直听说,怎么玩的 不太清楚.... 或者 知道贝塞尔,就是path.c..,怎么应用在实际的工作中? 不会。所以有了今天的学习,今天的目标主要是了解path,了解贝塞尔怎么用,并且实际的撸一下代码,记录下笔记。
path中的基础用法
moveTo 移动
lineTo 连接线
close 封闭
简单绘制图形
//最后的参数表示绘制的方向 CW顺时针 CCW逆时针
addArc() 添加弧线
addOval() 添加椭圆
addRect() 添加矩形
addRoundRect() 添加圆角矩形
addCircle() 添加圆形
//让你看看 顺逆时针到底有什么卵用
path.addCircle(500,500,500, Path.Direction.CCW);
canvas.drawPath(path,mPaint);
mPaint.setTextSize(30);
mPaint.setStrokeWidth(1);
canvas.drawTextOnPath("我是这么玩的",path,0,0,mPaint);
在这里 我觉得这个圆角矩形是有点意思的,之前也确实没注意过这个
//1.四角一样弧度
RectF rectF=new RectF(100,100,600,400);
path.addRoundRect(rectF,50,50, Path.Direction.CCW);
canvas.drawPath(path,mPaint);
//2.单独设定每个角的弧度 第二参每两个值表示 左上x,y 右上x,y 右下x,y 左下x,y
canvas.save();
canvas.translate(0,400);
path.reset();
path.addRoundRect(rectF,new float[]{150,150,50,50,50,50,0,0}, Path.Direction.CCW);
canvas.drawPath(path,mPaint);
canvas.restore();
这里有点我感觉重复的东西, 就是 op属性 ,当两个path组合时,使用op属性 可以实现一些特殊的效果。特殊? 其实和paint的mode差不多,就是又简单一点的几个效果而已
以下内容转自https://blog.csdn.net/xifei66/article/details/108831640
op属性/布尔值运算
感觉这个基础用法其实就够了,如果还想了解的话,可以看看更多的用法,哈哈,只是我一个小渣渣的感觉
接下来是更多的用法,至于有没有用,你可以看看,也许会用到。
r标识
path.rLineTo();
path.rCubicTo();
path.rMoveTo();
path.rQuadTo();
都是在原有基础上,再相对偏移一定量。
arcTo
什么玩意儿...其实就是绘制圆弧。那么i和addArc有什么区别?
为了更明显的看出一些效果,我绘制了个坐标系,再绘制个圆弧的外接矩形,这块可以不考虑绘制,不影响
//先画个x-y坐标系
mPaint.setStrokeWidth(0);
mPaint.setTextSize(40);
mPaint.setColor(Color.DKGRAY);
//x轴
canvas.drawLine(50,350,800,350,mPaint);
canvas.drawText("x",0,1,800,350,mPaint);
//y轴
canvas.drawLine(350,50,350,800,mPaint);
canvas.drawText("y",0,1,350,800,mPaint);
//画个矩形框
RectF rectF=new RectF(100,100,600,600);
mPaint.setStrokeWidth(2);
mPaint.setColor(Color.RED);
mPaint.setPathEffect(new DashPathEffect(new float[]{100,20},0));
canvas.drawRect(rectF,mPaint);
mPaint.setPathEffect(null);
//想判断后续的区别必须把path赋个值
path.lineTo(100,100);
//绘制圆弧
addArc和arcTo都是加入圆弧到path中。
addArc是直接加入圆弧到path中
arcTo会根据forceMoveTo属性 (true 强制移动 false 不强制移动) 默认为false
false时推断要绘制圆弧的起点与绘制圆弧之前path中最后的点是否是同一个点,假设不是同一个点的话,就会连接两个点。
1)addArc
//25表示偏移 具体值是绘制时的画笔宽度的一半,要不然会出现绘制的图一半宽度在外界的现象
RectF rectFInner=new RectF(100+25,100+25,600-25,600-25);
//由图很明显可看出 从3点钟方向绘制
path.addArc(rectFInner,0,130);
mPaint.setStrokeWidth(50);
mPaint.setColor(Color.BLUE);
canvas.drawPath(path,mPaint);
2)arcTo
path.arcTo(rectFInner,0,130,false);
计算面积
computeBounds (RectF bounds, boolean exact)
bounds:测量结果会放入这个矩形
exact:是否精确测量 目前这一个参数作用已经废弃,一般写true即可
RectF tempRect=new RectF();
path.computeBounds(tempRect,true);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawRect(tempRect,mPaint);
小兄弟,这里就把基本内容聊完了,我们开始精彩的贝塞尔阶段吧。
//二阶
path.moveTo(100,100); //起始点
path.quadTo(300,700,200,100); //控制点 结束点
canvas.drawCircle(100,100,10,mPaint);
canvas.drawCircle(300,700,5,mPaint);
canvas.drawCircle(200,100,10,mPaint);
canvas.drawPath(path,mPaint);
//三阶
path.moveTo(100,900); //起始点
path.cubicTo(300,200,400,400,600,900); //控制点1 控制点2 结束点
canvas.drawCircle(100,900,10,mPaint);
canvas.drawCircle(300,200,5,mPaint);
canvas.drawCircle(400,400,5,mPaint);
canvas.drawCircle(600,900,10,mPaint);
canvas.drawPath(path,mPaint);
完了。 完了!!!? 这是什么鬼....有个毛用.
其实上面的话,是我一直以来的感觉,基于当前的公司需求,连动画效果都完全不需要。所以我对这块的了解基本为0。
先胡乱的上传两个跟着课程敲得有关贝塞尔的例子
1.不断递归1阶贝塞尔公式,实现的N阶贝塞尔
//蔑视 贝塞尔
public class DespiseBezierextends View {
//点
private PaintmPaint;
//线
private PaintmLinePointPaint;
//路径
private PathmPath;
//几阶贝塞尔
private int order=2;
//点集
private ArrayListpointList=new ArrayList<>();
public DespiseBezier(Context context) {
this(context,null);
}
public DespiseBezier(Context context,@Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public DespiseBezier(Context context,@Nullable AttributeSet attrs,int defStyleAttr) {
this(context, attrs, defStyleAttr,0);
}
public DespiseBezier(Context context,@Nullable AttributeSet attrs,int defStyleAttr,int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
//初始化
private void init(){
mPaint =new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(4);
mPaint.setTextSize(26);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.RED);
mLinePointPaint =new Paint();
mLinePointPaint.setAntiAlias(true);
mLinePointPaint.setStrokeWidth(4);
mLinePointPaint.setStyle(Paint.Style.STROKE);
mLinePointPaint.setColor(Color.GRAY);
mPath =new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (pointList.size()==0) {
return;
}
PointF pointF=new PointF();
for (int i =0; i <=order; i++) {
pointF =pointList.get(i);
if (i>0){
mLinePointPaint.setColor(Color.GRAY);
canvas.drawLine(pointList.get(i-1).x,pointList.get(i-1).y,pointF.x,pointF.y,mLinePointPaint);
}
//起点、终点换颜色
if (i ==0) {
mLinePointPaint.setColor(Color.RED);
}else if (i ==order) {
mLinePointPaint.setColor(Color.BLUE);
}
canvas.drawCircle(pointF.x,pointF.y,5,mLinePointPaint);
canvas.drawText("("+(int)pointF.x+","+(int)pointF.y+")",pointF.x-1,pointF.y-10,mPaint);
}
createBezierPoint();
canvas.drawPath(mPath,mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction()== MotionEvent.ACTION_DOWN) {
createRandomPoint();
invalidate();
}
return super.onTouchEvent(event);
}
//生成随机点
private void createRandomPoint(){
Random random=new Random();
pointList.clear();
// arrayList.add(new PointF(200,200));
for (int i =0; i <=order; i++) {
PointF pointF=new PointF();
int x=random.nextInt(800)+200;
int y=random.nextInt(1000)+200;
pointF.x=x;
pointF.y=y;
pointList.add(pointF);
}
}
//生成贝塞尔点
private void createBezierPoint(){
mPath.reset();
ArrayList points =new ArrayList<>();
//份数
float delta =1.0f/1000;
for (float t =0; t <=1; t += delta) {
//bezier点集
PointF pointF =new PointF(deCastelJau(order,0, t,true), deCastelJau(order,0, t,false));//计算在曲线上点位置
points.add(pointF);
if (points.size() ==1) {
mPath.moveTo(points.get(0).x, points.get(0).y);
}else {
mPath.lineTo(pointF.x, pointF.y);
}
}
}
//阶层, 第几份,是否横坐标
private float deCastelJau(int i,int j,float t,boolean calculateX) {
if (i ==1) {
return calculateX ? (1 - t) *pointList.get(j).x + t *pointList.get(j +1).x :
(1 - t) *pointList.get(j).y + t *pointList.get(j +1).y;
}else {
return (1 - t) * deCastelJau(i -1, j, t, calculateX) + t * deCastelJau(i -1, j +1, t, calculateX);
}
}
//外界传入order
public void setOrder(int order){
this.order=order;
createRandomPoint();
invalidate();
}
}
2.QQ红色消息 贝塞尔效果
public class QQBezierViewextends View {
//圆球画笔
private PaintmBallPaint;
//曲线画笔
private PaintmLinePaint;
//数字画笔
private PaintmTextPaint;
//路径
private PathmPath;
//设置移动球 球心
private PointFmoveBallCenterP;
//设置固定球 球心
private PointFstillBallCenterP;
//默认球半径
private float moveBallRadius;
//小球半径
private float stillBallRadius;
//移动的距离
private float mDistance;
//连线的最远距离
private float mLineMaxDistance;
//偏移量
private float offset;
/**
* 气泡默认状态--静止
*/
private final int BUBBLE_STATE_DEFAUL =0;
/**
* 气泡相连
*/
private final int BUBBLE_STATE_CONNECT =1;
/**
* 气泡分离
*/
private final int BUBBLE_STATE_APART =2;
/**
* 气泡消失
*/
private final int BUBBLE_STATE_DISMISS =3;
/*
*标记当前状态
*/
private int state=BUBBLE_STATE_DEFAUL;
/*
* 消失部分
* */
//我认为多余的爆炸画笔
private PaintmBurstPaint;
//爆炸绘制区域
private RectmBurstRect;
/**
* 气泡爆炸的bitmap数组
*/
private Bitmap[]mBurstBitmapsArray;
/**
* 是否在执行气泡爆炸动画
*/
private boolean mIsBurstAnimStart =false;
/**
* 当前气泡爆炸图片index
*/
private int mCurDrawableIndex;
/**
* 气泡爆炸的图片id数组
*/
private int[]mBurstDrawablesArray = {R.drawable.burst_1, R.drawable.burst_2
, R.drawable.burst_3, R.drawable.burst_4, R.drawable.burst_5};
public QQBezierView(Context context) {
this(context,null);
}
public QQBezierView(Context context,@Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public QQBezierView(Context context,@Nullable AttributeSet attrs,int defStyleAttr) {
this(context, attrs, defStyleAttr,0);
}
public QQBezierView(Context context,@Nullable AttributeSet attrs,int defStyleAttr,int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init(){
//setLayerType(LAYER_TYPE_SOFTWARE,null);
/*
*球画笔初始化
*/
mBallPaint =new Paint();
mBallPaint.setAntiAlias(true);
mBallPaint.setDither(true);
// mBallPaint.setStrokeWidth(20);
mBallPaint.setColor(Color.RED);
mBallPaint.setStyle(Paint.Style.FILL_AND_STROKE);
/*
* 数字画笔
* */
mTextPaint =new Paint(Paint.ANTI_ALIAS_FLAG);//第二种写法
// mTextPaint.setStrokeWidth(1);
mTextPaint.setColor(Color.WHITE);
// mTextPaint.setTextSize(25);
mTextPaint.setStyle(Paint.Style.STROKE);
/*
* 曲线画笔
* */
mLinePaint =new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint.setStyle(Paint.Style.FILL_AND_STROKE);
mLinePaint.setColor(Color.RED);
// mLinePaint.setStrokeWidth(10);
/*
* 贝塞尔点
* */
mPath=new Path();
/*
* 爆炸数组准备
* */
mBurstBitmapsArray=new Bitmap[mBurstDrawablesArray.length];
for (int i =0; i
mBurstBitmapsArray[i]= BitmapFactory.decodeResource(getResources(),mBurstDrawablesArray[i]);
}
mBurstRect=new Rect();
mBurstPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
//解决view加载bitmap时的锯齿问题 实际上我没发现。。。。
mBurstPaint.setFilterBitmap(true);
}
@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w,int h,int oldw,int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
initData(0,0);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//1.绘制初始位置
if (state!=BUBBLE_STATE_DISMISS){
canvas.drawCircle(moveBallCenterP.x,moveBallCenterP.y,moveBallRadius,mBallPaint);
//要解决数字被遮挡,应该将以下代码 放在下面的条件 后面
Rect textRect=new Rect();
String text="100";
mTextPaint.getTextBounds(text,0,text.length(),textRect);
canvas.drawText(text,moveBallCenterP.x-mTextPaint.measureText(text)/2,moveBallCenterP.y+textRect.height()/2,mTextPaint);
}
//2.根据拖拽
if (state==BUBBLE_STATE_CONNECT){
//绘制固定圆
canvas.drawCircle(stillBallCenterP.x,stillBallCenterP.y,stillBallRadius,mBallPaint);
//绘制贝塞尔线
//1.计算角度
float distance=(float) Math.hypot(moveBallCenterP.x-stillBallCenterP.x,moveBallCenterP.y-stillBallCenterP.y);
mPath.reset();
//获取控制点
float mAnthorX=(moveBallCenterP.x+stillBallCenterP.x)/2;
float mAnthorY=(moveBallCenterP.y+stillBallCenterP.y)/2;
//获取角度
float cosIn=(moveBallCenterP.x-stillBallCenterP.x)/distance;
float sinIn=(moveBallCenterP.y-stillBallCenterP.y)/distance;
//获取四点
//A
float Ax=stillBallCenterP.x+stillBallRadius*sinIn;
float Ay=stillBallCenterP.y-stillBallRadius*cosIn;
//B
float Bx=moveBallCenterP.x+moveBallRadius*sinIn;
float By=moveBallCenterP.y-moveBallRadius*cosIn;
//C
float Cx=moveBallCenterP.x-moveBallRadius*sinIn;
float Cy=moveBallCenterP.y+moveBallRadius*cosIn;
//D
float Dx=stillBallCenterP.x-stillBallRadius*sinIn;
float Dy=stillBallCenterP.y+stillBallRadius*cosIn;
mPath.moveTo(Ax,Ay);
mPath.quadTo(mAnthorX,mAnthorY,Bx,By);
mPath.lineTo(Cx,Cy);
mPath.quadTo(mAnthorX,mAnthorY,Dx,Dy);
mPath.close();
canvas.drawPath(mPath,mLinePaint);
}
//3.爆炸消失
if (state==BUBBLE_STATE_DISMISS){
if (mIsBurstAnimStart){
mBurstRect.set((int)(moveBallCenterP.x-2*moveBallRadius),
(int)(moveBallCenterP.y-2*moveBallRadius),
(int)(moveBallCenterP.x+2*moveBallRadius),
(int)(moveBallCenterP.y+2*moveBallRadius));
canvas.drawBitmap(mBurstBitmapsArray[mCurDrawableIndex],null,mBurstRect,mBurstPaint);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//测距
mDistance=(float) Math.hypot(event.getX()-getWidth()/2-stillBallCenterP.x,event.getY()-stillBallCenterP.y-getHeight()/2);
//距离在这个范围内 改变它的状态
if (mDistance
state=BUBBLE_STATE_CONNECT;
}else{
state=BUBBLE_STATE_DEFAUL;
}
break;
case MotionEvent.ACTION_MOVE:
//不是初始状态 (相连状态 分离状态)
if (state!=BUBBLE_STATE_DEFAUL){
//赋值移动球的圆心
moveBallCenterP.set(event.getX()-getWidth()/2,event.getY()-getHeight()/2);
//获取距离 根据距离进行判断 是否变为分离状态,相连状态时,不断变化固定球的半径实现一个好像被不断抽取的变化效果
mDistance=(float) Math.hypot(event.getX()-getWidth()/2-stillBallCenterP.x,event.getY()-stillBallCenterP.y-getHeight()/2);
//这个范围内 不断变化固定球半径
if(mDistance
stillBallRadius=moveBallRadius-mDistance/8;
}else{
state=BUBBLE_STATE_APART;
}
}
invalidate();
break;
case MotionEvent.ACTION_UP:
//松开时 若之前是相连状态 则恢复成初始状态 若之前是分离状态 则改为消失状态
if (state==BUBBLE_STATE_CONNECT){
startRestAnim();
}
if (state==BUBBLE_STATE_APART){
if(mDistance<2*moveBallRadius){
startRestAnim();
}else{
startDismissAnim();
}
}
break;
}
return true;
}
/*
* 初始化数据
* */
private void initData(int w,int h){
if (moveBallCenterP==null) {
moveBallCenterP=new PointF(w/2,h/2);
}else{
moveBallCenterP.set(w/2,h/2);
}
if (stillBallCenterP==null){
stillBallCenterP=new PointF(w/2,h/2);
}else {
stillBallCenterP.set(w/2,h/2);
}
moveBallRadius=40;
stillBallRadius=moveBallRadius;
mLineMaxDistance=8*moveBallRadius;
offset=2*moveBallRadius;
//赋值初始状态
state=BUBBLE_STATE_DEFAUL;
}
/*
* 传说中的属性动画 重置
* */
private void startRestAnim(){
ValueAnimator valueAnimator=ValueAnimator.ofObject(new PointFEvaluator(),
new PointF(moveBallCenterP.x,moveBallCenterP.y),new PointF(stillBallCenterP.x,stillBallCenterP.y));
valueAnimator.setDuration(1000);
valueAnimator.setInterpolator(new OvershootInterpolator(5f));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
moveBallCenterP=(PointF) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
state=BUBBLE_STATE_DEFAUL;
}
});
valueAnimator.start();
}
/*
* 消失动画
* */
private void startDismissAnim(){
mIsBurstAnimStart=true;
state=BUBBLE_STATE_DISMISS;
ValueAnimator animator=ValueAnimator.ofInt(0,mBurstBitmapsArray.length);
animator.setDuration(1000);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurDrawableIndex=(int)animation.getAnimatedValue();
invalidate();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mIsBurstAnimStart=false;
}
});
animator.start();
}
}
跟着一点点敲出来,觉得收获很大,有种自豪感油然而生。要说具体会了什么,可能也并不会啥,但就是盲目自信了。哈哈哈
最近写的所有的自定义控件,都缺少两块比较重要的东西,1.xml可配置属性 2.measure部分。
1.实现xml可配置
1)values中建attrs文件夹
2)建 declare-styleable
3)xml中使用
4)view中获取
attrs
<declare-styleable name="QQBezierView">
<attr name="stillBallRadius" format="dimension"></attr>
<attr name="ballColor" format="color"></attr>
<attr name="textColor" format="color"></attr>
<attr name="textSize" format="dimension"></attr>
<attr name="text" format="string"></attr>
</declare-styleable>
xml
<com.whc.paintapp.QQBezierView
android:layout_width="500dp"
android:layout_height="wrap_content"
app:ballColor="@color/colorAccent"
app:stillBallRadius="30px"
app:text="200"
app:textColor="#ffffff"
app:textSize="24sp" />
view
TypedArray type=context.obtainStyledAttributes(attrs,R.styleable.QQBezierView,defStyleAttr,defStyleRes);
type.getColor(R.styleable.QQBezierView_ballColor,Color.RED);
type.getColor(R.styleable.QQBezierView_textColor,Color.WHITE);
type.getDimension(R.styleable.QQBezierView_textSize,25);
type.getDimension(R.styleable.QQBezierView_stillBallRadius,40);
2.measure部分
这部分我还在验证,也许是不对的 需要改。--用是可以放心用的
@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width=setMeasure(widthMeasureSpec);
int height=setMeasure(heightMeasureSpec);
}
private int setMeasure(int size){
int tempSize=1080;
int type=MeasureSpec.getMode(size);
int width=MeasureSpec.getSize(size);
switch (type){
case MeasureSpec.UNSPECIFIED:
width=Math.min(tempSize,width);
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
break;
}
return width;
}
2021-02-07