一、前言
需求是一个控件在指定区域内做漂浮碰壁动画,当触碰到边缘时候会反弹。这里简单做了下这个动画,不过里面移动速率是固定的,没有做弹性动画。移动角度是固定的,没有做随机运算。只是做了下简单的反弹动画。不过这个代码有bug,动画持续一个小时以上会出现一直在一个位置来回水平运动的问题
二、相关代码
使用方式:
BouncingUtil bouncingUtil = new BouncingUtil(childView, parentView);
bouncingUtil.startAnim();
//停止动画
if (bouncingUtil != null) bouncingUtil.stopAnim();
/**
* 一个用来控制控件在指定范围内弹跳的类
*
* @author YM
*/
public class BouncingUtil {
private final int motionEvent = 0x001;
/**
* 移动步长
*/
private final int STEP = 8;
private final int DURATION = 15; //每次运动的时间间隔
/**
* 运动角度
*/
private double angle = Math.PI;
private double angleOffset = 1; //处于某些特殊情况下对角度进行修正
private float startMulti = 0.5f; // 起始倍数
private final View bouncingView; //用来弹跳的控件
private final View scopeView; //用来运动的区域
private int scopeViewLeft; //区域左边距
private int scopeViewRight; //区域右边距
private int scopeViewTop; //区域上边距
private int scopeViewBottom; //区域底边距
private boolean isRunning = false;//动画是否运行中
private final Handler uiHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what == motionEvent) {
updateLocation();
}
}
};
public BouncingUtil(View bouncingView, View scopeView) {
this.bouncingView = bouncingView;
this.scopeView = scopeView;
if (bouncingView.getWidth() == 0) {
throw new IllegalStateException("运动控件大小范围不能为0");
}
if (scopeView.getWidth() == 0) {
throw new IllegalStateException("运动的区域范围不能为0");
}
initData();
}
private void initData() {
scopeViewLeft = scopeView.getLeft();
scopeViewRight = scopeView.getRight();
scopeViewTop = scopeView.getTop();
scopeViewBottom = scopeView.getBottom();
// Log.e("YM---->","BouncingUtil--->scopeViewLeft:"+scopeViewLeft+ " -->scopeViewRight:"+scopeViewRight+" ----->scopeViewTop:"+scopeViewTop+"--->scopeViewBottom:"+scopeViewBottom);
computerAngle();
}
//如果角度刚好是水平或者垂直的就会出现水平或垂直运动,这里进行下调整
// 在 0.5 * PI - 1 * PI - 1.5 * PI - 2 * PI - .... - ... - 9.5 * PI - ... 之间进行 向上垂直 - 向左水平 - 向下垂直 - 向右水平 - ... - 向下垂直 - ....
// 综上所述,0.75 向右上
private void computerAngle() {
// angle = 2 + Math.PI * Math.random();
// angle = Math.PI; //水平
// angle = 0.5 * Math.PI; //垂直
// if (angle % Math.PI == 0) {
// angle += angleOffset;
// }
// if (angle % (Math.PI * 0.5) == 0) {
// angle += angleOffset;
// }
// angle = 5.165469871174528;
angle = startMulti * Math.PI;
angle = fixNum(angle);
// angle = 5.09;
Log.e("YM---->", "--->漂浮动画运动的角度:" + angle);
}
//修正下数字,只保留两位小数
//这里需要金属保留两位小数量,否则小数位数太多会导致问题,具体可以尝试angle = 5.165469871174528
private double fixNum(double num) {
BigDecimal two = new BigDecimal(num);
return two.setScale(2, RoundingMode.HALF_UP).doubleValue();//保留两位小数
}
public void startAnim() {
if (isRunning) return;
isRunning = true;
new Thread(new Runnable() {
@Override
public void run() {
while (isRunning) {
if (bouncingView.getWidth() > 0){
uiHandler.sendEmptyMessage(motionEvent);
}
try {
Thread.sleep(DURATION);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
//停止动画
public void stopAnim() {
isRunning = false;
uiHandler.removeCallbacksAndMessages(null);
}
private void updateLocation() {
float bouncingViewX = bouncingView.getX();
float bouncingViewY = bouncingView.getY();
float bouncingViewW = bouncingView.getWidth();
float bouncingViewH = bouncingView.getHeight();
double left = fixNum(bouncingViewX);
double right = fixNum(bouncingViewX + bouncingViewW);
double top = fixNum(bouncingViewY);
double bottom = fixNum(bouncingViewY + bouncingViewH);
/* 触碰到边界,发生镜像反射 */
// Log.e("YM---->", "BouncingUtil--bouncingView->left:" + left + " -->right:" + right + " ----->top:" + top + "--->bottom:" + bottom);
int direction = -1; // -1 无边界情况 0 right,1 left 2 top 3 bottom
if (right >= scopeViewRight) { // 碰右边界
angle = Math.PI - angle;
direction = 0;
// Log.e("YM--->", "--->右边界:" + angle);
} else if (left <= scopeViewLeft) { // 碰左边界
angle = Math.PI - angle;
direction = 1;
// Log.e("YM--->", "--->左边界:" + angle);
} else if (top <= scopeViewTop) { // 碰上边界
angle = -angle;
direction = 2;
// Log.e("YM--->", "--->上边界:" + angle);
} else if (bottom >= scopeViewBottom) { // 碰下边界
angle = -angle;
direction = 3;
// Log.e("YM--->", "--->下边界:" + angle);
}
/* 计算新的新坐标 */
// double cosStep = STEP * Math.cos(angle); //余弦值在-1.0 和 1.0 之间进行获取,这个值与angle的正负值无关
// double sinStep = STEP * Math.sin(angle); //正弦值在-1.0 和 1.0 之间进行获取,这个值与angle的正负值无关
//由于正弦和余弦的正负值无法确定,所以下面将正负值进行统一处理
double cosAngle = Math.cos(angle);
double sinAngle = Math.sin(angle);
if (direction == 0){
cosAngle = -Math.abs(cosAngle);
}
if (direction == 1){
cosAngle = Math.abs(cosAngle);
}
if (direction == 2){
cosAngle = Math.abs(sinAngle);
}
if (direction == 3){
cosAngle = -Math.abs(sinAngle);
}
double cosStep = STEP * cosAngle;
double sinStep = STEP * sinAngle;
float newX = (float) fixNum(left + cosStep);
float newY = (float) fixNum(top - sinStep);
//设置新的移动位置
bouncingView.setX(newX);
bouncingView.setY(newY);
}
public boolean isRunning() {
return isRunning;
}
}