手把手讲解-一个复杂动效的自定义绘制,2021最新Java开发面试大全

1、canvas.clipPath 画布裁剪
2、canvas.save 画布状态保存
3、canvas.restore 恢复
4、canvas.translate 画布平移
6、path.rCubicTo 构建三阶贝塞尔曲线(相当于上一个点位置)
5、属性动画 ValueAnimator / AnimatorSet

#开始撸码
按照之前拆解的步骤,

###第1步:构建一个心形区域

当一个复杂图形摆在我们面前,而且还是不规则图形,我们首先应该想到的,就是android.graphics.Path 类,它可以记录复杂图形的全部点组成的路径。
关键代码:

/**
     * 构建心形
     * <p>
     * 注意,它这个是以 矩形区域中心点为基准的图形,所以绘制的时候,必须先把坐标轴移动到 区域中心
     */
    private void initHeartPath(Path path) {
        List<PointF> pointList = new ArrayList<>();
        pointList.add(new PointF(0, Utils.dp2px(-38)));
        pointList.add(new PointF(Utils.dp2px(50), Utils.dp2px(-103)));
        pointList.add(new PointF(Utils.dp2px(112), Utils.dp2px(-61)));
        pointList.add(new PointF(Utils.dp2px(112), Utils.dp2px(-12)));
        pointList.add(new PointF(Utils.dp2px(112), Utils.dp2px(37)));
        pointList.add(new PointF(Utils.dp2px(51), Utils.dp2px(90)));
        pointList.add(new PointF(0, Utils.dp2px(129)));
        pointList.add(new PointF(Utils.dp2px(-51), Utils.dp2px(90)));
        pointList.add(new PointF(Utils.dp2px(-112), Utils.dp2px(37)));
        pointList.add(new PointF(Utils.dp2px(-112), Utils.dp2px(-12)));
        pointList.add(new PointF(Utils.dp2px(-112), Utils.dp2px(-61)));
        pointList.add(new PointF(Utils.dp2px(-50), Utils.dp2px(-103)));

        path.reset();
        for (int i = 0; i < 4; i++) {
            if (i == 0) {
                path.moveTo(pointList.get(i * 3).x, pointList.get(i * 3).y);
            } else {
                path.lineTo(pointList.get(i * 3).x, pointList.get(i * 3).y);
            }

            int endPointIndex;
            if (i == 3) {
                endPointIndex = 0;
            } else {
                endPointIndex = i * 3 + 3;
            }

            path.cubicTo(pointList.get(i * 3 + 1).x, pointList.get(i * 3 + 1).y,
                    pointList.get(i * 3 + 2).x, pointList.get(i * 3 + 2).y,
                    pointList.get(endPointIndex).x, pointList.get(endPointIndex).y); //你的心形就是用贝塞尔曲线来画的吗
        }
        path.close();
        path.computeBounds(mHeartRect, false);//把path所占据的最小矩形区域,返回出去
    }

传入一个Path引用,然后在方法内部对path进行各种api调用改变其属性. 这里需要提及一个重点:最后一行代码 path.computeBounds(mHeartRect, false); 意思是,无论什么样的path,它都会占据一个最小矩形区域,computeBounds方法可以获取这个矩形区域,设置给入参mHeartRect.

###第2步:将心形区域裁剪出来, 裁剪之后,后续的绘制都只会显示在这个区域之内

(为了作图方便,我们通常先把坐标轴原点移动到 绘制区域的正中央)

    @Override
    protected void onDraw(Canvas canvas) {

        int width = getWidth();
        int height = getHeight();
        canvas.translate(width / 2, height / 2);//为了作图方便,我们通常先把坐标轴原点移动到 绘制区域的正中央
        ...省略无关代码

        canvas.clipPath(mMainPath);//裁剪心形区域
        canvas.save();//保存画布状态

        ...省略无关代码

    }

###第3步:绘制波浪区域

这里有两点细节
1)波浪区域分为两块,topbottom 上下两块
2) 整个波浪区域的长度为 心形矩形范围宽度的2
(?为什么是2倍?因为上面的波浪动画,其实是整个波浪区域平移造成的视觉效果,为了让这个动画可以无限执行,设计两倍宽度,当一半的宽度向右移动刚好触及心形矩形区域的右边框的时候,让它还原到原始位置,这样就能无缝衔接。)

######关键代码1 - 波浪path的构建

    /**
     * @param ifTop   是否是上部分; 上下部分的封口位置不一样
     * @param r       心形的矩形区域
     * @param process 当前进度值
     */
    private void resetWavePath(boolean ifTop, RectF r, float process, Path path) {
        final float width = r.width();
        final float height = r.width();

        path.reset();

        if (ifTop) {
            path.moveTo(r.left - width, r.top);
        } else {
            path.moveTo(r.left - width, r.bottom); //下部,初始位置点在 下
        }

        float waveHeight = height / 8f;//波动的最大幅度

        //找到矩形区域的左边线中点
        path.lineTo(r.left - width, r.bottom - height * process);

        //做两个周期的贝塞尔曲线
        for (int i = 0; i < 2; i++) {
            float px1, py1, px2, py2, px3, py3;

            px1 = width / 4;
            py1 = -waveHeight;

            px2 = width / 4 * 3;
            py2 = waveHeight;

            px3 = width;
            py3 = 0;

            path.rCubicTo(px1, py1, px2, py2, px3, py3);
        }
        if (ifTop) {
            path.lineTo(r.right, r.top);
        } else {
            path.lineTo(r.right, r.bottom);
        }
        path.close();

    }

