游动的鲤鱼用Drawable画,然后用控制imageview的位置
一、Drawable
package com.example.fish;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.view.animation.LinearInterpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class FishDrawable extends Drawable {
private Path mPath;
private Paint mPaint;
private int OTHER_ALPHA = 110;
private int BODY_ALPHA = 160;
private float HEAD_RADIUS = 50;
private float BODY_LENGTH = HEAD_RADIUS * 3.2f;
private float FIND_FINS_LENGTH = 0.9f * HEAD_RADIUS;
private float FINS_LENGTH = 1.3f * HEAD_RADIUS;
private float BIG_CIRCLE_RADIUS = 0.7f * HEAD_RADIUS;
private float MIDDLE_CIRCLE_RADIUS = 0.6f * BIG_CIRCLE_RADIUS;
private float SMALL_CIRCLE_RADIUS = 0.4f * MIDDLE_CIRCLE_RADIUS;
private final float FIND_MIDDLE_CIRCLE_LENGTH = BIG_CIRCLE_RADIUS * (0.6f + 1);
private final float FIND_SMALL_CIRCLE_LENGTH = MIDDLE_CIRCLE_RADIUS * (0.4f + 2.7f);
private final float FIND_TRIANGLE_LENGTH = MIDDLE_CIRCLE_RADIUS * 2.7f;
private PointF middlePoint;
private float fishMainAngle = 90;
private float animatedValue = 0;
private float segmentAngle;
private PointF headPoinF;
private float frequence = 1f;
public FishDrawable() {
init();
}
public PointF getMiddlePoint() {
return middlePoint;
}
public void setMiddlePoint(PointF middlePoint) {
this.middlePoint = middlePoint;
}
public PointF getHeadPoinF() {
return headPoinF;
}
public void setHeadPoinF(PointF headPoinF) {
this.headPoinF = headPoinF;
}
public float getHEAD_RADIUS() {
return HEAD_RADIUS;
}
public void setHEAD_RADIUS(float HEAD_RADIUS) {
this.HEAD_RADIUS = HEAD_RADIUS;
}
public float getFrequence() {
return frequence;
}
public void setFrequence(float frequence) {
this.frequence = frequence;
}
public float getFishMainAngle() {
return fishMainAngle;
}
public void setFishMainAngle(float fishMainAngle) {
this.fishMainAngle = fishMainAngle;
}
private void init() {
mPath = new Path();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setARGB(OTHER_ALPHA,244,92,71);
middlePoint = new PointF(4.19f * HEAD_RADIUS, 4.19f * HEAD_RADIUS);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,3600f);
valueAnimator.setDuration(15 * 1000);
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
animatedValue = (float) animation.getAnimatedValue();
invalidateSelf();
}
});
valueAnimator.start();
}
@Override
public void draw(@NonNull Canvas canvas) {
float fishAngle = (float) (fishMainAngle + Math.sin(Math.toRadians(animatedValue * 1.2 *frequence)) * 10);
headPoinF = calculatePoin(middlePoint, BODY_LENGTH / 2, fishAngle);
canvas.drawCircle(headPoinF.x, headPoinF.y,HEAD_RADIUS,mPaint);
PointF rightFinsPointF = calculatePoin(headPoinF, FIND_FINS_LENGTH, fishAngle - 110);
makeFins(canvas, rightFinsPointF, fishAngle, true);
PointF leftFinsPointF = calculatePoin(headPoinF, FIND_FINS_LENGTH, fishAngle + 110);
makeFins(canvas, leftFinsPointF, fishAngle, false);
PointF bottomCenterPoint = calculatePoin(headPoinF, BODY_LENGTH, fishAngle - 180);
PointF middleCirclePoint = makeSegment(canvas, bottomCenterPoint, BIG_CIRCLE_RADIUS, MIDDLE_CIRCLE_RADIUS,
FIND_MIDDLE_CIRCLE_LENGTH, fishAngle, true);
makeSegment(canvas, middleCirclePoint, MIDDLE_CIRCLE_RADIUS, SMALL_CIRCLE_RADIUS, FIND_SMALL_CIRCLE_LENGTH
, fishAngle, false);
float findEdgeLength = (float) Math.abs(Math.sin(Math.toRadians(animatedValue * 1.5*frequence)) * BIG_CIRCLE_RADIUS);
makeTriangle(canvas,middleCirclePoint,FIND_TRIANGLE_LENGTH,findEdgeLength,fishAngle);
makeTriangle(canvas,middleCirclePoint,FIND_TRIANGLE_LENGTH - 10,
findEdgeLength - 20,fishAngle);
makeBody(canvas, headPoinF,bottomCenterPoint,fishAngle);
}
private void makeBody(Canvas canvas, PointF headPoinF, PointF bottomCenterPoint, float fishAngle) {
PointF topLeftPoint = calculatePoin(headPoinF, HEAD_RADIUS, fishAngle + 90);
PointF topRightPoint = calculatePoin(headPoinF, HEAD_RADIUS, fishAngle - 90);
PointF bottomLeftPoint = calculatePoin(bottomCenterPoint, BIG_CIRCLE_RADIUS, fishAngle + 90);
PointF bottomRightPoint = calculatePoin(bottomCenterPoint, BIG_CIRCLE_RADIUS, fishAngle - 90);
PointF controlLeft = calculatePoin(headPoinF, BODY_LENGTH * 0.56f, fishAngle + 130);
PointF controlRight = calculatePoin(headPoinF, BODY_LENGTH * 0.56f, fishAngle - 130);
mPath.reset();
mPath.moveTo(topLeftPoint.x,topLeftPoint.y);
mPath.quadTo(controlLeft.x,controlLeft.y,bottomLeftPoint.x, bottomLeftPoint.y);
mPath.lineTo(bottomRightPoint.x,bottomRightPoint.y);
mPath.quadTo(controlRight.x, controlRight.y, topRightPoint.x, topRightPoint.y);
mPaint.setAlpha(BODY_ALPHA);
canvas.drawPath(mPath,mPaint);
}
private void makeTriangle(Canvas canvas, PointF startPoin, float findCenterLenth, float trangleEdge, float fishAngle) {
float trangleAngle = (float) (fishAngle + Math.sin(Math.toRadians(animatedValue * 1.5*frequence)) * 35);
PointF tranglePoint = calculatePoin(startPoin, findCenterLenth, trangleAngle - 180);
PointF leftPointF = calculatePoin(tranglePoint, trangleEdge, trangleAngle + 90);
PointF rightPointF = calculatePoin(tranglePoint, trangleEdge, trangleAngle - 90);
mPath.reset();
mPath.moveTo(startPoin.x, startPoin.y);
mPath.lineTo(leftPointF.x,leftPointF.y);
mPath.lineTo(rightPointF.x,rightPointF.y);
canvas.drawPath(mPath,mPaint);
}
private PointF makeSegment(Canvas canvas, PointF bottomCenterPoint, float bigCircleRadius,
float middleCircleRadius, float find_middle_circle_length,
float fishAngle, boolean hasBigCircle) {
float segmentAngle;
if (hasBigCircle) {
segmentAngle = (float) (fishAngle + Math.cos(Math.toRadians(animatedValue * 1.5*frequence)) * 15);
} else {
segmentAngle = (float) (fishAngle + Math.sin(Math.toRadians(animatedValue * 1.5*frequence)) * 35);
}
PointF upCenterPoint = calculatePoin(bottomCenterPoint, find_middle_circle_length, segmentAngle - 180);
PointF bottomLeft = calculatePoin(bottomCenterPoint, bigCircleRadius, segmentAngle + 90);
PointF bottomRight = calculatePoin(bottomCenterPoint, bigCircleRadius, segmentAngle - 90);
PointF uperLeft = calculatePoin(upCenterPoint, middleCircleRadius, segmentAngle + 90);
PointF uperRight = calculatePoin(upCenterPoint, middleCircleRadius, segmentAngle - 90);
if (hasBigCircle) {
canvas.drawCircle(bottomCenterPoint.x,bottomCenterPoint.y,bigCircleRadius,mPaint);
}
canvas.drawCircle(upCenterPoint.x,upCenterPoint.y,middleCircleRadius,mPaint);
mPath.reset();
mPath.moveTo(bottomLeft.x,bottomLeft.y);
mPath.lineTo(bottomRight.x,bottomRight.y);
mPath.lineTo(uperRight.x,uperRight.y);
mPath.lineTo(uperLeft.x,uperLeft.y);
canvas.drawPath(mPath,mPaint);
return upCenterPoint;
}
private void makeFins(Canvas canvas, PointF startPoint, float fishAngle, boolean isRight) {
float controlAngle = 115;
PointF endPoint = calculatePoin(startPoint, FINS_LENGTH, fishAngle - 180);
PointF controlPoint = calculatePoin(startPoint, FINS_LENGTH * 1.8f,
isRight ? fishAngle - controlAngle : fishAngle + controlAngle);
mPath.reset();
mPath.moveTo(startPoint.x,startPoint.y);
mPath.quadTo(controlPoint.x,controlPoint.y,endPoint.x,endPoint.y);
canvas.drawPath(mPath,mPaint);
}
public PointF calculatePoin(PointF startPoint, float length, float angle) {
float deltaX = (float) (Math.cos(Math.toRadians(angle)) * length);
float deltaY = (float) (Math.sin(Math.toRadians(angle - 180)) * length);
return new PointF(startPoint.x + deltaX, startPoint.y + deltaY);
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public int getIntrinsicWidth() {
return (int) (8.38f * HEAD_RADIUS);
}
@Override
public int getIntrinsicHeight() {
return (int) (8.38f * HEAD_RADIUS);
}
}
二、imageview
package com.example.fish;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PointF;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import androidx.annotation.RequiresApi;
public class FishRelativeLayout extends RelativeLayout {
private Paint mPaint;
private ImageView ivFish;
private FishDrawable fishDrawable;
private float touchX;
private float touchY;
private float ripple;
private int alpha;
public FishRelativeLayout(Context context) {
this(context,null);
}
public FishRelativeLayout(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public FishRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public float getRipple() {
return ripple;
}
public void setRipple(float ripple) {
alpha = (int) (100 * (1 - ripple));
this.ripple = ripple;
}
private void init(Context context) {
setWillNotDraw(false);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(8);
ivFish = new ImageView(context);
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ivFish.setLayoutParams(layoutParams);
fishDrawable = new FishDrawable();
ivFish.setImageDrawable(fishDrawable);
addView(ivFish);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setAlpha(alpha);
canvas.drawCircle(touchX,touchY,ripple * 150,mPaint);
invalidate();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onTouchEvent(MotionEvent event) {
touchX = event.getX();
touchY = event.getY();
mPaint.setAlpha(100);
ObjectAnimator ripple = ObjectAnimator.ofFloat(this, "ripple", 0, 1f).setDuration(1000);
ripple.start();
makeTrail();
return super.onTouchEvent(event);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void makeTrail() {
PointF fishRelativeMiddle = fishDrawable.getMiddlePoint();
PointF fishMiddle = new PointF(ivFish.getX() + fishRelativeMiddle.x, ivFish.getY() + fishRelativeMiddle.y);
PointF headPoinF = fishDrawable.getHeadPoinF();
PointF fishHead = new PointF(ivFish.getX() + headPoinF.x, ivFish.getY() + headPoinF.y);
PointF touch = new PointF(touchX, touchY);
float angle = includeAngle(fishMiddle, fishHead, touch) / 2;
float delta = includeAngle(fishMiddle, new PointF(fishMiddle.x + 1, fishMiddle.y), fishHead);
PointF controlPoint = fishDrawable.calculatePoin(fishMiddle,
fishDrawable.getHEAD_RADIUS() * 1.6f, angle + delta);
Path path = new Path();
path.moveTo(fishMiddle.x - fishRelativeMiddle.x, fishMiddle.y - fishRelativeMiddle.y);
path.cubicTo(fishHead.x - fishRelativeMiddle.x, fishHead.y - fishRelativeMiddle.y,
controlPoint.x - fishRelativeMiddle.x, controlPoint.y - fishRelativeMiddle.y,
touchX - fishRelativeMiddle.x, touchY - fishRelativeMiddle.y);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(ivFish, "x", "y", path);
objectAnimator.setDuration(2000);
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
fishDrawable.setFrequence(1f);
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
fishDrawable.setFrequence(3f);
}
});
PathMeasure pathMeasure = new PathMeasure(path, false);
float[] tan = new float[2];
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction = animation.getAnimatedFraction();
pathMeasure.getPosTan(pathMeasure.getLength() * fraction, null, tan);
float degrees = (float) Math.toDegrees(Math.atan2(-tan[1], tan[0]));
fishDrawable.setFishMainAngle(degrees);
}
});
objectAnimator.start();
}
private float includeAngle(PointF O, PointF A, PointF B) {
float AOB = (A.x - O.x) * (B.x - O.x) + (A.y - O.y) * (B.y - O.y);
float OALength = (float) Math.sqrt((A.x - O.x)*(A.x - O.x) + (A.y - O.y)*(A.y - O.y));
float OBLength = (float) Math.sqrt((B.x - O.x)*(B.x - O.x) + (B.y - O.y)*(B.y - O.y));
float cosAOB = AOB / (OALength * OBLength);
float angleAOB = (float) Math.toDegrees(Math.acos(cosAOB));
float direction = (A.y - B.y) / (A.x - B.x) - (O.y - B.y) / (O.x - B.x);
if (direction == 0) {
if (AOB >= 0) {
return 0;
} else {
return 180;
}
} else {
if (direction > 0) {
return -angleAOB;
} else {
return angleAOB;
}
}
}
}