Android 自定义游动的鱼view
2022-05-23 本文已影响0人
清朝公主大清正黄旗
1、先画鱼
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 Paint mPaint;
private Path mPath;
private float currentValue = 0f;
private int OTHER_ALPHA = 1000;
public float getFrequance() {
return frequance;
}
public void setFrequance(float frequance) {
this.frequance = frequance;
}
//小鱼点击尾巴加速摆动
private float frequance = 1f;
public float getHEAD_RADIUS() {
return HEAD_RADIUS;
}
private float HEAD_RADIUS = 50;
public void setFishMainAngle(float fishMainAngle) {
this.fishMainAngle = fishMainAngle;
}
//
private float fishMainAngle = 90;
public PointF getMiddlePoint() {
return middlePoint;
}
//鱼的中心
private PointF middlePoint;
//鱼身长度
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 float FIND_MIDDLE_CIRCLE_LENGTH = BIG_CIRCLE_RADIUS * 1.6f;
//寻找小圆
private float FIND_SMALL_CIRCLE_LENGTH = MIDDLE_CIRCLE_RADIUS * 3.1f;
//寻找三角形
private float FIND_TRIANGLE = MIDDLE_CIRCLE_RADIUS * 2.7f;
//眼镜半径
private float EYE_CIRCLE = HEAD_RADIUS * 0.1f;
//鱼头坐标
PointF headPoint;
public PointF getHeadPoint() {
return headPoint;
}
FishDrawable(){
init();
}
private void init() {
mPaint = new Paint();
mPath = new Path();
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(0f,360f);
//动画周期
valueAnimator.setDuration(2000);
//重复模式
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
//重复次数
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
//插值器
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentValue = (float) animation.getAnimatedValue();
invalidateSelf();
}
});
valueAnimator.start();
}
@Override
public void draw(@NonNull Canvas canvas) {
//鱼的初始角度
float fishAngle = (float) (fishMainAngle + Math.sin(Math.toRadians(currentValue * frequance))*5);
//鱼头的圆心坐标
headPoint = calculatePoint(middlePoint, BODY_LENGTH ,fishAngle);
//画鱼头
canvas.drawCircle(headPoint.x,headPoint.y,HEAD_RADIUS,mPaint);
//画右鱼鳍
PointF rightFinsPoint = calculatePoint(headPoint,FIND_FINS_LENGTH,fishAngle-130);
makeFins(canvas,rightFinsPoint,fishAngle,true);
//画左鱼鳍
PointF leftFinsPoint = calculatePoint(headPoint,FIND_FINS_LENGTH,fishAngle+130);
makeFins(canvas,leftFinsPoint,fishAngle,false);
//画大节支
PointF bodyBottomCenterPoint = calculatePoint(headPoint,BODY_LENGTH,fishAngle-180);
PointF smallCenterPoint = makeSegment(canvas,bodyBottomCenterPoint,BIG_CIRCLE_RADIUS,MIDDLE_CIRCLE_RADIUS,FIND_MIDDLE_CIRCLE_LENGTH,
fishAngle,true);
//画小节支
makeSegment(canvas,smallCenterPoint,MIDDLE_CIRCLE_RADIUS,SMALL_CIRCLE_RADIUS,FIND_SMALL_CIRCLE_LENGTH,
fishAngle,false);
float findEdgeLength = (float) (Math.abs(Math.sin(Math.toRadians(currentValue * frequance)) * BIG_CIRCLE_RADIUS));
//画尾巴
makeTriangle(canvas,smallCenterPoint,FIND_TRIANGLE,findEdgeLength,fishAngle);
makeTriangle(canvas,smallCenterPoint,FIND_TRIANGLE-10,findEdgeLength-10,fishAngle);
//画身体
makeBody(canvas,headPoint,fishAngle);
//画眼睛
makeEyes(canvas,headPoint,fishAngle);
}
/**
*
* @param canvas
* @param headPoint
* @param fishAngle
*/
private void makeEyes(Canvas canvas, PointF headPoint, float fishAngle) {
PointF leftEye = calculatePoint(headPoint,HEAD_RADIUS,fishAngle + 45);
PointF rightEye = calculatePoint(headPoint,HEAD_RADIUS,fishAngle - 45);
canvas.drawCircle(leftEye.x,leftEye.y,EYE_CIRCLE,mPaint);
canvas.drawCircle(rightEye.x,rightEye.y,EYE_CIRCLE,mPaint);
}
/**
*
* @param canvas
* @param headPoint
* @param fishAngle
*/
private void makeBody(Canvas canvas, PointF headPoint, float fishAngle) {
PointF topLeft = calculatePoint(headPoint,HEAD_RADIUS,fishAngle+90);
PointF topRight = calculatePoint(headPoint,HEAD_RADIUS,fishAngle-90);
PointF bottomCenterPoint = calculatePoint(headPoint,BODY_LENGTH,fishAngle-180);
PointF bottomLeft = calculatePoint(bottomCenterPoint,BIG_CIRCLE_RADIUS,fishAngle+90);
PointF bottomRight = calculatePoint(bottomCenterPoint,BIG_CIRCLE_RADIUS,fishAngle-90);
PointF controlLeft = calculatePoint(headPoint,BODY_LENGTH*0.56f,fishAngle+140);
PointF controlRight = calculatePoint(headPoint,BODY_LENGTH*0.56f,fishAngle-140);
mPath.reset();
mPath.moveTo(topLeft.x,topLeft.y);
mPath.quadTo(controlLeft.x,controlLeft.y,bottomLeft.x,bottomLeft.y);
mPath.lineTo(bottomRight.x,bottomRight.y);
mPath.quadTo(controlRight.x,controlRight.y,topRight.x,topRight.y);
canvas.drawPath(mPath,mPaint);
}
/**
*
* @param canvas
* @param startPoint
* @param fishAngle
*/
private void makeTriangle(Canvas canvas, PointF startPoint,float findTriangle, float bigCircleLength, float fishAngle) {
float tr = 0;
tr = (float) (fishAngle + Math.sin(Math.toRadians(currentValue*2*frequance))*30);
//三角形底边中心坐标
PointF centerPoint = calculatePoint(startPoint,findTriangle,tr - 180);
//得到三角形的两个点
PointF bottomLeft = calculatePoint(centerPoint,bigCircleLength,tr+90);
PointF bottomRight = calculatePoint(centerPoint,bigCircleLength,tr-90);
mPath.reset();
mPath.moveTo(startPoint.x,startPoint.y);
mPath.lineTo(bottomLeft.x,bottomLeft.y);
mPath.lineTo(bottomRight.x,bottomRight.y);
canvas.drawPath(mPath,mPaint);
}
/**
*
* @param canvas
* @param startPoint
* @param fishAngle
* @return
*/
private PointF makeSegment(Canvas canvas, PointF startPoint,float bigRadius, float smallRadius,
float findLength, float fishAngle, boolean hasBigCircle) {
float segmentAngle = 0f;
if (hasBigCircle){
segmentAngle = (float) (fishAngle + Math.cos(Math.toRadians(currentValue*2* frequance))*15);
}else {
segmentAngle = (float) (fishAngle + Math.sin(Math.toRadians(currentValue*2* frequance))*30);
}
//梯形上底圆心
PointF upperPoint = calculatePoint(startPoint,findLength,segmentAngle - 180);
//梯形4个点
PointF bottomLeftPoint = calculatePoint(startPoint,bigRadius,segmentAngle-90);
PointF bottomRightPoint = calculatePoint(startPoint,bigRadius,segmentAngle+90);
PointF upperLeftPoint = calculatePoint(upperPoint,smallRadius,segmentAngle-90);
PointF upperRightPoint = calculatePoint(upperPoint,smallRadius,segmentAngle+90);
if(hasBigCircle) {
//画大圆
canvas.drawCircle(startPoint.x, startPoint.y, bigRadius, mPaint);
}
//画小圆
canvas.drawCircle(upperPoint.x,upperPoint.y,smallRadius,mPaint);
//画梯形
mPath.reset();
mPath.moveTo(bottomLeftPoint.x,bottomLeftPoint.y);
mPath.lineTo(bottomRightPoint.x,bottomRightPoint.y);
mPath.lineTo(upperRightPoint.x,upperRightPoint.y);
mPath.lineTo(upperLeftPoint.x,upperLeftPoint.y);
canvas.drawPath(mPath,mPaint);
return upperPoint;
}
/**
* 画鱼鳍
* @param canvas
* @param startPoint
* @param fishAngle
* @param isRight
*/
private void makeFins(Canvas canvas, PointF startPoint, float fishAngle, boolean isRight) {
float controlAngle = 115;
PointF endPoint = calculatePoint(startPoint,FINS_LENGTH,fishAngle - 180);
mPath.reset();
mPath.moveTo(startPoint.x,startPoint.y);
//控制点
PointF controlPoint = calculatePoint(startPoint,FINS_LENGTH * 1.8f,
isRight? fishAngle - controlAngle : fishAngle + controlAngle);
mPath.quadTo(controlPoint.x,controlPoint.y,endPoint.x,endPoint.y);
canvas.drawPath(mPath,mPaint);
}
/**
* 求关键坐标点的核心算法
* @param startPoint 相对点的坐标
* @param length 相对点到要求的点之间的直线距离
* @param angle 和fishAngle之间的角度
* @return
*/
public PointF calculatePoint(PointF startPoint, float length, float angle){
//x
float deltaX = (float) (Math.cos(Math.toRadians(angle)) * length);
//Y
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 getIntrinsicHeight() {
return (int) (8.38f * HEAD_RADIUS);
}
@Override
public int getIntrinsicWidth() {
return (int) (8.38f * HEAD_RADIUS);
}
}
2、在画一个容器,让鱼在容器中跟随点击的位置游动到按下的位置
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.Color;
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.widget.ImageView;
import android.widget.RelativeLayout;
import androidx.annotation.RequiresApi;
public class FishRelativeLayout extends RelativeLayout {
private Path mPath;
private Paint mPaint;
private ImageView iv_fish;
private FishDrawable fishDrawable;
private float touchX;
private float touchY;
private float ripple ;
@Override
public float getAlpha() {
return alpha;
}
@Override
public void setAlpha(float alpha) {
this.alpha = alpha;
}
private float alpha;
public float getRipple() {
return ripple;
}
public void setRipple(float ripple) {
this.ripple = ripple;
alpha = 100*(1-ripple);
}
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);
}
private void init(Context context) {
//VIewGroup默认不执行onDraw方法
setWillNotDraw(false);
//画水波纹
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(8);
mPaint.setColor(Color.RED);
iv_fish = new ImageView(context);
LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
iv_fish.setLayoutParams(layoutParams);
fishDrawable = new FishDrawable();
iv_fish.setImageDrawable(fishDrawable);
addView(iv_fish);
mPath = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setAlpha((int) 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();
ObjectAnimator.ofFloat(this,"ripple",0f,1f).setDuration(500).start();
makeTrail();
return super.onTouchEvent(event);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void makeTrail() {
//鱼的重心相对坐标
PointF fishRelativeMiddle = fishDrawable.getMiddlePoint();
//鱼的绝对坐标 --- 起始点
PointF fishMiddle = new PointF(fishRelativeMiddle.x + iv_fish.getX(), fishRelativeMiddle.y + iv_fish.getY());
// 鱼头坐标 ---- 控制点一
final PointF fishHead = new PointF(fishDrawable.getHeadPoint().x + iv_fish.getX(),
fishDrawable.getHeadPoint().y+iv_fish.getY());
//点击坐标 ----结束点
PointF touch = new PointF(touchX,touchY);
/**
* 先用cos公式向量+三角函数算出AOB的度数
* 控制点2在AOB的角平分线上(人为规定的)
*/
//控制点2的坐标
float angle = includeAngle(fishMiddle,fishHead,touch)/2;
float delta = includeAngle(fishMiddle,new PointF(fishMiddle.x + 1, fishMiddle.y),fishHead);
PointF control2 = fishDrawable.calculatePoint(fishMiddle, fishDrawable.getHEAD_RADIUS()*1.6f ,Math.abs(delta-angle));
mPath.reset();
mPath.moveTo(fishMiddle.x- fishRelativeMiddle.x,fishMiddle.y - fishRelativeMiddle.y);
mPath.cubicTo(fishHead.x- fishRelativeMiddle.x,fishHead.y- fishRelativeMiddle.y,control2.x,control2.y,touch.x- fishRelativeMiddle.x,touch.y- fishRelativeMiddle.y);
ObjectAnimator animator = ObjectAnimator.ofFloat(iv_fish, "x", "y", mPath);
animator.setDuration(2500);
//鱼游动时胃部加快摆动
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
fishDrawable.setFrequance(1);
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
fishDrawable.setFrequance(4);
}
});
final PathMeasure pathMeasure = new PathMeasure(mPath,false);
final float[] tan = new float[2];
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//执行了周期的多少百分比
float fraction = animation.getAnimatedFraction();
//得到路径的切线
pathMeasure.getPosTan(pathMeasure.getLength() * fraction,null,tan);
float angle = (float) Math.toDegrees(Math.atan2(-tan[1],tan[0]));
fishDrawable.setFishMainAngle(angle);
}
});
animator.start();
}
/**
*
* @param A
* @param O
* @param B
* @return
*/
public float includeAngle(PointF A, PointF O, PointF B){
//0A*0B向量积
float AOB = (A.x-O.x)*(B.x-O.x) + (A.y-O.y)*(B.y-O.y);
//OA*OB绝对值
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;
}
}
}
}
3、使用:
<FishRelativeLayout
android:id="@+id/fish"
android:layout_width="match_parent"
android:layout_height="match_parent" />