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)波浪区域分为两块,top
和bottom
上下两块
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大面试专题就全都有的。以上提及的这些全部的面试+学习的各种笔记资料,我这差不多来回搞了三个多月,收集整理真的很不容易,其中还有很多自己的一些知识总结。正是因为很麻烦,所以对以上这些学习复习资料感兴趣,