转眼已经18年底了,在Androd这行已经混了3年,一直说写些东西,总是没有执行。有想法的时候没时间,有时间的时候没想法,今天终于开始写一些东西了。
回首Androd,自定义View是使用最多的,也是我第一次有计划的去学习和整理的模块。以下是我总结的自定义View的一些用法,没有涉及底层原理。
一:自定义View需要重新的2个方法。
1、onMeasure 方法
通过onMeasure方法可以得到父类的specMode和size
1.1、当父View的specMode为EXACTLY的时候:父View强加给子View一个确切的大小,有如下两种情况
1.1.1) 子View的layout_width或者layout_height设置为MATCH_PARENT的时候,子View的specMode为EXACTLY
1.1.2) 子View的layout_width或者layout_height设置为WRAP_CONTENT的时候,子View的specMode为AT_MOST
1.1.3) 不论子view为match_parent或者wrap_content,resultSize都等于父类的size.
1.2、当父view的specMode为AT_MOST的时候:父View强加给一个最大的size给子view,最大的size也就是父view的size
1.2.1) 此时不论子view的为match_parent或者wrap_content,子view的specMode都为AT_MOST
1.2.2) resultSize的大小被设置为父view的大小
1.3、当父view的specMode为UNSPECIFIED的时候:
1.3.1) 此时不论子view的为match_parent或者wrap_content,子view的specMode都为UNSPECIFIED
1.3.2) 此时reusltSize = 0
2、onDraw方法
2.1、画笔
mPaint = new Paint(); //初始化
1、mPaint.setAntiAlias(true);//去除边缘锯齿,优化绘制效果
2、mPaint.setColor(Color.BLACK);//设置颜色
3、setStrokeWidth(float width) //设置画笔宽度
4、//设置线冒样式,取值有Cap.ROUND(圆形线冒)、Cap.SQUARE(方形线冒)、Cap.BUTT(无线冒)
//注意:冒多出来的那块区域就是线帽!就相当于给原来的直线加上一个帽子一样,所以叫线帽
setStrokeCap(Paint.Cap cap)
5、//设置线段连接处样式,取值有:Join.MITER(结合处为锐角)、Join.Round(结合处为圆弧)、Join.BEVEL(结合处为直线)
setStrokeJoin(Paint.Join join)
6、//设置笔画的倾斜度,90度拿画笔与30拿画笔,画出来的线条样式肯定是不一样的吧。
setStrokeMiter(float miter)
7、void reset() //清空画笔复位。
8、void set(Paint src) //设置一个外来Paint画笔
9、//获取与设置alpha值、颜色、ARGB等。
void setARGB(int a, int r, int g, int b)
int getAlpha()
void setAlpha(int a)
int getColor()
void setColor(int color)
10、//获取与设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢,一般会开启。设置后会平滑一些;
void setAntiAlias(boolean aa)
11、//获取与设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满、图像更加清晰。
final boolean isDither()
void setDither(boolean dither)
12、setPathEffect(PathEffect effect) //设置绘制路径的效果
13、CornerPathEffect //圆形拐角效果
14、paint.setPathEffect(newCornerPathEffect(100));//利用半径R=50的圆来代替原来两条直线间的夹角
15、DashPathEffect //虚线效果
16、//设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果
setXfermode(Xfermode xfermode)
17、//设置MaskFilter,可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等
setMaskFilter(MaskFilter maskfilter)
18、//设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果
setColorFilter(ColorFilter colorfilter)
19、//设置图像效果,使用Shader可以绘制出各种渐变效果
setShader(Shader shader)
20、//在图形下面设置阴影层,产生阴影效果,radius为阴影的角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色
setShadowLayer(float radius ,float dx,floatdy,int color)
21、//设置画笔样式1.
Paint.Style.FILL :填充内部
Paint.Style.FILL_AND_STROKE:填充内部和描边
Paint.Style.STROKE :仅描边、
2.2、画圆
1. canvas.drawCircle(cx, cy, radius,;//外圆
mPaint.setColor(Color.WHITE);
canvas.drawCircle(cx, cy, radius - mBorderWidth, mPaint);//内圆
2.3、绘画弧形圆
//画上面黑色半圆
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.BLACK);
RectF blackHalfRect = new RectF(cx - 45, cy - 90, cx + 45, cy);
canvas.drawArc(blackHalfRect, 270, 180, true, mPaint);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.WHITE);
RectF whiteHalfRect = new RectF(cx - 45, cy, cx + 45, cy + 90);
canvas.drawArc(whiteHalfRect, 270, -180, true, mPaint);
2.4、绘画半圆
RectF mRectf = new RectF(left, top, right,botton);
mPaint.setColor(Color.WHITE);
canvas.drawArc(mRectf, 270, 180, true, mPaint);
mPaint.setColor(Color.BLACK);
canvas.drawArc(mRectf, 270, -180, true, mPaint);
2.5、贝塞尔曲线
2.5.1、什么是贝塞尔曲线
贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。主要结构:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。
2.5.2、贝塞尔曲线的分类
2.5.2.1、一阶贝塞尔曲线(线段):
公式:
原理:
由 P0 至 P1 的连续点, 描述的一条线段
2.5.2.2、二阶贝塞尔曲线(抛物线):
公式:
原理:
由 P0 至 P1 的连续点 Q0,描述一条线段。
由 P1 至 P2 的连续点 Q1,描述一条线段。
由 Q0 至 Q1 的连续点 B(t),描述一条二次贝塞尔曲线。
2.5.2.3、三阶贝塞尔曲线:
公式:
原理:
2.5.3、贝塞尔曲线的代码实现
quadTo()方法从上一个点为起点开始绘制贝塞尔曲线,其中(x1,y1)为辅 助控制点,(x2,y2)为终点。
Path mPath = new Path();
mPath.moveTo(x0,y0);
mPath.quadTo(x1,y1,x2,y2);
如调用以上代码,即绘制起点(x0,y0),终点(x2,y2),辅助控制点(x1,y1)的贝塞尔曲线。因此,通过不断改变这三个点的位置,我们可以绘制出各种各样的曲线。
cubicTo()方法从上一个点为起点开始绘制三阶贝塞尔曲线,其中(x1,y1),( x2, y2 )为辅助控制点,(x3,y3)为终点。
2.5.4、贝塞尔曲线的应用
2.5.4.1、波浪纹
要实现一个波浪不断涌动的效果,这种效果在很多手机应用中可见,例如手机电量,内存剩余等。类似这种需要实现波浪的效果,我们需要绘制带有平滑自然效果的曲线,这时候就需要贝塞尔曲线来辅助了。
原理图:
图中的矩阵即为视图的可见范围,也就是我们手机常见的区域。通过属性动画类ValueAnimator不断改变点1的横坐标,随着点1横坐标向右移动,点2,点3,点4,点5,以及四个控制点的坐标随着点1的移动同时位移相同距离,每一次坐标点更新,我们调用一次invalidate()方法,调用draw重新绘制视图,绘制四段贝塞尔曲线。最后点1移动到原先点3的位置,这样就完成了一次动画。
这样,通过循环不断的动画效果,我们就实现了波浪的效果。
2.5.4.2、粘连体
利用二阶贝塞尔曲线还可以实现,类似两种物体粘合在一起的效果,比如我们常用的qq,在qq聊天列表上有一个非常有意思的功能,就是当我们用手指移动聊天列表上的未读消息标志的时候,它与聊天列表会产生粘连的效果:
现在,我们来模拟这种效果,利用学到的二阶贝塞尔曲线来绘制。
我们看到原理图,基本构造为两个圆,和两端贝塞尔曲线,绘制贝塞尔曲线,由于这是一个二阶的贝塞尔曲线,我们只需要一个控制点,在这个图里,我们的两条贝塞尔曲线的两个控制点分别为(x1,y1)(x4, y4)的中点,(x2, y2)(x3, y3)的中点。
从图中可以看出,我们的贝塞尔曲线由我们的控制点控制,控制点又是被起点和终点控制着,因此,当两个圆距离越大,曲线越趋于平缓,当两个圆距离越小,曲线的波动度越大,这样,我们想要的粘连的效果就实现了。另外,这里有一个还有角度(图中的45度角)可以用来控制,也可以作为控制曲线波动度的参数。
通过以上分析,我们通过一个方法来绘制两个圆之间的粘连体路径:
我们给控件设置一个粘连的最大距离,即如果两个圆之间的距离超过这个值,则不再绘制粘连体。
2.5.4.3、弹性球
三阶贝塞尔曲线,就是有两个控制点,它相比二阶曲线的优点是,由于控制点的增加,它能够更加轻松地绘制出更平滑更自然的曲线。
如何绘制类似这种,看起来具有弹性球的滑动球,我们需要使用三阶贝塞尔曲线,那么首先如何用三阶贝塞尔曲线绘制出一个圆,这里有一篇文章,是关于如何用贝塞尔曲线绘制圆:http://spencermortensen.com/articles/bezier-circle/ ,大概意思是讲,我们需要一个值就是c = 0.552284749,如下图,要绘制右上角的圆弧,我们需要两个控制点,其中B就是一个控制点,我们需要保证AB = c *r,即可以画出1/4的圆弧,以此类推,连续画四段这样的圆弧,就可以画出一个标准的圆。
接下来我们观察弹性球的运动,大概可以分为以下几个阶段:
1)开始启动,此时右边点位移,其他点不动
2)开始加速
3)减速
4)到达终点
5)回弹效果
上面完成了一个弹性球的封装,可以实现四个方向的运动,然后我们实现一个弹性球的loader
package com.zero.bezier.widget.elastic;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.graphics.Path;
import android.graphics.PointF;
import android.view.animation.AccelerateDecelerateInterpolator;
/**
* 弹性球
* @author linzewu
* @date 2016/6/1
*/
public class ElasticBall extends Ball {
/**
* 向上运动
*/
private static final int DIRECTION_UP = 1;
/**
* 向下运动
*/
private static final int DIRECTION_DOWN = 2;
/**
* 向左运动
*/
private static final int DIRECTION_LEFT = 3;
/**
* 向右运动
*/
private static final int DIRECTION_RIGHT = 4;
/**
* 运动方向
*/
private int mDirection;
/**
* 动画完成百分比(0~1)
*/
private float mAnimPercent;
/**
* 弹性距离
*/
private float mElasticDistance;
/**
* 弹性比例
*/
private float mElasticPercent = 0.8f;
/**
* 位移距离
*/
private float mMoveDistance;
/**
* 动画消费时间
*/
private long mDuration = 1500;
/**
* 偏移值
*/
private float offsetTop, offsetBottom, offsetLeft, offsetRight;
/**
* 圆形偏移比例
*/
private float c = 0.551915024494f;
private float c2 = 0.65f;
/**
* 动画开始点
*/
private Ball mStartPoint;
/**
* 动画结束点
*/
private Ball mEndPoint;
/**
* 构造方法
*
* @param x 圆心横坐标
* @param y 圆心纵坐标
* @param radius 圆半径
*/
public ElasticBall(float x, float y, float radius) {
super(x, y, radius);
init();
}
private void init() {
mElasticDistance = mElasticPercent * radius;
offsetTop = c * radius;
offsetBottom = c * radius;
offsetLeft = c * radius;
offsetRight = c * radius;
}
public interface ElasticBallInterface{
void onChange(Path path);
void onFinish();
}
private ElasticBallInterface mElasticBallInterface;
/**
* 对外公布方法,设置弹性比例 (0~1)
* @param elasticPercent
*/
public void setElasticPercent(float elasticPercent) {
this. mElasticPercent= elasticPercent;
}
/**
* 对外公布方法,设置动画时间
* @param duration
*/
public void setDuration(long duration) {
this.mDuration = duration;
}
/**
* 对外公布方法, 开启动画
* @param endPoint
*/
public void startElasticAnim(PointF endPoint, ElasticBallInterface elasticBallInterface) {
this.mEndPoint = new ElasticBall(endPoint.x, endPoint.y, radius);
this.mStartPoint = new ElasticBall(x, y, radius);
this.mStatusPoint1 = new ElasticBall(x, y, radius);
this.mStatusPoint2 = new ElasticBall(x, y, radius);
this.mStatusPoint3 = new ElasticBall(x, y, radius);
this.mStatusPoint4 = new ElasticBall(x, y, radius);
this.mStatusPoint5 = new ElasticBall(x, y, radius);
this.mElasticBallInterface = elasticBallInterface;
judgeDirection();
mMoveDistance = getDistance(mStartPoint.x, mStatusPoint1.y, endPoint.x, endPoint.y);
animStatus0();
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.setDuration(mDuration);
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.start();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimPercent = (float) animation.getAnimatedValue();
if(mAnimPercent>=0 && mAnimPercent <= 0.2){
animStatus1();
} else if(mAnimPercent > 0.2 && mAnimPercent <= 0.5){
animStatus2();
} else if(mAnimPercent > 0.5 && mAnimPercent <= 0.8){
animStatus3();
} else if(mAnimPercent > 0.8 && mAnimPercent <= 0.9){
animStatus4();
} else if(mAnimPercent > 0.9&&mAnimPercent <= 1){
animStatus5();
}
if (mElasticBallInterface != null) {
mElasticBallInterface.onChange(drawElasticCircle(topX, topY, offsetTop, offsetTop,
bottomX, bottomY, offsetBottom, offsetBottom,
leftX, leftY, offsetLeft, offsetLeft,
rightX, rightY, offsetRight, offsetRight));
}
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
if (mElasticBallInterface != null) {
mElasticBallInterface.onFinish();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
private void judgeDirection() {
if (mEndPoint.x - mStartPoint.x > 0) {
mDirection = DIRECTION_RIGHT;
}else if (mEndPoint.x - mStartPoint.x < 0) {
mDirection = DIRECTION_LEFT;
}else if (mEndPoint.y - mStartPoint.x > 0) {
mDirection = DIRECTION_DOWN;
}else if (mEndPoint.y - mStartPoint.y < 0){
mDirection = DIRECTION_UP;
}
}
/**
* 动画状态0 (初始状态:圆形)
*/
private void animStatus0() {
offsetTop = c * radius;
offsetBottom = c * radius;
offsetLeft = c * radius;
offsetRight = c * radius;
}
private Ball mStatusPoint1;
/**
* 动画状态1 (0~0.2)
*/
private void animStatus1() {
float percent = mAnimPercent * 5f;
if (mDirection == DIRECTION_LEFT) {
leftX = mStartPoint.leftX - percent * mElasticDistance;
} else if (mDirection == DIRECTION_RIGHT) {
rightX = mStartPoint.rightX + percent * mElasticDistance;
} else if (mDirection == DIRECTION_UP) {
topY = mStartPoint.topY - percent * mElasticDistance;
} else if (mDirection == DIRECTION_DOWN) {
bottomY = mStartPoint.bottomY + percent * mElasticDistance;
}
mStatusPoint1.refresh(x, y, topX, topY, bottomX, bottomY,
leftX, leftY, rightX, rightY);
}
private Ball mStatusPoint2;
/**
* 动画状态2 (0.2~0.5)
*/
private void animStatus2() {
float percent = (float) ((mAnimPercent - 0.2) * (10f / 3));
if (mDirection == DIRECTION_LEFT) {
leftX = mStatusPoint1.leftX - percent * (mMoveDistance / 2 - mElasticDistance / 2 );
x = mStatusPoint1.x - percent * (mMoveDistance / 2);
rightX = mStatusPoint1.rightX - percent * (mMoveDistance / 2 - mElasticDistance / 2 );
topX = x;
bottomX = x;
//偏移值稍作变化
offsetTop = radius * c + radius * ( c2 - c ) * percent;
offsetBottom = radius * c + radius * ( c2 - c ) * percent;
} else if (mDirection == DIRECTION_RIGHT) {
rightX = mStatusPoint1.rightX + percent * (mMoveDistance / 2 - mElasticDistance / 2 );
x = mStatusPoint1.x + percent * (mMoveDistance / 2);
leftX = mStatusPoint1.leftX + percent * (mMoveDistance / 2 - mElasticDistance / 2 );
topX = x;
bottomX = x;
//偏移值稍作变化
offsetTop = radius * c + radius * ( c2 - c ) * percent;
offsetBottom = radius * c + radius * ( c2 - c ) * percent;
} else if (mDirection == DIRECTION_UP) {
topY = mStatusPoint1.topY - percent * (mMoveDistance / 2 - mElasticDistance / 2 );
y = mStatusPoint1.y - percent * (mMoveDistance / 2);
bottomY = mStatusPoint1.bottomY - percent * (mMoveDistance / 2 - mElasticDistance / 2 );
leftY = y;
rightY = y;
//偏移值稍作变化
offsetLeft = radius * c + radius * ( c2 - c ) * percent;
offsetRight = radius * c + radius * ( c2 - c ) * percent;
} else if (mDirection == DIRECTION_DOWN) {
bottomY = mStatusPoint1.bottomY + percent * (mMoveDistance / 2 - mElasticDistance / 2 );
y = mStatusPoint1.y + percent * (mMoveDistance / 2);
topY = mStatusPoint1.topY + percent * (mMoveDistance / 2 - mElasticDistance / 2 );
leftY = y;
rightY = y;
//偏移值稍作变化
offsetLeft = radius * c + radius * ( c2 - c ) * percent;
offsetRight = radius * c + radius * ( c2 - c ) * percent;
}
mStatusPoint2.refresh(x, y, topX, topY, bottomX, bottomY,
leftX, leftY, rightX, rightY);
}
private Ball mStatusPoint3;
/**
* 动画状态3 (0.5~0.8)
*/
private void animStatus3() {
float percent = (mAnimPercent - 0.5f) * (10f / 3f);
if (mDirection == DIRECTION_LEFT) {
leftX = mStatusPoint2.leftX - Math.abs(percent * (mEndPoint.rightX - mStatusPoint2.rightX));
x = mStatusPoint2.x - Math.abs(percent * (mEndPoint.x - mStatusPoint2.x));
rightX = mStatusPoint2.rightX - Math.abs(percent * (mEndPoint.x - mStatusPoint2.x));
topX = x;
bottomX = x;
//偏移值稍作变化
offsetTop = radius * c2 - radius * ( c2 - c ) * percent;
offsetBottom = radius * c2 - radius * ( c2 - c ) * percent;
} else if (mDirection == DIRECTION_RIGHT) {
rightX = mStatusPoint2.rightX + percent * (mEndPoint.rightX - mStatusPoint2.rightX);
x = mStatusPoint2.x + percent * (mEndPoint.x - mStatusPoint2.x);
leftX = mStatusPoint2.leftX + percent * (mEndPoint.x - mStatusPoint2.x);
topX = x;
bottomX = x;
//偏移值稍作变化
offsetTop = radius * c2 - radius * ( c2 - c ) * percent;
offsetBottom = radius * c2 - radius * ( c2 - c ) * percent;
} else if (mDirection == DIRECTION_UP) {
topY = mStatusPoint2.topY - Math.abs(percent * (mEndPoint.topY - mStatusPoint2.topY));
y = mStatusPoint2.y - Math.abs(percent * (mEndPoint.y - mStatusPoint2.y));
bottomY = mStatusPoint2.bottomY - Math.abs(percent * (mEndPoint.y - mStatusPoint2.y));
leftY = y;
rightY = y;
//偏移值稍作变化
offsetLeft = radius * c2 - radius * ( c2 - c ) * percent;
offsetRight = radius * c2 - radius * ( c2 - c ) * percent;
} else if (mDirection == DIRECTION_DOWN) {
bottomY = mStatusPoint2.bottomY + percent * (mEndPoint.bottomY - mStatusPoint2.bottomY);
y = mStatusPoint2.y + percent * (mEndPoint.y - mStatusPoint2.y);
topY = mStatusPoint2.topY + percent * (mEndPoint.y - mStatusPoint2.y);
leftY = y;
rightY = y;
//偏移值稍作变化
offsetLeft = radius * c2 - radius * ( c2 - c ) * percent;
offsetRight = radius * c2 - radius * ( c2 - c ) * percent;
}
mStatusPoint3.refresh(x, y, topX, topY, bottomX, bottomY,
leftX, leftY, rightX, rightY);
}
private Ball mStatusPoint4;
/**
* 动画状态4 (0.8~0.9)
*/
private void animStatus4() {
float percent = (float) (mAnimPercent - 0.8) * 10;
if (mDirection == DIRECTION_LEFT) {
rightX = mStatusPoint3.rightX - percent * (Math.abs(mEndPoint.rightX - mStatusPoint3.rightX) + mElasticDistance/2);
//再做一次赋值,防止和终点不重合
leftX = mEndPoint.leftX;
x = mEndPoint.x;
bottomX = mEndPoint.bottomX;
topX = mEndPoint.topX;
} else if (mDirection == DIRECTION_RIGHT) {
leftX = mStatusPoint3.leftX + percent * (mEndPoint.leftX - mStatusPoint3.leftX +
mElasticDistance/2);
//再做一次赋值,防止和终点不重合
rightX = mEndPoint.rightX;
x = mEndPoint.x;
bottomX = mEndPoint.bottomX;
topX = mEndPoint.topX;
} else if (mDirection == DIRECTION_UP) {
bottomY = mStatusPoint3.bottomY - percent * (Math.abs(mEndPoint.bottomY - mStatusPoint3
.bottomY) + mElasticDistance/2);
//再做一次赋值,防止和终点不重合
topY = mEndPoint.topY;
y = mEndPoint.y;
leftY = mEndPoint.leftY;
rightY = mEndPoint.rightY;
} else if (mDirection == DIRECTION_DOWN) {
topY = mStatusPoint3.topY + percent * (mEndPoint.topY - mStatusPoint3
.topY + mElasticDistance/2);
//再做一次赋值,防止和终点不重合
bottomY = mEndPoint.bottomY;
y = mEndPoint.y;
leftY = mEndPoint.leftY;
rightY = mEndPoint.rightY;
}
mStatusPoint4.refresh(x, y, topX, topY, bottomX, bottomY,
leftX, leftY, rightX, rightY);
}
private Ball mStatusPoint5;
/**
* 动画状态5 (0.9~1)回弹
*/
private void animStatus5() {
float percent = (float) (mAnimPercent - 0.9) * 10;
if (mDirection == DIRECTION_LEFT) {
rightX = mStatusPoint4.rightX + percent * (mEndPoint.rightX - mStatusPoint4.rightX);
} else if (mDirection == DIRECTION_RIGHT) {
leftX = mStatusPoint4.leftX + percent * (mEndPoint.leftX - mStatusPoint4.leftX);
} else if (mDirection == DIRECTION_UP) {
bottomY = mStatusPoint4.bottomY + percent * (mEndPoint.bottomY - mStatusPoint4.bottomY);
} else if (mDirection == DIRECTION_DOWN) {
topY = mStatusPoint4.topY + percent * (mEndPoint.topY - mStatusPoint4.topY);
}
mStatusPoint5.refresh(x, y, topX, topY, bottomX, bottomY,
leftX, leftY, rightX, rightY);
}
/**
* 绘制弹性圆
* 通过绘制四段三阶贝塞尔曲线,来实现有弹性变化的圆
* @param topX
* @param topY
* @param offsetTop1
* @param offsetTop2
* @param bottomX
* @param bottomY
* @param offsetBottom1
* @param offsetBottom2
* @param leftX
* @param leftY
* @param offsetLeft1
* @param offsetLeft2
* @param rightX
* @param rightY
* @param offsetRight1
* @param offsetRight2
* @return
*/
private Path drawElasticCircle(
float topX, float topY, float offsetTop1, float offsetTop2,
float bottomX, float bottomY, float offsetBottom1, float offsetBottom2,
float leftX, float leftY, float offsetLeft1, float offsetLeft2,
float rightX, float rightY, float offsetRight1, float offsetRight2
) {
/**
* 绘制每一段三阶贝塞尔曲线需要两个控制点
*/
PointF controlTop1, controlTop2, controlBottom1, controlBottom2,
controlLeft1, controlLeft2, controlRight1, controlRight2;
controlTop1 = new PointF();
controlTop1.x = topX - offsetTop1;
controlTop1.y = topY;
controlTop2 = new PointF();
controlTop2.x = topX + offsetTop2;
controlTop2.y = topY;
controlBottom1 = new PointF();
controlBottom1.x = bottomX - offsetBottom1;
controlBottom1.y = bottomY;
controlBottom2 = new PointF();
controlBottom2.x = bottomX + offsetBottom2;
controlBottom2.y = bottomY;
controlLeft1 = new PointF();
controlLeft1.x = leftX;
controlLeft1.y = leftY - offsetLeft1;
controlLeft2 = new PointF();
controlLeft2.x = leftX;
controlLeft2.y = leftY + offsetLeft2;
controlRight1 = new PointF();
controlRight1.x = rightX;
controlRight1.y = rightY - offsetRight1;
controlRight2 = new PointF();
controlRight2.x = rightX;
controlRight2.y = rightY + offsetRight2;
Path path = new Path();
/**
* 绘制top到left的圆弧
*/
path.moveTo(topX, topY);
path.cubicTo(controlTop1.x, controlTop1.y, controlLeft1.x, controlLeft1.y, leftX, leftY);
/**
* 绘制left到bottom的圆弧
*/
path.cubicTo(controlLeft2.x ,controlLeft2.y, controlBottom1.x, controlBottom1.y, bottomX, bottomY);
/**
* 绘制bottom到right的圆弧
*/
path.cubicTo(controlBottom2.x, controlBottom2.y, controlRight2.x, controlRight2.y, rightX, rightY);
/**
* 绘制right到top的圆弧
*/
path.cubicTo(controlRight1.x, controlRight1.y, controlTop2.x, controlTop2.y, topX, topY);
return path;
}
/**
* 求两点之间的距离
* @param x1 第一个点的横坐标
* @param y1 第一个点的纵坐标
* @param x2 第二个点的横坐标
* @param y2 第二个点的纵坐标
* @return 两点距离
*/
private float getDistance(float x1, float y1, float x2, float y2) {
return (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
}