手把手讲解-一个复杂动效的自定义绘制

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();
… 省略无关代码

// 上波浪区域
resetWavePath(true, mHeartRect, growProcess, topWavePath);
canvas.translate(curXOffset, 0);
canvas.clipPath(topWavePath);
canvas.drawPath(topWavePath, mTopPaint);
… 省略无关代码

//下波浪区域
resetWavePath(false, mHeartRect, growProcess, bottomWavePath);
canvas.restore();
canvas.translate(curXOffset, 0);
canvas.clipPath(bottomWavePath);
canvas.drawPath(bottomWavePath, mBottomPaint);
… 省略无关代码

}


###第4步:绘制”一条大灰狼“ 到心形中央,并且达成双色效果

这里有两个细节:
1.canvas.drawText, 就算你把paint 设置了.setTextAlign(Paint.Align.CENTER); 它也未必会在你给的x,y 为中心 绘制。原因就不解释了,谷歌大佬就是这么设计的。
解决方法:利用paint.getTextBounds,获得文字的矩形区域。然后在真正canvas.drawText,计算y的时候考虑这个矩形区域,就像下面这样如下

mainRect = new Rect();
textBottomPaint.getTextBounds(text, 0, text.length(), mainRect);

2. 由于之前波浪的横向移动,坐标轴产生了平移,所以我绘制文字,要将平移的距离减去,再绘制,保证居中,且文字位置不随着波浪的横向移动而变化。

完整代码如下(此步骤的关键代码已经标红):

image.png

#结语
来解答乍一看里面提出的3个问题:

诶?心形是怎么绘制的?
答:构建Path,然后canvas.clipPath裁剪画布,裁剪之后,所有的作图效果就只在这个心形区域内可见

诶?波浪是怎么画出来的,又是如何动起来的?
答:波浪,或者说波浪区域,也是Path构建,主要由一根波浪线以及三根直线组成,是一个封闭区域.
让波浪动起来,其实就是 canvas平移操作,利用属性动画+双倍宽度的波浪区域,形成无缝无限循环动画.

诶? 文字是怎么呈现出同一时刻的两种颜色的?
答:在两个相邻的波浪区域,使用不一样的颜色绘制两次文字。视觉效果上还是一串文字,但是实际上是两次绘制的组合效果。神奇吗?神奇个屁,其实就是 同一位置绘制两次文字,后面的覆盖前面的…话粗理不粗- -!

话题延伸

要想随心所欲地掌控自定义View,需要有完整的知识体系。

  • view的树形结构概念
  • 测量,布局,绘制流程
  • 事件分发/滑动冲突核心原理
  • Canvas Paint Path 绘制常用api
  • Bitmap 位图
  • 属性动画
  • 如果与系统的某些View发生交互,还有可能需要你了解系统源码

但是要想随心所欲地使用自定义View,仅仅如此还不够,还需要: 良好的数学基础

因为大部分的不规则图形,可能都需要数学公式思想的辅助,像是:

  • 心形path的构建

  • 无限波浪的设计思路

  • 后续文章将会 提到的贝塞尔曲线的使用

都离不开多年前数学课上的时候养成的数学思维,如果数学基础比较糟糕,做起这些特效,往往会比较困难.

现在都说互联网寒冬,其实只要自身技术能力够强,咱们就不怕!我这边专门针对Android开发工程师整理了一套【Android进阶学习视频】、【全套Android面试秘籍】、【Android知识点PDF】。如有需要获取资料文档的朋友,可以点击我的GitHub免费获取!

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

都说三年是程序员的一个坎,能否晋升或者提高自己的核心竞争力,这几年就十分关键。

技术发展的这么快,从哪些方面开始学习,才能达到高级工程师水平,最后进阶到Android架构师/技术专家?我总结了这 5大块;

我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 18
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值