android自定义Drawable实现炫酷UI-锦鲤游泳效果

一、实现效果:

当点击屏幕的时候,屏幕中的锦鲤会身体摆动并且游到屏幕点击处,如下图:

效果分析:

1、小鱼的身体各个部件都是简单的半透明几何图形。

2、各个部件都可以活动。

3、从头到尾方向的部件摆动幅度越来越大,频率越来越高。

二、实现思路:

1、实现小鱼的绘制,创建一个自定义Drawable,把小鱼的形状画出来,然后放到ImageView上面显示。

2、实现小鱼的原地摆动,通过属性动画ValueAnimator改变鱼的角度来实现让小鱼可以在原地摆动。

3、实现小鱼的点击游动。

三、自定义Drawable

1、drawable是什么?

(1)、一种可以在Canvas上进行绘制的抽象的概念。

(2)、颜色、图片等都可以是一个Drawable。

(3)、Drawable可以通过XML定义,或者通过代码创建。

(4)、Android中Drawable是一个抽象类,每个具体的Drawable都是其子类。

2、drawable的优点有哪些?

(1)、使用简单,比自定义View成本低。

(2)、非图片类的Drawable所占空间小,能减小apk的大小。

四、鱼鳍和鱼身的弧线如何实现?

        要实现弧线,我们需要使用贝塞尔曲线,而对于贝塞尔曲线,最重要的点是数据点和控制               点。
        数据点: 指一条路径的起始点和终止点。
        控制点:控制点决定了一条路径的弯曲轨迹

        根据控制点的个数,贝塞尔曲线被分为一阶贝塞尔曲线(0个控制点)、二阶贝塞尔曲                 线  (1  个控制点)、三阶贝塞尔曲线(2个控制点)等等。

         我们可以使用二阶贝赛尔曲线来实现:

在这里插入图片描述

五、代码实现

 1、activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!--实现鱼的原地摆尾-->
    <ImageView
        android:id="@+id/iv_fish"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" />
</RelativeLayout>

 2、MainActivity.java

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());
    }
}

 3、FishDrawable.java

public class FishDrawable extends Drawable {
    private Paint mPaint;
    private Path mPath;

    // 身体之外的部分的透明度
    private final static int OTHER_ALPHA = 110;
    // 身体的透明度
    private final static int BODY_ALPHA = 160;

    // 鱼的重心(鱼身的中心)
    private PointF middlePoint;

    private float fishMainAngle = 90;

    public final static float HEAD_RADIUS = 150;

    // 鱼身的长度
    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 float BIG_CIRCLE_RADIUS = HEAD_RADIUS * 0.7f;
    // 尾部中圆的半径
    private final float MIDDLE_CIRCLE_RADIUS = BIG_CIRCLE_RADIUS * 0.6f;
    // 尾部小圆的半径
    private final float SMALL_CIRCLE_RADIUS = MIDDLE_CIRCLE_RADIUS * 0.4f;
    // --寻找尾部中圆圆心的线长
    private final float FIND_MIDDLE_CIRCLE_LENGTH = BIG_CIRCLE_RADIUS + MIDDLE_CIRCLE_RADIUS;
    // --寻找尾部小圆圆心的线长
    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;

    public FishDrawable() {
        init();
    }

    private void init() {
        mPath = new Path();// 路径
        mPaint = new Paint();// 画笔
        mPaint.setStyle(Paint.Style.FILL);// 画笔类型,填充
        mPaint.setARGB(OTHER_ALPHA, 244, 92, 71);// 设置颜色
        mPaint.setAntiAlias(true);// 抗锯齿
        mPaint.setDither(true);// 防抖

        middlePoint = new PointF(4.19f * HEAD_RADIUS, 4.19f * HEAD_RADIUS);
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        float fishAngle = fishMainAngle;

        // 绘制鱼头
        PointF headPoint = calculatePoint(middlePoint, BODY_LENGTH / 2, fishAngle);
        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);

