//先上效果图
//这个是圆的自定义view类 AdhesionCircleLoader
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 linzewu
* @date 2016/5/29
*/
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;
}
}
//这个是横线的类
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;
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
import android.graphics.Path;
/**
* 粘合体(二阶贝塞尔曲线)
* @author linzewu
* @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;
}
}
//最后Main调用
//只需要在布局中调用一下,就是一个自定义view,不用写其他代码,
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.chenqq.Main2Activity">
<com.example.chenxuqq.progress.AdhesionCircleLoader
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="30dp"/>
<com.example.chenxuqq.progress.AdhesionHorizontalLoader
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="30dp"/>
</LinearLayout>