今天又个下拉刷新的需求,UI需要我们自定义一个比较刷新动画。两个重叠小球先按太极分割线的方式到圆的两端,然后进行旋转,最后又已太极的形式收回来。
说着很简单,但是做起来还是有一点难度的,需要计算蛮多东西的,对于我这种数学不好得人很要命啊。
先说思路吧
- 先在控件上绘制重叠的两个圆
- 开启滑动之后,两个小圆按八卦的形式滑动出去,此时他们的轨迹是一个半圆形,直径是外层轨迹(小圆将在他的圆线上滑动)的半径,可以先确定两个半圆的圆心,根据公式计算出八卦分割线上每个点的的坐标
- 当两个小圆滑动到大圆上时,进行旋转
- 回收,思路与2一样
//根据圆心、半径求圆上每一个点的坐标
X坐标=a + Math.sin(2*Math.PI / 360*角度) * r ;
Y坐标=b + Math.cos(2*Math.PI / 360*角度) * r ;
上核心代码
/**
* 定义小球的状态
* 0、初始化化阶段
* 1、两个小球向沿八卦从圆心滑动到圆上
* 2、两个小球进行旋转
* 3、小球沿八卦从圆上滑动到圆心
* 4、小球交换位置后沿八卦从圆心滑动到圆上
* 5、两个小球进行旋转
* 6、小球交换位置小球沿八卦从圆上滑动到圆心
*/
private int mState = 0;
//小圆的半径
private int mRadius;
//大圆的半径(轨迹)
private int mMaxRadius = 150;
//小圆在轨迹上快速滑动的速度
private int mFast = 8;
//小圆在轨迹上快速滑动的速度(角度)
private int mSlow = 6;
//小圆缩放时的速度(角度)
private int mZoom = 10;
//上半部分的圆上的点以及角度(角度)
private int mTopPointX;
//小圆滑动的距离(角度)
private int mDistance = 360;
private int mTopPointY;
private int mTopAngle = 0; //默认为0
//下半部分的圆上的点以及角度
private int mBottomPointX;
private int mBottomPointY;
private int mBottomAngle = 180; //默认为180
//小圆绕大圆滑动距离
private int mRotateAngle = 0;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mState == 0) /*初始状态*/ {
stateFor0(canvas);
}
if (mState == 1)/*小球沿八卦从圆心滑动到圆上*/ {
stateFor1(canvas);
}
if (mState == 2)/*小球在圆上滑动*/ {
stateFor2(canvas);
}
if (mState == 3)/*小球沿八卦从圆上滑动到圆心*/ {
stateFor3(canvas);
}
if (mState == 4)/*两个小球交换位置后小球沿八卦从圆心滑动到圆上*/ {
stateFor4(canvas);
}
if (mState == 5)/*小球沿八卦在圆上滑动*/ {
stateFor5(canvas);
}
if (mState == 6)/*两个小球交换位置后小球沿八卦从圆上滑动到圆心*/ {
stateFor6(canvas);
}
}
private void stateFor0(Canvas canvas) {
for (Paint paint : mPaints) {
canvas.drawCircle(mMeasuredWidth / 2, mMeasuredHeight / 2, mRadius, paint);
canvas.save();
}
}
private void stateFor1(Canvas canvas) {
for (int i = 0; i < mPaints.size(); i++) {
if (i == 0) {
mTopPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
mTopPointY = (int) (mMeasuredHeight / 2 - mMaxRadius + Math.cos(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
canvas.drawCircle(mTopPointX, mTopPointY, mRadius, mPaints.get(i));
} else {
mBottomPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
mBottomPointY = (int) (mMeasuredHeight / 2 + mMaxRadius + Math.cos(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
canvas.drawCircle(mBottomPointX, mBottomPointY, mRadius, mPaints.get(i));
}
}
if (mBottomAngle == 0) {
mState = 2;
//重置数据,为进入mState=3状态做准备
mBottomAngle = 0;
mTopAngle = 180;
invalidate();
return;
}
mTopAngle -= mZoom;
mBottomAngle -= mZoom;
invalidate();
}
private void stateFor2(Canvas canvas) {
for (int i = 0; i < mPaints.size(); i++) {
if (i == 0) {
canvas.drawCircle(mMeasuredWidth / 2, mMeasuredHeight / 2 - mMaxRadius * 2, mRadius, mPaints.get(i));
} else {
canvas.drawCircle(mMeasuredWidth / 2, mMeasuredHeight / 2 + mMaxRadius * 2, mRadius, mPaints.get(i));
}
}
if (mRotateAngle % 360 <= 60)/*慢速旋转*/ {
mRotateAngle += mSlow;
} else if (mRotateAngle % 360 <= 300)/*快速旋转*/ {
mRotateAngle += mFast;
} else if (mRotateAngle % 360 < 360)/*慢速旋转*/ {
mRotateAngle += mSlow;
if (mRotateAngle > mDistance) { //必须判断,防止角度出问题
mRotateAngle = mDistance;
}
}
setRotation(mRotateAngle);
if (mRotateAngle >= mDistance) {
mRotateAngle = 0;
mState = 3;
invalidate();
return;
}
invalidate();
}
private void stateFor3(Canvas canvas) {
for (int i = 0; i < mPaints.size(); i++) {
if (i == 0) {
mTopPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
mTopPointY = (int) (mMeasuredHeight / 2 - mMaxRadius + Math.cos(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
canvas.drawCircle(mTopPointX, mTopPointY, mRadius, mPaints.get(i));
} else {
mBottomPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
mBottomPointY = (int) (mMeasuredHeight / 2 + mMaxRadius + Math.cos(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
canvas.drawCircle(mBottomPointX, mBottomPointY, mRadius, mPaints.get(i));
}
}
if (mTopAngle == 0) {
postDelayed(new Runnable() {
@Override
public void run() {
mState = 4;
invalidate();
}
}, 80);
//重置数据,为mState = 1状态做准备
mBottomAngle = 180;
mTopAngle = 0;
invalidate();
return;
}
mTopAngle -= mZoom;
mBottomAngle -= mZoom;
invalidate();
}
private void stateFor4(Canvas canvas) {
for (int i = 0; i < mPaints.size(); i++) {
if (i == 1) {
mTopPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
mTopPointY = (int) (mMeasuredHeight / 2 - mMaxRadius + Math.cos(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
canvas.drawCircle(mTopPointX, mTopPointY, mRadius, mPaints.get(i));
} else {
mBottomPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
mBottomPointY = (int) (mMeasuredHeight / 2 + mMaxRadius + Math.cos(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
canvas.drawCircle(mBottomPointX, mBottomPointY, mRadius, mPaints.get(i));
}
}
if (mBottomAngle == 0) {
mState = 5;
//重置数据,为进入mState=3状态做准备
mBottomAngle = 0;
mTopAngle = 180;
invalidate();
return;
}
mTopAngle -= mZoom;
mBottomAngle -= mZoom;
invalidate();
}
private void stateFor5(Canvas canvas) {
for (int i = 0; i < mPaints.size(); i++) {
if (i == 1) {
canvas.drawCircle(mMeasuredWidth / 2, mMeasuredHeight / 2 - mMaxRadius * 2, mRadius, mPaints.get(i));
} else {
canvas.drawCircle(mMeasuredWidth / 2, mMeasuredHeight / 2 + mMaxRadius * 2, mRadius, mPaints.get(i));
}
}
if (mRotateAngle % 360 <= 60)/*慢速旋转*/ {
mRotateAngle += mSlow;
} else if (mRotateAngle % 360 <= 300)/*快速旋转*/ {
mRotateAngle += mFast;
} else if (mRotateAngle % 360 < 360)/*慢速旋转*/ {
mRotateAngle += mSlow;
if (mRotateAngle > mDistance) { //必须判断,防止角度出问题
mRotateAngle = mDistance;
}
}
setRotation(mRotateAngle);
if (mRotateAngle >= mDistance) {
mRotateAngle = 0;
mState = 6;
invalidate();
return;
}
invalidate();
}
private void stateFor6(Canvas canvas) {
for (int i = 0; i < mPaints.size(); i++) {
if (i == 1) {
mTopPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
mTopPointY = (int) (mMeasuredHeight / 2 - mMaxRadius + Math.cos(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
canvas.drawCircle(mTopPointX, mTopPointY, mRadius, mPaints.get(i));
} else {
mBottomPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
mBottomPointY = (int) (mMeasuredHeight / 2 + mMaxRadius + Math.cos(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
canvas.drawCircle(mBottomPointX, mBottomPointY, mRadius, mPaints.get(i));
}
}
if (mTopAngle == 0) {
postDelayed(new Runnable() {
@Override
public void run() {
mState = 1;
invalidate();
}
}, 80);
//重置数据,为mState = 1状态做准备
mBottomAngle = 180;
mTopAngle = 0;
invalidate();
return;
}
mTopAngle -= mZoom;
mBottomAngle -= mZoom;
invalidate();
}
/**
* 开始旋转(最后一个调用的方法,开始之后不能改变属性)
*/
public void startSpinning() {
mState = 1;
invalidate();
}
这样,调用startSpinning()就可以运行了。还可以提供一些设置方法。这个控件就完成了
写在结尾:我感觉这个控件写的有点复杂了,应该有更简单的方式,希望哪位看到的话可以帮我修改下,让我可以学到更好的解决方式
控件项目地址:https://git.oschina.net/qiangshen/commentview.git
这是我开始维护的一个控件地址,这里面的控件都是我自己学习时或工作时写的,可能控件还有各种bug,可能我的做法太复杂或代码规范有问题,我希望大家可以帮我提出并改正。