原创

Android利用贝塞尔曲线实现海浪和QQ消息删除效果功能

贝塞尔曲线在上一篇文章中,在这里就不过多解释了。本文主要介绍的是Android利用贝塞尔曲线实现海浪和QQ消息删除效果等功能。Android实现赛贝尔曲线介绍及源码分析:https://blog.yoodb.com/fakepoets/article/detail/1456

1、Android利用贝塞尔曲线实现一个动态的海浪效果,具体代码如下:

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
/**
 * @author fakepoets
 * @date 2017/10/23
 */
public class WaveView extends View {
    /**
     * 波峰
     */
    private float mWavePeak = 35f;
    /**
     * 波槽
     */
    private float mWaveTrough = 35f;
    /**
     * 水位
     */
    private float mWaveHeight = 250f;
    private Paint mPaint;
    private Path mPath;
    private int mWaterColor = 0xBB0000FF;
    public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
    }
    public WaveView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
    }
    public WaveView(Context context) {
super(context);
init();
    }
    private void init() {
mPath = new Path();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mWaterColor);
    }
    private float mWidth, mHeight;
    private PointF mStart;
    private PointF mLeft1,mLeft2;
    private PointF mFirst, mSecond;
    private PointF mRight;
    private PointF mControlLeft1,mControlLeft2;
    private PointF mControlFirst;
    private PointF mControlSecond;
    private boolean mIsRunning = false;
    private boolean mHasInit = false;
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (!mHasInit) {
    mWidth = w;
    mHeight = h;
    mHasInit = true;
}
super.onSizeChanged(w, h, oldw, oldh);
    }
    @Override
    protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!mIsRunning || !mHasInit)
    return;
mPath.reset();
mPath.moveTo(mLeft1.x, mLeft1.y);
mPath.quadTo(mControlLeft1.x, mControlLeft1.y, mLeft2.x, mLeft2.y);
mPath.quadTo(mControlLeft2.x, mControlLeft2.y, mFirst.x, mFirst.y);
mPath.quadTo(mControlFirst.x, mControlFirst.y, mSecond.x, mSecond.y);
mPath.quadTo(mControlSecond.x, mControlSecond.y, mRight.x, mRight.y);
mPath.lineTo(mRight.x, mHeight);
mPath.lineTo(mLeft1.x, mHeight);
mPath.lineTo(mLeft1.x, mLeft1.y);
canvas.drawPath(mPath, mPaint);
    }
    private void reset() {
mStart = new PointF(-mWidth, mHeight - mWaveHeight);
mLeft1 = new PointF(-mWidth, mHeight - mWaveHeight);
mLeft2 = new PointF(-mWidth/2f, mHeight - mWaveHeight);
mFirst = new PointF(0, mHeight - mWaveHeight);
mSecond = new PointF(mWidth / 2f, mHeight - mWaveHeight);
mRight = new PointF(mWidth, mHeight - mWaveHeight);
mControlLeft1 = new PointF(mLeft1.x + mWidth / 4f, mLeft1.y + mWavePeak);
mControlLeft2 = new PointF(mLeft2.x + mWidth / 4f, mLeft2.y - mWaveTrough);
mControlFirst = new PointF(mFirst.x + mWidth / 4f, mFirst.y + mWavePeak);
mControlSecond = new PointF(mSecond.x + mWidth / 4f, mSecond.y - mWaveTrough);
    }
    private class WaveHandler extends Handler {
@Override
public void handleMessage(Message msg) {
    super.handleMessage(msg);
    if (msg.what == ANIM_START) {
reset();
startAnim();
mIsRunning = true;
    }
}
    }
    private WaveHandler mWaveHandler = new WaveHandler();
    private static final int ANIM_START = 1;
    public void setRunning() {
new Thread(new Runnable() {
    @Override
    public void run() {
while (!mHasInit) {
    try {
Thread.sleep(50);
    } catch (InterruptedException e) {
e.printStackTrace();
    }
}
mWaveHandler.sendEmptyMessage(ANIM_START);
    }
}).start();
    }
    private void startAnim() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(mStart.x, 0);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setDuration(2000);
