自定义一个view画(ondraw)两个静态圆
先放到布局文件中
绘制两个圆—要看看两个圆的坐标大致放在哪里
(300,300)(500,300)
半径给20
public class GooView extends View {
private Paint paint;
public GooView(Context context) {
this(context, null);
}
public GooView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GooView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
paint.setColor(Color.RED);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(300, 300, 20, paint);
canvas.drawCircle(300, 400, 20, paint);
}
}
绘制静态圆的连接部分
花两根曲线—填充中间部分—由于是不规则图形—所以通过path来画
曲线的因素—给定起点和终点,还有弯曲点
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(300, 300, 20, paint);
canvas.drawCircle(300, 400, 20, paint);
Path path1 = new Path();
path1.moveTo(400, 280);//指定曲线的起点
//quadTo画贝塞尔曲线--参1参2控制点(弯曲点)--参3参4结束点
path1.quadTo(350, 300, 300, 280);
//把path画出来
//连线到第2条曲线的起点
path1.lineTo(300, 320);//直线
path1.quadTo(350, 300, 400, 320);
path1.close();//闭合--会自动闭合
canvas.drawPath(path1, paint);//会自动闭合,会自动填充
}
定义变量来替换固定值的位置—以达到动态变换
FloatEvaluator floatEval = new FloatEvaluator();
float dragRadius = 20;//drag圆的半径
float stickyRadius = 20;//sticky圆的半径
PointF dragCenter = new PointF(400, 300);//drag圆的圆心(PointF点)(Point--是int值)
PointF stickyCenter = new PointF(400, 300);//sticky圆的圆心
PointF controlPoint = new PointF(350, 300);//控制点
//sticky圆上的2个点—数组方便计算
PointF[] stickyPoints = {new PointF(400, 280), new PointF(400, 320)};
//drag圆上的2个点
PointF[] dragPoints = {new PointF(300, 280), new PointF(300, 320)};
double lineK;//角的斜率,就是正切值, tan
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(dragCenter.x, dragCenter.y, dragRadius, paint);
canvas.drawCircle(stickyCenter.x, stickyCenter.y, stickyRadius, paint);
//2.绘制2圆中间连接的部分
Path path = new Path();
path.moveTo(stickyPoints[0].x, stickyPoints[0].y);//指定曲线的起点
//quadTo画贝塞尔曲线
path.quadTo(controlPoint.x, controlPoint.y, dragPoints[0].x, dragPoints[0].y);
//连线到第2条曲线的起点
path.lineTo(dragPoints[1].x, dragPoints[1].y);
path.quadTo(controlPoint.x, controlPoint.y, stickyPoints[1].x, stickyPoints[1].y);
path.close();
canvas.drawPath(path, paint);
}
让drag圆跟随手指的移动而改变位置
Drag圆的圆心与手指的触摸点的圆心一致
取出触摸点—在ontouch中
并让drag圆的圆心变成触摸点
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
//按下和移动都可以改变圆的坐标
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
//设置drag圆的圆心点--会改变圆心的坐标-但界面不会发生变化--得重绘—point有设置点的坐标的方法
dragCenter.set(event.getX(), event.getY());
break;
}
//刷新重绘
invalidate();
return true;
}
动态计算4个点和控制点的坐标
圆心与半径已知
double lineK;//角的斜率,就是正切值, tan
@Override
protected void onDraw(Canvas canvas) {
//a.计算正切值
float dy = dragCenter.y - stickyCenter.y;//2圆圆心y的差值
float dx = dragCenter.x - stickyCenter.x;//
if (dx != 0) {
lineK = dy / dx;
}
//b.动态计算4个点的坐标
dragPoints = GeometryUtil.getIntersectionPoints(dragCenter, dragRadius, lineK);
stickyPoints = GeometryUtil.getIntersectionPoints(stickyCenter, stickyRadius, lineK);
//c.动态计算控制点
controlPoint = GeometryUtil.getPointByPercent(dragCenter, stickyCenter, 0.618f);
}
动态计算stick圆点 的坐标
//2圆圆心最大的距离
int maxDistance = 150;
//浮点计算器
FloatEvaluator floatEval = new FloatEvaluator();
/**
* 动态计算sticky圆的半径
*
* @return
*/
private float calculateStickyRadius() {
//计算2圆圆心当前的距离
float distance = GeometryUtil.getDistanceBetween2Points(dragCenter, stickyCenter);
//比例
float fraction = distance / maxDistance;
//浮点计算器--计算--参1比例--参2起始值,参3结束值
return floatEval.evaluate(fraction, 20, 4);
}
@Override
protected void onDraw(Canvas canvas) {
//动态计算sticky圆的半径
stickyRadius = calculateStickyRadius();
}
几何图形工具类
/**
* 几何图形工具
*/
public class GeometryUtil {
/**
* As meaning of method name.
* 获得两点之间的距离
*
* @param p0
* @param p1
* @return
*/
public static float getDistanceBetween2Points(PointF p0, PointF p1) {
float distance = (float) Math.sqrt(Math.pow(p0.y - p1.y, 2) + Math.pow(p0.x - p1.x, 2));
return distance;
}
/**
* Get middle point between p1 and p2.
* 获得两点连线的中点
*
* @param p1
* @param p2
* @return
*/
public static PointF getMiddlePoint(PointF p1, PointF p2) {
return new PointF((p1.x + p2.x) / 2.0f, (p1.y + p2.y) / 2.0f);
}
/**
* Get point between p1 and p2 by percent.
* 根据百分比获取两点之间的某个点坐标
*
* @param p1
* @param p2
* @param percent
* @return
*/
public static PointF getPointByPercent(PointF p1, PointF p2, float percent) {
return new PointF(evaluateValue(percent, p1.x, p2.x), evaluateValue(percent, p1.y, p2.y));
}
/**
* 根据分度值,计算从start到end中,fraction位置的值。fraction范围为0 -> 1
*
* @param fraction
* @param start
* @param end
* @return
*/
public static float evaluateValue(float fraction, Number start, Number end) {
return start.floatValue() + (end.floatValue() - start.floatValue()) * fraction;
}
/**
* Get the point of intersection between circle and line.
* 获取 通过指定圆心,斜率为lineK的直线与圆的交点。
*
* @param pMiddle The circle center point.
* @param radius The circle radius.
* @param lineK The slope of line which cross the pMiddle.
* @return
*/
public static PointF[] getIntersectionPoints(PointF pMiddle, float radius, Double lineK) {
PointF[] points = new PointF[2];
float radian, xOffset = 0, yOffset = 0;
if (lineK != null) {
radian = (float) Math.atan(lineK);//得到该角的角度
xOffset = (float) (Math.sin(radian) * radius);//得到对边的长
yOffset = (float) (Math.cos(radian) * radius);//得到邻边的长
} else {
xOffset = radius;
yOffset = 0;
}
points[0] = new PointF(pMiddle.x + xOffset, pMiddle.y - yOffset);
points[1] = new PointF(pMiddle.x - xOffset, pMiddle.y + yOffset);
return points;
}
}
当拖动超出保护圈-则连接部位断掉
断掉就是不绘制
定义变量当超出时不绘制连接部位
定义变量确定是否超出范围
boolean isOutOfRange = false;//是否超出最大范围
拖动时进行判断(每次都会重绘)
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
//按下和移动都可以改变圆的坐标
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
isOutOfRange = distance > maxDistance;
break;
重绘—当超出不绘制中间连接部位
@Override
protected void onDraw(Canvas canvas) {
if (!isOutOfRange) {
//绘制固定圆
canvas.drawCircle(stickyCenter.x, stickyCenter.y, stickyRadius, paint);
//2.绘制2圆中间连接的部分
Path path = new Path();
path.moveTo(stickyPoints[0].x, stickyPoints[0].y);//指定曲线的起点
//quadTo画贝塞尔曲线
path.quadTo(controlPoint.x, controlPoint.y, dragPoints[0].x, dragPoints[0].y);
//连线到第2条曲线的起点
path.lineTo(dragPoints[1].x, dragPoints[1].y);
path.quadTo(controlPoint.x, controlPoint.y, stickyPoints[1].x, stickyPoints[1].y);
path.close();
canvas.drawPath(path, paint);
}
}
手指抬起处理
处理圈内抬起手指的弹回动画
case MotionEvent.ACTION_UP:
if (isOutOfRange) {
//在圈外抬起手指,执行爆炸动画
execBoomAnim(dragCenter.x, dragCenter.y);
//重置坐标
dragCenter.set(stickyCenter);
} else {
//在圈内,将圆回弹到sticky的位置
final PointF start = new PointF(dragCenter.x, dragCenter.y);
ValueAnimator animator = ValueAnimator.ofFloat(1, 3);//--随便填
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获取动画执行的百分比--0--1
float fraction = animation.getAnimatedFraction();
//获取两点间的任一比例的值(参1起始点,参2终止点)
PointF p = GeometryUtil.getPointByPercent(start, stickyCenter, fraction);
//将drag圆的圆心设置p--只是改变点的坐标
dragCenter.set(p);
//重绘
invalidate();
}
});
//加点弹性
animator.setInterpolator(new OvershootInterpolator(4));
animator.setDuration(500).start();
}
break;
}
圈外抬起执行爆炸动画
画保护圈
//给Sticky画出保护圈
paint.setStyle(Paint.Style.STROKE);//只有边框线
canvas.drawCircle(stickyCenter.x, stickyCenter.y, maxDistance, paint);
paint.setStyle(Paint.Style.FILL);//填充内容--画完之后填充内容--因为每次都会重绘
case MotionEvent.ACTION_UP:
if (isOutOfRange) {
//在圈外抬起手指,执行爆炸动画
//在手指抬起的位置执行爆炸
execBoomAnim(dragCenter.x, dragCenter.y);
//重置坐标--爆炸之后坐标回去
dragCenter.set(stickyCenter);
} else {
//在圈内,将圆回弹到sticky的位置
}
break;
帧动画xml—动画需要view作为载体
创建执行爆炸
/**
* 创建爆炸动画
*
* @param x
* @param y
*/
//帧动画--将几张图片拷贝过来
private void execBoomAnim ( float x, float y){
//创建载体view
final ImageView imageView = new ImageView(getContext());
//从图片取
int w = getResources().getDrawable(R.drawable.pop1).getIntrinsicWidth();
//设置宽高
imageView.setLayoutParams(new ViewGroup.LayoutParams(w, w));
//将动画--设置给背景
imageView.setBackgroundResource(R.drawable.boom_anim);
//播放帧动画
AnimationDrawable drawable = (AnimationDrawable) imageView.getBackground();
drawable.start();
//将载体view添加到父view中(界面),--这样子就看到了
final ViewGroup parent = (ViewGroup) getParent();//得到gooView的父view
parent.addView(imageView);
//移动ImageView到合适的位置
//x-imageview 的一半
imageView.setTranslationX(x - 34);
imageView.setTranslationY(y - 34);
//播放完毕后消失--因为没有动画监听器
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
parent.removeView(imageView);
}
}, 650);
}
全码
public class GooView extends View {
private Paint paint;
public GooView(Context context) {
this(context, null);
}
public GooView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GooView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.RED);
}
FloatEvaluator floatEval = new FloatEvaluator();
float dragRadius = 20;//drag圆的半径
float stickyRadius = 20;//sticky圆的半径
PointF dragCenter = new PointF(400, 300);//drag圆的圆心
PointF stickyCenter = new PointF(400, 300);//sticky圆的圆心
PointF controlPoint = new PointF(350, 300);//控制点
//sticky圆上的2个点
PointF[] stickyPoints = {new PointF(400, 280), new PointF(400, 320)};
//drag圆上的2个点
PointF[] dragPoints = {new PointF(300, 280), new PointF(300, 320)};
double lineK;//角的斜率,就是正切值, tan
@Override
protected void onDraw(Canvas canvas) {
//动态计算sticky圆的半径
stickyRadius = calculateStickyRadius();
//a.计算正切值
float dy = dragCenter.y - stickyCenter.y;//2圆圆心y的差值
float dx = dragCenter.x - stickyCenter.x;//
if(dx!=0){
lineK = dy / dx;
}
//b.动态计算4个点的坐标
dragPoints = GeometryUtil.getIntersectionPoints(dragCenter, dragRadius, lineK);
stickyPoints = GeometryUtil.getIntersectionPoints(stickyCenter, stickyRadius, lineK);
//c.动态计算控制点
controlPoint = GeometryUtil.getPointByPercent(dragCenter,stickyCenter,0.618f);
//1.画2个静态圆
canvas.drawCircle(dragCenter.x, dragCenter.y, dragRadius, paint);
if(!isOutOfRange){
canvas.drawCircle(stickyCenter.x, stickyCenter.y, stickyRadius, paint);
//2.绘制2圆中间连接的部分
Path path = new Path();
path.moveTo(stickyPoints[0].x, stickyPoints[0].y);//指定曲线的起点
path.quadTo(controlPoint.x, controlPoint.y, dragPoints[0].x, dragPoints[0].y);
//连线到第2条曲线的起点
path.lineTo(dragPoints[1].x, dragPoints[1].y);
path.quadTo(controlPoint.x, controlPoint.y, stickyPoints[1].x, stickyPoints[1].y);
path.close();
canvas.drawPath(path, paint);
}
//给Sticky画出保护圈
paint.setStyle(Paint.Style.STROKE);//只有边框线
canvas.drawCircle(stickyCenter.x, stickyCenter.y, maxDistance, paint);
paint.setStyle(Paint.Style.FILL);//填充内容
}
//2圆圆心最大的距离
int maxDistance = 150;
/**
* 动态计算sticky圆的半径
* @return
*/
private float calculateStickyRadius() {
//计算2圆圆心当前的距离
float distance = GeometryUtil.getDistanceBetween2Points(dragCenter, stickyCenter);
float fraction = distance / maxDistance;
return floatEval.evaluate(fraction, 20, 4);
}
boolean isOutOfRange = false;//是否超出最大范围
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
dragCenter.set(event.getX(), event.getY());
float distance = GeometryUtil.getDistanceBetween2Points(dragCenter, stickyCenter);
isOutOfRange = distance > maxDistance;
break;
case MotionEvent.ACTION_UP:
if(isOutOfRange){
//在圈外抬起手指,执行爆炸动画
execBoomAnim(dragCenter.x,dragCenter.y);
//重置坐标
dragCenter.set(stickyCenter);
}else {
//在圈内,将圆回弹到sticky的位置
final PointF start = new PointF(dragCenter.x, dragCenter.y);
ValueAnimator animator = ValueAnimator.ofFloat(1,3);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction = animation.getAnimatedFraction();
PointF p = GeometryUtil.getPointByPercent(start, stickyCenter, fraction);
//将drag圆的圆心设置p
dragCenter.set(p);
invalidate();
}
});
animator.setInterpolator(new OvershootInterpolator(4));
animator.setDuration(500).start();
}
break;
}
//刷新重绘
invalidate();
return true;
}
/**
* 创建爆炸动画
* @param x
* @param y
*/
private void execBoomAnim(float x, float y) {
final ImageView imageView = new ImageView(getContext());
int w = getResources().getDrawable(R.drawable.pop1).getIntrinsicWidth();
imageView.setLayoutParams(new ViewGroup.LayoutParams(w,w));
imageView.setBackgroundResource(R.drawable.boom_anim);
AnimationDrawable drawable = (AnimationDrawable) imageView.getBackground();
drawable.start();
//添加进来
final ViewGroup parent = (ViewGroup) getParent();
parent.addView(imageView);
//移动ImageView到合适的位置
imageView.setTranslationX(x-34);
imageView.setTranslationY(y-34);
//播放完毕后消失
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
parent.removeView(imageView);
}
},650);
}
}
动画
drawable