一、效果图
二、代码实现
public class ObjectFallingView extends View implements ValueAnimator.AnimatorUpdateListener {
private Paint mPaint = null;
private int mWidth;
private int mHeight;
private List<ObjectItem> mObjectItems = new ArrayList<>();
private long mDuration = 1200;
private int maxGroup = 3;
private ValueAnimator mUpdateFrameAniator;
private int mObjectCounter = 0;
private long fraquceDuration = 125;
private long lastIncrementTime = 0;
private boolean isPlaying = false;
public ObjectFallingView(Context context) {
this(context, null);
}
public ObjectFallingView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ObjectFallingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setClickable(true);
setFocusable(true);
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
heightSize = getPaddingTop() + getPaddingBottom() + 320;
}
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
widthSize = getPaddingTop() + getPaddingBottom() + 320;
}
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mObjectItems.size(); i++) {
ObjectItem objectItem = mObjectItems.get(i);
if (!objectItem.isRunning) {
continue;
}
objectItem.draw(canvas, mPaint);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
public void addObjectItem(final int index) {
final Path path = new Path();
final PathMeasure pathMeasure = new PathMeasure();
final ObjectItem objectItem = new ObjectItem();
//绘制三阶贝塞尔曲线 起点位置
PointF startPoint = new PointF();
//贝塞尔结束点
PointF endPoint = new PointF();
//贝塞尔控制点1
PointF control1 = new PointF();
//贝塞尔控制点2
PointF control2 = new PointF();
initControlPoint(startPoint, control1, control2, endPoint, index);
path.moveTo(startPoint.x, startPoint.y);
// path.cubicTo(control1.x, control1.y, control2.x, control2.y, endPoint.x, endPoint.y);
path.quadTo(control1.x, control1.y, endPoint.x, endPoint.y);
pathMeasure.setPath(path, false);
objectItem.index = index;
objectItem.pathMeasure = pathMeasure;
objectItem.startTime = System.currentTimeMillis();
objectItem.endTime = objectItem.startTime + mDuration;
objectItem.startAlpha = (int) (145 + (100) * Math.random());
objectItem.color = argb(objectItem.startAlpha, 255, 255, 255);
objectItem.angel = (float) (90 * Math.random());
objectItem.size = (int) dp2px(getContext(), (float) (7 + 8 * Math.random()));
mObjectItems.add(objectItem);
}
public static int argb(
@IntRange(from = 0, to = 255) int alpha,
@IntRange(from = 0, to = 255) int red,
@IntRange(from = 0, to = 255) int green,
@IntRange(from = 0, to = 255) int blue) {
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
/**
* 设置控制点
*
* @param control1
* @param control2
*/
private void initControlPoint(PointF startPoint, PointF control1, PointF control2, PointF endPoint, int index) {
int delta = (index % maxGroup);
float padding = dp2px(getContext(), 35);
float contentWidth = mWidth - 2*padding;
float average = ((contentWidth) * 1.0f / maxGroup);
startPoint.x = padding + (float) (contentWidth * Math.random());
startPoint.y = 0;
control1.x = padding / 2 + (float) (average * (delta) + average * Math.random());
control1.y = (float) (Math.random() * mHeight / 2);
if(control1.y ==0){
control1.y = mHeight/2;
}
control2.x = padding / 2 + (float) (average * (delta) + average * Math.random());
control2.y = (float) (Math.random() * mHeight / 2 + control1.y);
if(control2.y==control1.y){
control2.y = 2*control1.y;
}
endPoint.x = padding / 2 + (float) (average * (delta) + average * Math.random());
endPoint.y = mHeight;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (isPlaying) {
int count = mObjectItems.size();
long dx = System.currentTimeMillis() - lastIncrementTime;
if (count < 40 && dx > fraquceDuration) {
int num = mObjectCounter%3;
while(num>=0){
addObjectItem(mObjectCounter);
mObjectCounter++;
num--;
}
if (mObjectCounter >= Integer.MAX_VALUE) {
mObjectCounter = 0;
}
lastIncrementTime = System.currentTimeMillis();
}
}
Iterator<ObjectItem> iterator = mObjectItems.iterator();
do {
if (iterator == null || !iterator.hasNext()) break;
ObjectItem objectItem = iterator.next();
PathMeasure pathMeasure = objectItem.pathMeasure;
float pathLength = pathMeasure.getLength();
float currentTime = (System.currentTimeMillis() - objectItem.startTime);
float fraction = currentTime / mDuration;
if (fraction <= 1) {
float[] pos = new float[2];
pathMeasure.getPosTan(fraction * pathLength, pos, null);
objectItem.x = pos[0];
objectItem.y = pos[1];
if (objectItem.y == 0) {
objectItem.isRunning = false;
} else {
objectItem.isRunning = true;
}
objectItem.fraction = fraction;
} else {
objectItem.isRunning = false;
iterator.remove();
}
} while (true);
invalidate();
if (!isPlaying && mObjectItems.isEmpty()) {
cancelUpdateFrameAnimator();
}
}
public void stopPlay() {
isPlaying = false;
}
public static float dp2px(Context context, float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
context.getResources().getDisplayMetrics());
}
public void startPlay() {
cancelUpdateFrameAnimator();
isPlaying = true;
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1.0f);
//先加速后减速
animator.setInterpolator(new LinearInterpolator());
//动画的长短来控制速率
animator.setDuration(50);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.addUpdateListener(this);
animator.start();
mUpdateFrameAniator = animator;
}
private void cancelUpdateFrameAnimator() {
if (mUpdateFrameAniator != null) {
mUpdateFrameAniator.removeAllUpdateListeners();
mUpdateFrameAniator.cancel();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopPlay();
}
public boolean isPlaying() {
return isPlaying;
}
static class ObjectItem {
public int index;
public float fraction;
public float x;
public float y;
public boolean isRunning;
public long startTime = System.currentTimeMillis();
public long endTime = System.currentTimeMillis();
public float angel;
public int startAlpha;
private PathMeasure pathMeasure;
private int color = Color.WHITE;
private int size = 1;
public void draw(Canvas canvas, Paint paint) {
paint.setColor(color);
paint.setAlpha((int) ((startAlpha * (1.0f - this.fraction)) + 20*fraction));
RectF rectF = new RectF();
rectF.left = -size / 2;
rectF.top = -size / 2;
rectF.right = +size / 2;
rectF.bottom = +size / 2;
canvas.save();
canvas.translate(x, y);
canvas.rotate(angel);
canvas.drawRect(rectF, paint);
canvas.restore();
}
}
}