valueAnimator.setRepeatCount(Animation.INFINITE);
//动画效果重复
//valueAnimator.setRepeatMode(Animation.RESTART);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
mLeft1.x = (float) animation.getAnimatedValue();
mLeft2 = new PointF(mLeft1.x + mWidth / 2f, mHeight - mWaveHeight);
mFirst = new PointF(mLeft2.x + mWidth / 2f, mHeight - mWaveHeight);
mSecond = new PointF(mFirst.x + mWidth / 2f, mHeight - mWaveHeight);
mRight = new PointF(mSecond.x + mWidth / 2f, mHeight - mWaveHeight);
mControlLeft1 = new PointF(mLeft1.x + mWidth / 4f, mLeft1.y + mWavePeak);
mControlLeft2 = new PointF(mLeft2.x + mWidth / 4f, mLeft2.y - mWaveTrough);
mControlFirst = new PointF(mFirst.x + mWidth / 4f, mFirst.y + mWavePeak);
mControlSecond = new PointF(mSecond.x + mWidth / 4f, mSecond.y - mWaveTrough);
invalidate();
    }
});
valueAnimator.start();
    }
}

这是一个自定义的控件需要在xml文件中用这个控件最后在主页面实例化然后在调用它的setRunning方法就可以实现海浪的效果,参考图:

QQ图片20171023153332.jpg

2、Android利用贝塞尔曲线实现一个QQ消息消失的效果代码如下:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
 * Created by huqing on 2016/6/27.
 */
public class BezierThree extends View {

    private Paint mPaint;
    private int centerX, centerY;
    private PointF start, end, fingerPointOne, fingerPointTwo;
    private boolean mode = true;
    public BezierThree(Context context) {
this(context, null);
    }
    public BezierThree(Context context, AttributeSet attrs) {
super(context, attrs);
//画笔
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(8);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setTextSize(60);
//中间点
start = new PointF(0, 0);
end = new PointF(0, 0);
fingerPointOne = new PointF(0, 0);
fingerPointTwo = new PointF(0, 0);
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX = w / 2;
centerY = h / 2;
// 初始化数据点和控制点的位置
start.x = 10;
start.y = centerY;
end.x = w - 10;
end.y = centerY;
fingerPointOne.x = centerX;
fingerPointOne.y = centerY - 100;
fingerPointTwo.x = centerX;
fingerPointTwo.y = centerY - 100;
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
// 根据触摸位置更新控制点,并提示重绘
if (mode) {
    fingerPointOne.x = event.getX();
    fingerPointOne.y = event.getY();
} else {
    fingerPointTwo.x = event.getX();
    fingerPointTwo.y = event.getY();
}
invalidate();
return true;
    }
    @Override
    protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//以下是辅助理解的线条和点
// 绘制数据点和控制点
mPaint.setColor(Color.GRAY);
mPaint.setStrokeWidth(20);
canvas.drawPoint(start.x, start.y, mPaint);
canvas.drawPoint(end.x, end.y, mPaint);
canvas.drawPoint(fingerPointOne.x, fingerPointOne.y, mPaint);
canvas.drawPoint(fingerPointTwo.x, fingerPointTwo.y, mPaint);
// 绘制辅助线
mPaint.setStrokeWidth(4);
canvas.drawLine(start.x, start.y, fingerPointOne.x, fingerPointOne.y, mPaint);
canvas.drawLine(fingerPointOne.x, fingerPointOne.y, fingerPointTwo.x, fingerPointTwo.y, mPaint);
canvas.drawLine(fingerPointTwo.x, fingerPointTwo.y, end.x, end.y, mPaint);
// 绘制贝塞尔曲线
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(8);
Path path = new Path();
//起点
path.moveTo(start.x, start.y);
//贝塞尔曲线中间途径的点
path.cubicTo(fingerPointOne.x, fingerPointOne.y, fingerPointTwo.x, fingerPointTwo.y, end.x, end.y);
canvas.drawPath(path, mPaint);
    }
}

 还需要在主函数 里面去调用这个方法:

 adhesionLayout = (AdhesionLayout) findViewById(R.id.wava);