        // 绘制节肢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);
    }

    /**
     * 画鱼身
     * @param headPoint
     * @param bodyBottomCenterPoint
     */
    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);
    }

    /**
     * 画三角形
     * @param findCenterLength 顶点到底部的垂直线长
     * @param findEdgeLength 底部一半
     */
    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);
    }

    /**
     * 画节肢
     * @param bottomCenterPoint  梯形底部的中心点坐标(长边)
     * @param bigRadius 大圆的半径
     * @param smallRadius 小圆的半径
     * @param findSmallCircleLength 寻找梯形小圆的线长
     * @param hasBigCircle 是否有大圆
     */
    private void makeSegment(Canvas canvas, PointF bottomCenterPoint, float bigRadius,
                             float smallRadius, float findSmallCircleLength, float fishAngle,
                             boolean hasBigCircle) {
        // 梯形上底的中心点(短边)
        PointF upperCenterPoint = calculatePoint(bottomCenterPoint, findSmallCircleLength,
                fishAngle - 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);

        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);
    }

    /**
     * 绘制鱼鳍
     *
     * @param startPoint  起始点的坐标
     * @param fishAngle   鱼头相对于x坐标的角度
     * @param isRightFins
     */
    private void makeFins(Canvas canvas, PointF startPoint, float fishAngle, boolean isRightFins) {
        float controlAngle = 115;

        // 结束点
        PointF endPoint = calculatePoint(startPoint, FINS_LENGTH, fishAngle - 180);
        // 控制点
        PointF controlPoint = calculatePoint(startPoint, 1.8f * FINS_LENGTH,
                isRightFins ? fishAngle - controlAngle : fishAngle + controlAngle);

        mPath.reset();
        mPath.moveTo(startPoint.x, startPoint.y);
        // 二阶贝塞尔曲线
        mPath.quadTo(controlPoint.x, controlPoint.y, endPoint.x, endPoint.y);

        canvas.drawPath(mPath, mPaint);

    }

    /**
     * 求对应点的坐标 -- 知道起始点,知道鱼头的角度,知道两点间的距离,就可以算出想要的点的坐标
     *
     * @param startPoint 起始点的坐标
     * @param length     两点间的长度
     * @param angle      鱼头相对于x坐标的角度
     * @return
     */
    public static PointF calculatePoint(PointF startPoint, float length, float angle) {
        // angle 角度(0度~360度)  三角函数 -- 弧度
        float deltaX = (float) (Math.cos(Math.toRadians(angle)) * length);
        float deltaY = (float) (-Math.sin(Math.toRadians(angle)) * 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;
    }

    // 如果ImageView的宽高为wrap_content,则获取这个值
    @Override
    public int getIntrinsicHeight() {
        return (int) (8.38f * HEAD_RADIUS);
    }

    @Override
    public int getIntrinsicWidth() {
        return (int) (8.38f * HEAD_RADIUS);
    }
}

到此,鱼的形状已经描绘成功,想要实现游泳效果,请下载完整demo:

android自定义炫酷UI-灵动的锦鲤-Android文档类资源-CSDN下载

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这些文件夹是Android中的资源文件夹,用于存放应用程序所需的各种资源文件,例如图片、布局文件、颜色值、字符串等等。其中,drawable-v21和drawable-v24是用于存放特定版本的Android系统所需的图片资源文件,下面是它们的区别: - drawable-v21:这个文件夹是用于存放Android 5.0(API level 21)及以上版本所需的图片资源文件。通常,这些图片资源文件用于支持Material Design风格的UI设计,如矢量图标、状态选择器等。 - drawable-v24:这个文件夹是用于存放Android 7.0(API level 24)及以上版本所需的图片资源文件。它主要用于支持Android Nougat(7.0)引入的多窗口模式,以及支持新的设备像素密度。 如果你的应用程序需要支持不同版本的Android系统,那么你可以将特定版本所需的图片资源文件放置在对应的文件夹中,这样系统会根据设备的Android版本来自动加载对应版本的图片资源文件。 举个例子,如果你想在Android 5.0及以上版本中使用Material Design风格的矢量图标,那么你可以将这些图片资源文件放置在drawable-v21文件夹中。而如果你想在Android 7.0及以上版本中支持多窗口模式,那么你可以将相关的图片资源文件放置在drawable-v24文件夹中。 在使用这两个文件夹时,你需要注意以下几点: - 文件夹名称必须按照规定的格式命名,即“drawable-vxx”,其中xx代表Android版本号。 - 如果你在drawable文件夹中也有相同名称的图片资源文件,那么系统会优先加载drawable-vxx文件夹中对应版本的图片资源文件。 - 如果你的应用程序只支持较低版本的Android系统,那么你可以将所有图片资源文件都放置在drawable文件夹中,系统会自动加载适合当前设备的版本的图片资源文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值