######关键代码2- 属性动画 改变两个全局变量 波浪的向上增长系数 以及 横向波浪动画系数

    AnimatorSet animatorSet;
    // 动起来
    public void startAnimator() {

        if (animatorSet == null) {
            animatorSet = new AnimatorSet();
            ValueAnimator growAnimator = ValueAnimator.ofFloat(0f, 1f);
            growAnimator.addUpdateListener(animation -> growProcess = (float) animation.getAnimatedValue());
            growAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    animatorSet.cancel();
                }
            });
            growAnimator.setInterpolator(new DecelerateInterpolator());
            growAnimator.setDuration((long) (4000 / animatorSpeedCoefficient));

            ValueAnimator waveAnimator = ValueAnimator.ofFloat(0f, 1f);
            waveAnimator.setRepeatCount(ValueAnimator.INFINITE);
            waveAnimator.setRepeatMode(ValueAnimator.RESTART);
            waveAnimator.addUpdateListener(animation -> {
                waveProcess = (float) animation.getAnimatedValue();
                invalidate();
            });
            waveAnimator.setInterpolator(new LinearInterpolator());
            waveAnimator.setDuration((long) (1000 / animatorSpeedCoefficient));

            animatorSet.playTogether(growAnimator, waveAnimator);
            animatorSet.start();
        } else {
            animatorSet.cancel();
            animatorSet.start();
        }
    }

######关键代码3- 利用属性动画改变的全局变量,构建动态效果

    @Override
    protected void onDraw(Canvas canvas) {

        int width = getWidth();
        int height = getHeight();
        canvas.translate(width / 2, height / 2);//为了作图方便,我们通常先把坐标轴原点移动到 绘制区域的正中央
        curXOffset = waveProcess * mHeartRect.width();//当前X轴方向上 波浪偏移量

        canvas.clipPath(mMainPath);
        canvas.save();

        mainRect = new Rect();


# 最后

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

![image](https://img-blog.csdnimg.cn/img_convert/81e3d9ca5b8a9b4ec1a0f1fab4fd72be.png)

![image](https://img-blog.csdnimg.cn/img_convert/4e1c751cafc57423d9d34ec854d58f20.png)

**再免费分享一波[我的Java专题面试真题+视频学习详解+Java进阶学习书籍](https://gitee.com/vip204888/java-p7)**

与成长,其余的都不重要,希望读者们能谨记这一点。

[外链图片转存中...(img-cWeapLFl-1628441976159)]

[外链图片转存中...(img-2Xuo4MuU-1628441976162)]

**再免费分享一波[我的Java专题面试真题+视频学习详解+Java进阶学习书籍](https://gitee.com/vip204888/java-p7)**

其实面试这一块早在第一个说的25大面试专题就全都有的。以上提及的这些全部的面试+学习的各种笔记资料,我这差不多来回搞了三个多月,收集整理真的很不容易,其中还有很多自己的一些知识总结。正是因为很麻烦,所以对以上这些学习复习资料感兴趣,
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 Spring Boot 3 开发一个后端分离的生产级系统需要以下步骤: 第一步:环境准备 1. 安装 Java 开发工具包(JDK) 2. 安装集成开发环境(IDE),如Eclipse或IntelliJ IDEA 3. 安装Maven构建工具 4. 安装数据库(如MySQL)和相关工具(如MySQL Workbench) 第二步:创建后端项目 1. 使用IDE创建一个新的Spring Boot项目 2. 配置项目的基本信息,如项目名称、包名等 3. 添加必要的依赖,如Spring Boot Starter Web、Spring Data JPA等 4. 定义实体类、控制器、服务等后端代码 第三步:创建前端项目 1. 使用前端开发工具,如Vue.js或React.js,创建一个新的前端项目 2. 配置项目的基本信息,如项目名称、包名等 3. 定义前端路由、页面、组件等前端代码 第四步:前后端集成 1. 在后端项目中配置跨域访问,允许前端项目访问后端接口 2. 在前端项目中调用后端接口,实现数据的交互 第五步:开发和测试 1. 根据需求逐步开发后端和前端功能模块 2. 使用测试框架,如JUnit和Selenium,对系统进行单元测试和端到端测试 第六步:部署和上线 1. 打包后端项目为可执行的JAR文件 2. 部署JAR文件到生产环境的服务器上 3. 配置服务器的环境变量、数据库连接等 4. 启动服务器,验证系统是否正常运行 通过以上步骤,我们可以完成一个使用Spring Boot 3开发的前后端分离的生产级系统。这种架构可以提高开发效率、降低系统耦合性,并且适合大型项目的开发和部署。同时,我们还可以根据实际需求,进一步优化系统性能、可维护性和安全性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值