自定义View——游动锦鲤实践


不积跬步,无以至千里;不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵!

自定义View——游动锦鲤实践

一个对于自定义View的尝试,主要用于尝试熟悉自定义View的实现过程,有些小瑕疵。

布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/iv_fish"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

使用布局——MainAvtivity

package com.hnucm.otherapp;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ImageView ivFish = findViewById(R.id.iv_fish);
        ivFish.setImageDrawable(new FishDrawable());
    }
}

主要实现——FishDrawable

package com.hnucm.otherapp;

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.Point;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.view.animation.LinearInterpolator;
import android.widget.HeaderViewListAdapter;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * @author created by lvjunkai
 * @date 2021-09-27
 * @description: 实现一个自定义锦鲤的绘制
 */
public class FishDrawable extends Drawable {
    //路径
    private Path mPath;
    //画笔
    private Paint mPaint;
    //身体外透明度
    private final static int OTHER_ALPHA = 110;
    //身体透明度
    private final static int BODY_ALPHA = 160;
    //鱼头半径,用于改变鱼的大小
    private final static int HEAD_RADIUS = 150;
    //坐标
    private PointF middlePoint;
    //鱼朝向
    private float fishMainAngle = 90;
    //鱼身长度
    private final static float BODY_LENGTH = 3.2f * HEAD_RADIUS;
    //寻找鱼鳍起点的线长
    private final static float FIND_FINS_LENGTH = 0.9f * HEAD_RADIUS;
    //鱼鳍的长度
    private final static float FINS_LENGTH = 1.3f * HEAD_RADIUS;

    //-------------鱼尾----------------
    //尾部大圆的半径(圆心就是身体底部的中点)
    private final static float BIG_CIRCLE_RADIUS = HEAD_RADIUS * 0.7f;
    //尾部中圆的半径
    private final static float MIDDLE_CIRCLE_RADIUS = BIG_CIRCLE_RADIUS * 0.6f;
    //尾部小圆的半径
    private final static float SMALL_CIRCLE_RADIUS = MIDDLE_CIRCLE_RADIUS * 0.4f;
    // --寻找尾部中圆圆心的线长
    private final static float FIND_MIDDLE_CIRCLE_LENGTH = BIG_CIRCLE_RADIUS + MIDDLE_CIRCLE_RADIUS;
    // --寻找尾部小圆圆心的线长
    private final static float FIND_SMALL_CIRCLE_LENGTH = MIDDLE_CIRCLE_RADIUS * (0.4f + 2.7f);
    // --寻找大三角形底边中心点的线长
    private final static float FIND_TRIANGLE_LENGTH = MIDDLE_CIRCLE_RADIUS * 2.7f;

    private float currentValue = 0;
    private float currentValue1 = 0;
    private float currentValue2 = 0;

    //构造函数,用于初始化drawable
    public FishDrawable(){
        init();
    }