adhesionLayout.setMaxAdherentLength(500 );//设置拉动的距离
adhesionLayout.setOnAdherentListener(new AdhesionLayout.OnAdherentListener() {
    @Override
    public void onDismiss() {
    }
});

QQ消失效果.jpg

3、Android利用贝塞尔曲线实现小球来回走动效果的实现,具体代码如下:

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import java.util.ArrayList;
import java.util.List;
/**
 * 粘合体进度条(水平)
 *
 * @author fakepoets
 * @date 2016/5/23
 */
public class AdhesionHorizontalLoader extends View {
    /**
     * 宽度
     */
    private int mWidth;
    /**
     * 高度
     */
    private int mHeight;
    /**
     * 当前的静态圆半径
     */
    private float mCurrentStaticCircleRadius = 10f;
    /**
     * 静态圆变化半径的最大比率
     */
    private float mMaxStaticCircleRadiusScaleRate = 0.4f;
    /**
     * 静态圆个数
     */
    private int mStaticCircleCount = 5;
    /**
     * 圆与圆之间的间隔距离
     */
    private float mDivideWidth = 3 * mCurrentStaticCircleRadius;
    /**
     * 最大粘连长度
     */
    private float mMaxAdherentLength = 3.5f * mCurrentStaticCircleRadius;
    /**
     * 静态圆
     */
    private Circle mStaticCircle;
    /**
     * 动态圆
     */
    private Circle mDynamicCircle = new Circle();
    /**
     * 维护静态圆容器
     */
    private List<Circle> mStaticCircles = new ArrayList<Circle>();
    /**
     * 画笔
     */
    private Paint mPaint = new Paint();
    /**
     * 路径
     */
    private Path mPath = new Path();
    /**
     * 默认颜色
     */
    private int mColor = 0xFF4DB9FF;
    /**
     * 构造函数
     *
     * @param context
     */
    public AdhesionHorizontalLoader(Context context) {
super(context);
init();
    }
    /**
     * 构造函数
     *
     * @param context
     * @param attrs
     */
    public AdhesionHorizontalLoader(Context context, AttributeSet attrs) {
super(context, attrs);
init();
    }
    /**
     * 构造函数
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public AdhesionHorizontalLoader(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
    }
    /**
     * 初始化
     */
    private void init() {

/* 画笔 */
mPaint.setColor(mColor);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
/* 宽度、高度 */
mWidth = (int) ((mStaticCircleCount + 1) * (mCurrentStaticCircleRadius * 2 + mDivideWidth));
mHeight = (int) (2 * mCurrentStaticCircleRadius * (1 + mMaxStaticCircleRadiusScaleRate));
/* 动态圆 */
mDynamicCircle.radius = mCurrentStaticCircleRadius * 3 / 4;
;
mDynamicCircle.x = mDynamicCircle.radius;
mDynamicCircle.y = mHeight / 2;

/* 静态圆 */
for (int i = 0; i < mStaticCircleCount; i++) {
    mStaticCircle = new Circle();
    mStaticCircle.radius = mCurrentStaticCircleRadius;
    mStaticCircle.x = (mStaticCircle.radius * 2 + mDivideWidth) * (i + 1);
    mStaticCircle.y = mHeight / 2;
    mStaticCircles.add(mStaticCircle);
}

/* 开始动画 */
startAnim();
    }
    /**
     * 测量尺寸
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec 知识补充:
     *  方法resolveSizeAndState()
     *  用来创建最终的宽和高. 这个方法通过比较视图的期望大小返回一个合适的View.MeasureSpec值传入onMeasure()
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(resolveSizeAndState(mWidth, widthMeasureSpec, MeasureSpec.UNSPECIFIED), resolveSizeAndState(mHeight, heightMeasureSpec, MeasureSpec.UNSPECIFIED));
    }
    /**
     * 绘制视图
     *
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
/* 动态圆 */
canvas.drawCircle(mDynamicCircle.x, mDynamicCircle.y, mDynamicCircle.radius, mPaint);

/* 静态圆 */
for (int i = 0; i < mStaticCircleCount; i++) {
    mStaticCircle = mStaticCircles.get(i);
    
    /* 判断哪个圆可以作贝塞尔曲线 */
    if (doAdhere(i)) {
canvas.drawCircle(mStaticCircle.x, mStaticCircle.y, mCurrentStaticCircleRadius, mPaint);
// drawAdherentBody(canvas, i);
Path path = Adhesion.drawAdhesionBody(mStaticCircle.x, mStaticCircle.y, 
mCurrentStaticCircleRadius, 45, mDynamicCircle.x, mDynamicCircle.y, mDynamicCircle.radius, 45);
canvas.drawPath(path, mPaint);
    } else {
canvas.drawCircle(mStaticCircle.x, mStaticCircle.y, mStaticCircle.radius, mPaint);
    }
}
    }
    
    /**
     * 判断粘连范围,动态改变静态圆大小
     *
     * @param position
     * @return
     */
    private boolean doAdhere(int position) {
mStaticCircle = mStaticCircles.get(position);

/* 半径变化 */
float distance = (float) Math.sqrt(Math.pow(mDynamicCircle.x - mStaticCircle.x, 2) + Math.pow(mDynamicCircle.y - mStaticCircle.y, 2));
float scale = mMaxStaticCircleRadiusScaleRate - mMaxStaticCircleRadiusScaleRate * (distance / mMaxAdherentLength);
mCurrentStaticCircleRadius = mStaticCircle.radius * (1 + scale);
       
/* 判断是否可以作贝塞尔曲线 */
if (distance < mMaxAdherentLength)
    return true;
else
    return false;
    }
    /**
     * 开始动画
     */
    private void startAnim() {
 /* x方向 */
ValueAnimator xValueAnimator = ValueAnimator.ofFloat(mDynamicCircle.x, mWidth - mDynamicCircle.radius);
xValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
xValueAnimator.setDuration(2500);
xValueAnimator.setRepeatCount(Animation.INFINITE);
//动画效果重复
xValueAnimator.setRepeatMode(Animation.REVERSE);
xValueAnimator.start();
xValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
mDynamicCircle.x = (float) animation.getAnimatedValue();
invalidate();
    }
});
    }
    public void setColor(int color) {
mColor = color;
mPaint.setColor(mColor);
    }
    /**
     * 圆类
     */
    private class Circle {
public float x;
public float y;
public float radius;
    }

}
import android.graphics.Path;
/**
 * 粘合体(二阶贝塞尔曲线)
 * @author fakepoets
 * @date 2016/5/23
 */
