写点什么

手把手讲解 - 一个复杂动效的自定义绘制,2021Android 开发社招面试总结

用户头像
Android架构
关注
发布于: 50 分钟前

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步:将心形区域裁剪出来, 裁剪之后,后续的绘制都只会显示在这个区域之内


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


@Overrideprotected 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() {@Overridepublic 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- 利用属性动画改变的全局变量,构建动态效果


@Overrideprotected void onDraw(Canvas canvas) {


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


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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


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



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


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

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
手把手讲解-一个复杂动效的自定义绘制,2021Android开发社招面试总结