不积跬步,无以至千里;不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵!
自定义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);
}
}