public class Adhesion {
    /**
     * 知识点:
     *   1弧度=180/π度
     *   Math.toDegrees(double) 转换以弧度为单位测得的角度大致相等的角度,以度衡量。   弧度->角度
     *   Math.toRadians(double) 转换为度大致相等的角度,以弧度为单位的角度。  角度->弧度
     *
     *   注意:Math.sin(double)、Math.cos(double)、Math.tan(double)、Math.atan(double)等方法的参数
     *均为弧度,所以如果传值来的数为角度,应该先转化为弧度
     *
     *
     *
     *   由于偏移角度为45,因为无论两个圆之间的角度(degree)如何变化
     *   (x1,y1)与(x3,y3)两个点之间的角度差永远为45*2(90)
     *
     */
    
    /**
     * 画粘连体
     * @param cx1     圆心x1
     * @param cy1     圆心y1
     * @param r1      圆半径r1
     * @param offset1 贝塞尔曲线偏移角度offset1
     * @param cx2     圆心x2
     * @param cy2     圆心y2
     * @param r2      圆半径r2
     * @param offset2 贝塞尔曲线偏移角度offset2
     * @return
     */
    public static Path drawAdhesionBody(float cx1, float cy1, float r1, float offset1, float 
    cx2, float cy2, float r2, float offset2) {

/* 求三角函数 */
float degrees =(float) Math.toDegrees(Math.atan(Math.abs(cy2 - cy1) / Math.abs(cx2 - cx1)));

/* 根据圆1与圆2的相对位置求四个点 */
float differenceX = cx1 - cx2;
float differenceY = cy1 - cy2;
/* 两条贝塞尔曲线的四个端点 */
float x1,y1,x2,y2,x3,y3,x4,y4;

/* 圆1在圆2的下边 */
if (differenceX == 0 && differenceY > 0) {
    x2 = cx2 - r2 * (float) Math.sin(Math.toRadians(offset2));
    y2 = cy2 + r2 * (float) Math.cos(Math.toRadians(offset2));
    x4 = cx2 + r2 * (float) Math.sin(Math.toRadians(offset2));
    y4 = cy2 + r2 * (float) Math.cos(Math.toRadians(offset2));
    x1 = cx1 - r1 * (float) Math.sin(Math.toRadians(offset1));
    y1 = cy1 - r1 * (float) Math.cos(Math.toRadians(offset1));
    x3 = cx1 + r1 * (float) Math.sin(Math.toRadians(offset1));
    y3 = cy1 - r1 * (float) Math.cos(Math.toRadians(offset1));
}
/* 圆1在圆2的上边 */
else if (differenceX == 0 && differenceY < 0) {
    x2 = cx2 - r2 * (float) Math.sin(Math.toRadians(offset2));
    y2 = cy2 - r2 * (float) Math.cos(Math.toRadians(offset2));
    x4 = cx2 + r2 * (float) Math.sin(Math.toRadians(offset2));
    y4 = cy2 - r2 * (float) Math.cos(Math.toRadians(offset2));
    x1 = cx1 - r1 * (float) Math.sin(Math.toRadians(offset1));
    y1 = cy1 + r1 * (float) Math.cos(Math.toRadians(offset1));
    x3 = cx1 + r1 * (float) Math.sin(Math.toRadians(offset1));
    y3 = cy1 + r1 * (float) Math.cos(Math.toRadians(offset1));
}
/* 圆1在圆2的右边 */
else if (differenceX > 0 && differenceY == 0) {
    x2 = cx2 + r2 * (float) Math.cos(Math.toRadians(offset2));
    y2 = cy2 + r2 * (float) Math.sin(Math.toRadians(offset2));
    x4 = cx2 + r2 * (float) Math.cos(Math.toRadians(offset2));
    y4 = cy2 - r2 * (float) Math.sin(Math.toRadians(offset2));
    x1 = cx1 - r1 * (float) Math.cos(Math.toRadians(offset1));
    y1 = cy1 + r1 * (float) Math.sin(Math.toRadians(offset1));
    x3 = cx1 - r1 * (float) Math.cos(Math.toRadians(offset1));
    y3 = cy1 - r1 * (float) Math.sin(Math.toRadians(offset1));
} 
/* 圆1在圆2的左边 */
else if (differenceX < 0 && differenceY == 0 ) {
    x2 = cx2 - r2 * (float) Math.cos(Math.toRadians(offset2));
    y2 = cy2 + r2 * (float) Math.sin(Math.toRadians(offset2));
    x4 = cx2 - r2 * (float) Math.cos(Math.toRadians(offset2));
    y4 = cy2 - r2 * (float) Math.sin(Math.toRadians(offset2));
    x1 = cx1 + r1 * (float) Math.cos(Math.toRadians(offset1));
    y1 = cy1 + r1 * (float) Math.sin(Math.toRadians(offset1));
    x3 = cx1 + r1 * (float) Math.cos(Math.toRadians(offset1));
    y3 = cy1 - r1 * (float) Math.sin(Math.toRadians(offset1));
}
/* 圆1在圆2的右下角 */
else if (differenceX > 0 && differenceY > 0) {
    x2 = cx2 - r2 * (float) Math.cos(Math.toRadians(180 - offset2 - degrees));
    y2 = cy2 + r2 * (float) Math.sin(Math.toRadians(180 - offset2 - degrees));
    x4 = cx2 + r2 * (float) Math.cos(Math.toRadians(degrees - offset2));
    y4 = cy2 + r2 * (float) Math.sin(Math.toRadians(degrees - offset2));
    x1 = cx1 - r1 * (float) Math.cos(Math.toRadians(degrees - offset1));
    y1 = cy1 - r1 * (float) Math.sin(Math.toRadians(degrees - offset1));
    x3 = cx1 + r1 * (float) Math.cos(Math.toRadians(180 - offset1 - degrees));
    y3 = cy1 - r1 * (float) Math.sin(Math.toRadians(180 - offset1 - degrees));
}
/* 圆1在圆2的左上角 */
else if (differenceX < 0 && differenceY < 0) {
    x2 = cx2 - r2 * (float) Math.cos(Math.toRadians(degrees - offset2));
    y2 = cy2 - r2 * (float) Math.sin(Math.toRadians(degrees - offset2));
    x4 = cx2 + r2 * (float) Math.cos(Math.toRadians(180 - offset2 - degrees));
    y4 = cy2 - r2 * (float) Math.sin(Math.toRadians(180 - offset2 - degrees));
    x1 = cx1 - r1 * (float) Math.cos(Math.toRadians(180 - offset1 - degrees));
    y1 = cy1 + r1 * (float) Math.sin(Math.toRadians(180 - offset1 - degrees));
    x3 = cx1 + r1 * (float) Math.cos(Math.toRadians(degrees - offset1));
    y3 = cy1 + r1 * (float) Math.sin(Math.toRadians(degrees - offset1));
}
/* 圆1在圆2的左下角 */
else if (differenceX < 0 && differenceY > 0) {
    x2 = cx2 - r2 * (float) Math.cos(Math.toRadians(degrees - offset2));
    y2 = cy2 + r2 * (float) Math.sin(Math.toRadians(degrees - offset2));
    x4 = cx2 + r2 * (float) Math.cos(Math.toRadians(180 - offset2 - degrees));
    y4 = cy2 + r2 * (float) Math.sin(Math.toRadians(180 - offset2 - degrees));
    x1 = cx1 - r1 * (float) Math.cos(Math.toRadians(180 - offset1 - degrees));
    y1 = cy1 - r1 * (float) Math.sin(Math.toRadians(180 - offset1 - degrees));
    x3 = cx1 + r1 * (float) Math.cos(Math.toRadians(degrees - offset1));
    y3 = cy1 - r1 * (float) Math.sin(Math.toRadians(degrees - offset1));
}
/* 圆1在圆2的右上角 */
else {
    x2 = cx2 - r2 * (float) Math.cos(Math.toRadians(180 - offset2 - degrees));
    y2 = cy2 - r2 * (float) Math.sin(Math.toRadians(180 - offset2 - degrees));
    x4 = cx2 + r2 * (float) Math.cos(Math.toRadians(degrees - offset2));
    y4 = cy2 - r2 * (float) Math.sin(Math.toRadians(degrees - offset2));
    x1 = cx1 - r1 * (float) Math.cos(Math.toRadians(degrees - offset1));
    y1 = cy1 + r1* (float) Math.sin(Math.toRadians(degrees - offset1));
    x3 = cx1 + r1 * (float) Math.cos(Math.toRadians(180 - offset1 - degrees));
    y3 = cy1 + r1 * (float) Math.sin(Math.toRadians(180 - offset1 - degrees));
}

/* 贝塞尔曲线的控制点 */
float anchorX1,anchorY1,anchorX2,anchorY2;

/* 圆1大于圆2 */
if (r1 > r2) {
    anchorX1 = (x2 + x3) / 2;
    anchorY1 = (y2 + y3) / 2;
    anchorX2 = (x1 + x4) / 2;
    anchorY2 = (y1 + y4) / 2;
}
/* 圆1小于或等于圆2 */
else {
    anchorX1 = (x1 + x4) / 2;
    anchorY1 = (y1 + y4) / 2;
    anchorX2 = (x2 + x3) / 2;
    anchorY2 = (y2 + y3) / 2;
}

/* 画粘连体 */
Path path = new Path();
path.reset();
path.moveTo(x1, y1);
path.quadTo(anchorX1, anchorY1, x2, y2);
path.lineTo(x4, y4);
path.quadTo(anchorX2, anchorY2, x3, y3);
path.lineTo(x1, y1);
return path;
    }
}

