美文网首页
Android 自定义游动的鱼view

Android 自定义游动的鱼view

作者: 清朝公主大清正黄旗 | 来源:发表于2022-05-23 14:18 被阅读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" />
    

    相关文章

      网友评论

          本文标题:Android 自定义游动的鱼view

          本文链接:https://www.haomeiwen.com/subject/ijprprtx.html