    //初始化
    private void init(){
        mPath = new Path();//路径
        mPaint = new Paint();//画笔
        mPaint.setAntiAlias(true);//抗锯齿
        mPaint.setDither(true);//防抖
        mPaint.setStyle(Paint.Style.FILL);//画笔类型填充
        mPaint.setARGB(OTHER_ALPHA,244,92,71);//设置颜色
        //鱼重心的坐标
        middlePoint = new PointF(4.18f * HEAD_RADIUS,4.18f * HEAD_RADIUS);
        //属性动画
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,360);
//        ValueAnimator valueAnimator = ValueAnimator.ofFloat(-1,1);
        valueAnimator.setDuration(1000);
//        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
        valueAnimator.setRepeatMode(ValueAnimator.RESTART);
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                currentValue = (float) animator.getAnimatedValue();
                invalidateSelf();
            }
        });
        valueAnimator.start();
    }

    //绘制
    @Override
    public void draw(@NonNull Canvas canvas) {
//        float fishAngle = fishMainAngle + currentValue * 20;
//        float fishAngle = fishMainAngle + currentValue * 10;
        float fishAngle = (float) (fishMainAngle + Math.sin(Math.toRadians(currentValue * 1)) * 10);
        //圆心
        PointF headPoint = calculatePoint(middlePoint,BODY_LENGTH / 2,fishAngle);
        //以圆心为中心,半径为HEAD_RADIUS,用画笔画圆
        canvas.drawCircle(headPoint.x,headPoint.y,HEAD_RADIUS,mPaint);
        //绘制右鱼鳍
        PointF rightFinsPoint = calculatePoint(headPoint,FIND_FINS_LENGTH,fishAngle - 110);
        makeFins(canvas,rightFinsPoint,fishAngle,true);
        //绘制左鱼鳍
        PointF leftFinsPoint = calculatePoint(headPoint,FIND_FINS_LENGTH,fishAngle + 110);
        makeFins(canvas,leftFinsPoint,fishAngle,false);

        //身体底部的中心点
        PointF bodyBottomCenterPoint = calculatePoint(headPoint,BODY_LENGTH,fishAngle - 180);
        //画节肢1
//        makeSegment(canvas,bodyBottomCenterPoint,BIG_CIRCLE_RADIUS,MIDDLE_CIRCLE_RADIUS,
//                FIND_MIDDLE_CIRCLE_LENGTH,fishAngle,true);
        PointF middleCircleCenterPoint = makeSegment(canvas,bodyBottomCenterPoint,BIG_CIRCLE_RADIUS,MIDDLE_CIRCLE_RADIUS,
                FIND_MIDDLE_CIRCLE_LENGTH,fishAngle,true);
        //画节肢2
//        PointF middleCircleCenterPoint = calculatePoint(bodyBottomCenterPoint,
//                FIND_MIDDLE_CIRCLE_LENGTH,fishAngle - 180);
        makeSegment(canvas,middleCircleCenterPoint,MIDDLE_CIRCLE_RADIUS,SMALL_CIRCLE_RADIUS,
                FIND_SMALL_CIRCLE_LENGTH,fishAngle,false);

        //画尾巴
        makeTriangle(canvas,middleCircleCenterPoint,FIND_TRIANGLE_LENGTH,
                BIG_CIRCLE_RADIUS,fishAngle);
        makeTriangle(canvas,middleCircleCenterPoint,FIND_TRIANGLE_LENGTH - 10,
                BIG_CIRCLE_RADIUS - 20,fishAngle);

        //画身体
        makeBody(canvas,headPoint,bodyBottomCenterPoint,fishAngle);
    }

    //绘制鱼鳍
    private void makeFins(Canvas canvas, PointF startPoint, float fishAngle,boolean isRightFins) {
        float controlAngle = 115;
        //二阶贝塞尔曲线的控制点坐标
        PointF controlPoint = calculatePoint(startPoint,1.8f * FINS_LENGTH,
                isRightFins ? fishAngle - controlAngle : fishAngle + controlAngle);
        //结束点坐标
        PointF endPoint = calculatePoint(startPoint,FINS_LENGTH,fishAngle - 180);
        mPath.reset();
        mPath.moveTo(startPoint.x,startPoint.y);
        mPath.quadTo(controlPoint.x,controlPoint.y,endPoint.x,endPoint.y);
        canvas.drawPath(mPath,mPaint);
    }

    //绘制节肢
    private PointF makeSegment(Canvas canvas,PointF bottomCenterPoint,float bigRadius,
                               float smallRadius,float findSmallCircleLength,float fishAngle,
                               boolean hasBigCircle){
        float segmentAngle;
        if(hasBigCircle){
//            segmentAngle = fishAngle + currentValue * 30;
            segmentAngle = (float) (fishAngle + Math.sin(Math.toRadians(currentValue * 2)) * 30);
        }else{
//            segmentAngle = fishAngle + currentValue * 50;
            segmentAngle = (float) (fishAngle + Math.sin(Math.toRadians(currentValue * 3)) * 50);
        }

        //梯形上底的中心点(中等大的圆的圆心)
//        PointF upperCenterPoint = calculatePoint(bottomCenterPoint,findSmallCircleLength,
//                fishAngle - 180);
        PointF upperCenterPoint = calculatePoint(bottomCenterPoint,findSmallCircleLength,
                segmentAngle - 180);
        //梯形的四个点
//        PointF bottomLeftPoint = calculatePoint(bottomCenterPoint,bigRadius,fishAngle + 90);
//        PointF bottomRightPoint = calculatePoint(bottomCenterPoint,bigRadius,fishAngle - 90);
//        PointF upperLeftPoint = calculatePoint(upperCenterPoint,smallRadius,fishAngle + 90);
//        PointF upperRightPoint = calculatePoint(upperCenterPoint,smallRadius,fishAngle - 90);
        PointF bottomLeftPoint = calculatePoint(bottomCenterPoint,bigRadius,segmentAngle + 90);
        PointF bottomRightPoint = calculatePoint(bottomCenterPoint,bigRadius,segmentAngle - 90);
        PointF upperLeftPoint = calculatePoint(upperCenterPoint,smallRadius,segmentAngle + 90);
        PointF upperRightPoint = calculatePoint(upperCenterPoint,smallRadius,segmentAngle - 90);
        if(hasBigCircle){
            //画大圆
            canvas.drawCircle(bottomCenterPoint.x,bottomCenterPoint.y,bigRadius,mPaint);
        }
        //画小圆
        canvas.drawCircle(upperCenterPoint.x,upperCenterPoint.y,smallRadius,mPaint);
        //画梯形
        mPath.reset();
        mPath.moveTo(bottomLeftPoint.x,bottomLeftPoint.y);
        mPath.lineTo(upperLeftPoint.x,upperLeftPoint.y);
        mPath.lineTo(upperRightPoint.x,upperRightPoint.y);
        mPath.lineTo(bottomRightPoint.x,bottomRightPoint.y);
        canvas.drawPath(mPath,mPaint);

        //绘制节肢2和三角形的起始点,必须在这儿返回
        return upperCenterPoint;
    }

    //绘制尾巴
    private void makeTriangle(Canvas canvas, PointF startPoint,
                              float findCenterLength,float findEdgeLength,float fishAngle){
        //三角形底边的中心点
        PointF centerPoint = calculatePoint(startPoint,findCenterLength,fishAngle - 180);
        //三角形底边的两点
        PointF leftPoint = calculatePoint(centerPoint,findEdgeLength,fishAngle + 90);
        PointF rightPoint = calculatePoint(centerPoint,findEdgeLength,fishAngle - 90);

        //绘制三角形
        mPath.reset();
        mPath.moveTo(startPoint.x,startPoint.y);
        mPath.lineTo(leftPoint.x,leftPoint.y);
        mPath.lineTo(rightPoint.x,rightPoint.y);
        canvas.drawPath(mPath,mPaint);
    }

    //绘制身体
    private void makeBody(Canvas canvas,PointF headPoint,PointF bodyBottomCenterPoint,float fishAngle){
        //身体的四个点
        PointF topLeftPoint = calculatePoint(headPoint,HEAD_RADIUS,fishAngle + 90);
        PointF topRightPoint = calculatePoint(headPoint,HEAD_RADIUS,fishAngle - 90);
        PointF bottomLeftPoint = calculatePoint(bodyBottomCenterPoint,BIG_CIRCLE_RADIUS,fishAngle + 90);
        PointF bottomRightPoint = calculatePoint(bodyBottomCenterPoint,BIG_CIRCLE_RADIUS,fishAngle - 90);

        // 二阶贝塞尔曲线的控制点,决定鱼的胖瘦
        PointF controlLeft = calculatePoint(headPoint,BODY_LENGTH * 0.56f,
                fishAngle + 130);
        PointF controlRight = calculatePoint(headPoint,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);
    }

    //计算公式  传入起始点坐标,给出两点之间的长度,两点连线和x坐标的夹角,求出点的坐标
    public static PointF calculatePoint(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);
    }

    //设置alpha值
    @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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值