这个直接在主类里面实例化就可以实现效果了,效果图如下:

小球走动.jpg

4、Android利用贝塞尔曲线实现一个旋转的小球走动,具体代码如下:

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import java.util.ArrayList;
import java.util.List;
/**
 * @author fakepoets
 * @date 2017/10/23
 */
public class AdhesionCircleLoader extends View{
    
    /**
     * 宽度
     */
    private int mWidth;
    /**
     * 高度
     */
    private int mHeight;
    /**
     * 大圆
     */
    private Circle mBigCircle = new Circle();
    /**
     * 大圆半径
     */
    private int mBigCircleRadius = 50;
    /**
     * 当前的静态圆半径
     */
    private float mCurrentStaticCircleRadius = mBigCircleRadius / 5;
    /**
     * 静态圆变化半径的最大比率
     */
    private float mMaxStaticCircleRadiusScaleRate = 0.4f;
    /**
     * 静态圆个数
     */
    private static int mStaticCircleCount = 8;
    /**
     * 最大粘连长度
     */
    private float mMaxAdherentLength = 2.5f  * mCurrentStaticCircleRadius;
    /**
     * 静态圆
     */
    private Circle mStaticCircle;
    /**
     * 动态圆
     */
    private Circle mDynamicCircle = new Circle();
    /**
     * 维护静态圆容器
     */
    private List<Circle> mStaticCircles = new ArrayList<Circle>();
    /**
     * 画笔
     */
    private Paint mPaint = new Paint();
    /**
     * 路径
     */
    private Path mPath = new Path();
    /**
     * 默认颜色
     */
    private int mColor = 0xFF4DB9FF;
    /**
     * 构造函数
     *
     * @param context
     */
    public AdhesionCircleLoader(Context context) {
super(context);
init();
    }
    /**
     * 构造函数
     *
     * @param context
     * @param attrs
     */
    public AdhesionCircleLoader(Context context, AttributeSet attrs) {
super(context, attrs);
init();
    }
    /**
     * 构造函数
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public AdhesionCircleLoader(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
    }
    /**
     * 初始化
     */
    private void init() {

/* 画笔 */
mPaint.setColor(mColor);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
/* 宽度、高度 */
mWidth = mHeight = (int)(2 * (mBigCircleRadius + mCurrentStaticCircleRadius * (1 + mMaxStaticCircleRadiusScaleRate)));

/* 大圆 */
mBigCircle.x = mWidth / 2;
mBigCircle.y = mHeight / 2;
mBigCircle.radius = mBigCircleRadius;

/* 动态圆 */
mDynamicCircle.radius = mCurrentStaticCircleRadius * 3 / 4;;
mDynamicCircle.x = mBigCircle.x;
mDynamicCircle.y = mCurrentStaticCircleRadius * (1 + mMaxStaticCircleRadiusScaleRate);

/* 静态圆 */
for (int i = 0; i < mStaticCircleCount; i++) {
    mStaticCircle = new Circle();
    mStaticCircle.radius = mCurrentStaticCircleRadius;
    mStaticCircle.x = (float)(mBigCircle.x + mBigCircleRadius * Math.cos(Math.toRadians(45 * i)));
    mStaticCircle.y = (float)(mBigCircle.y + mBigCircleRadius * Math.sin(Math.toRadians(45 * i)));
    mStaticCircles.add(mStaticCircle);
}

/* 开始动画 */
startAnim();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(resolveSizeAndState(mWidth, widthMeasureSpec, MeasureSpec.UNSPECIFIED), resolveSizeAndState(mHeight, heightMeasureSpec, MeasureSpec.UNSPECIFIED));
    }
    @Override
    protected void onDraw(Canvas canvas) {
/* 动态圆 */
canvas.drawCircle(mDynamicCircle.x, mDynamicCircle.y, mDynamicCircle.radius, mPaint);

/* 静态圆 */
for (int i = 0; i < mStaticCircleCount; i++) {
    mStaticCircle = mStaticCircles.get(i);
    
    /* 判断哪个圆可以作贝塞尔曲线 */
    if (doAdhere(i)) {
canvas.drawCircle(mStaticCircle.x, mStaticCircle.y, mCurrentStaticCircleRadius, mPaint);
//drawAdherentBody(canvas, i);
Path path = Adhesion.drawAdhesionBody(mStaticCircle.x,mStaticCircle.y,
mCurrentStaticCircleRadius,45,
mDynamicCircle.x, mDynamicCircle.y, mDynamicCircle.radius,45);
canvas.drawPath(path, mPaint);
    } else {
canvas.drawCircle(mStaticCircle.x, mStaticCircle.y, mStaticCircle.radius, mPaint);
    }
}
    }
    
