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

/**

  • 构建心形
  • 注意,它这个是以 矩形区域中心点为基准的图形,所以绘制的时候,必须先把坐标轴移动到 区域中心
    */
    private void initHeartPath(Path path) {
    List 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,Pathpath) {
    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);

最后

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

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

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

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门**

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

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

  • 15
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值