游动的鲤鱼

游动的鲤鱼用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();

                //ZSG
                //重绘drawable
                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);

        //ZSG
        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) {

            //ZSG
            //这里是Math.cos,因为cos比sin快90度
            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);

        //ZSG
        //先确定path在画
        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;
    }

    //ZSG
    //这里设置Drawable的宽高
    @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) {
        //ZSG
        //viewgrop 默认不绘制
        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);

        //ZSG
        //这里的x,y是view 里自带的setX setY,只要有set ,get 方法,有没有属性x,y 没有影响
        //注意这里的addListener和addUpdateListener是不同的
        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);
            }
        });

        //ZSG
        //pathMeasure + path 得到tan 的用法
        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));

        // AB连线与X的夹角的tan值 - OB与x轴的夹角的tan值
        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;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值