    /**
     * 判断粘连范围,动态改变静态圆大小
     *
     * @param position
     * @return
     */
    private boolean doAdhere(int position) {
mStaticCircle = mStaticCircles.get(position);

/* 半径变化 */
float distance = (float) Math.sqrt(Math.pow(mDynamicCircle.x - mStaticCircle.x, 2) + Math.pow(mDynamicCircle.y - mStaticCircle.y, 2));
float scale = mMaxStaticCircleRadiusScaleRate -  mMaxStaticCircleRadiusScaleRate * (distance / mMaxAdherentLength);
mCurrentStaticCircleRadius = mStaticCircle.radius * (1 + scale);

/* 判断是否可以作贝塞尔曲线 */
if (distance < mMaxAdherentLength)
    return true;
else
    return false;
    }
    /**
     * 开始动画
     */
    private void startAnim() {
/* 角度 */
ValueAnimator valueAnimator = ValueAnimator.ofFloat(-90, 270);
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.setDuration(2500);
valueAnimator.setRepeatCount(Animation.INFINITE);
valueAnimator.start();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
float angle = (float) animation.getAnimatedValue();
mDynamicCircle.x = (float)(mBigCircle.x + mBigCircleRadius * Math.cos(Math.toRadians(angle)));
mDynamicCircle.y = (float)(mBigCircle.y + mBigCircleRadius * Math.sin(Math.toRadians(angle)));
invalidate();
    }
});
    }
    public void setColor(int color) {
mColor = color;
mPaint.setColor(mColor);
    }
    /**
     * 圆类
     */
    private class Circle {
public float x;
public float y;
public float radius;
    }
}

这个类配合上面的Adhesion这个类实现一个旋转的小球走动效果,具体效果图:

旋转.jpg

~阅读全文-人机检测~

关注下方微信公众号“Java精选”(w_z90110),回复关键词领取资料:如Mysql、Hadoop、Dubbo、Spring Boot等,免费领取视频教程、资料文档和项目源码。微信搜索小程序“Java精选面试题”,内涵3000+道Java面试题!

Java精选专注程序员推送一些Java开发知识,包括基础知识、各大流行框架(Mybatis、Spring、Spring Boot等)、大数据技术(Storm、Hadoop、MapReduce、Spark等)、数据库(Mysql、Oracle、NoSQL等)、算法与数据结构、面试专题、面试技巧经验、职业规划以及优质开源项目等。其中一部分由小编总结整理,另一部分来源于网络上优质资源,希望对大家的学习和工作有所帮助。

您可能感兴趣的文章

评论

  1. #1

    (2018/01/16 16:59:26)回复
    一辈子只做好一件事!我是有多么的大方吖!哈哈

分享:

支付宝

微信