作者 :Samlss
博客 ://p1-tt.byteimg.com/origin/pgc-image/R6FfaWNAhIq4ye.jpg" style="width: 650px;">
自己实现比较容易,但是到了要出博客整理思路,总结要点的时候就挠头,不知云所以,所以最简单的还是 Read the fucking source code
思路
思路比较简单,整个view无非两样东西
云
雨滴
这里又包含两部分动画,一部分是云的左右移动动画,一部分是雨滴移动动画
那我们这里可以自定义一些属性,如果对自定义属性还不太了解的同学,搜下百度哈
<resources><declare-styleable name="RainyView"><!--雨滴的颜色--><attr name="raindrop_color" format="color"></attr><!--左边云的颜色--><attr name="left_cloud_color" format="color"></attr><!--右边云的颜色--><attr name="right_cloud_color" format="color"></attr><!-可同时存在的雨滴的最大数量--><attr name="raindrop_max_number" format="integer"></attr><!--每个雨滴之间创建的时间间隔--><attr name="raindrop_creation_interval" format="integer"></attr><!--每个雨滴的最小长度--><attr name="raindrop_min_length" format="integer"></attr><!--每个雨滴的最大长度--><attr name="raindrop_max_length" format="integer"></attr><!--雨滴的大小--><attr name="raindrop_size" format="integer"></attr><!--雨滴的最小移动速度--><attr name="raindrop_min_speed" format="float"></attr><!--雨滴的最大移动速度--><attr name="raindrop_max_speed" format="float"></attr><!--雨滴的斜率--><attr name="raindrop_slope" format="float"></attr></declare-styleable></resources>
画云
云怎么画?
云的形状不可胜举,我这里只实现了一种简单的形状:
那我们如何通过画笔将其画出来:
首先,我们先画底部,底部是一个圆角的矩形,通过下面方法绘制添加圆角矩形 path.addRoundRect(RectF rect, float rx, float ry, Direction dir)
在该圆角的矩形的基础上,再画两个圆,左边的为小圆,右边的为大圆,这样就产生了一个最简单的云的图形
在设置了以下代码之后
paint.setStyle(Paint.Style.FILL);
云的效果如下:
我们把这个云作为左边的云,那么右边的云怎么画?
很简单,因为我们这里用path来装载了这个云的路径,通过以下方法,
mComputeMatrix.preTranslate(rightCloudTranslateX, -calculateRect.height * (1 - CLOUD_SCALE_RATIO) / 2);mComputeMatrix.postScale(CLOUD_SCALE_RATIO, CLOUD_SCALE_RATIO, rightCloudCenterX, leftCloudEndY);mLeftCloudPath.transform(mComputeMatrix, mRightCloudPath);
将这个云的path移动,缩小,并将其路径转换到mRightCloudPath即可
在onDraw的时候,调用以下方法就可以描绘路径了
canvas.drawPath
接下来我们来实现云的动画,我们由上面已经了解到:
/*** Transform the points in this path by matrix, and write the answer* into dst. If dst is , then the the original path is modified.** @param matrix The matrix to apply to the path* @param dst The transformed path is written here. If dst is ,* then the the original path is modified*/public void transform(Matrix matrix, Path dst) {long dstNative = 0;if (dst != ) {dst.isSimplePath = false;dstNative = dst.mNativePath;}nTransform(mNativePath, matrix.native_instance, dstNative);}
该方法可以将一个path进行matrix转换,即矩阵转换,因此我们可以通过方法matrix.postTranslate来实现平移动画,即创建一个循环动画,通过postTranslate来设置动画值就可以了,这里左边的云在右边的云之上,因此先画右边的云。
mComputeMatrix.reset;mComputeMatrix.postTranslate((mMaxTranslationX / 2) * mRightCloudAnimatorValue, 0);mRightCloudPath.transform(mComputeMatrix, mComputePath);canvas.drawPath(mComputePath, mRightCloudPaint);mComputeMatrix.reset;mComputeMatrix.postTranslate(mMaxTranslationX * mLeftCloudAnimatorValue, 0);mLeftCloudPath.transform(mComputeMatrix, mComputePath);canvas.drawPath(mComputePath, mLeftCloudPaint);
画雨滴
首先我们要知道一点是,所有的雨滴都是随机产生的,而产生的值,可以根据上面的自定义属性指定,也可以使用自定义的值,我们先定义一个雨滴类
private class RainDrop{float speedX; //雨滴x轴移动速度float speedY; //雨滴y轴移动速度float xLength; //雨滴的x轴长度float yLength; //雨滴的y轴长度float x; //雨滴的x轴坐标float y; //雨滴的y轴坐标float slope; //雨滴的斜率}
关于上面参数,这里画张图来示例:
关于斜率
我这里开放了一个设置斜率的接口,代表雨滴的一个倾斜度,可以看到下图的雨滴都是倾斜的,就是通过斜率来设置这个倾斜度
斜率:表示一条直线(或曲线的切线)关于(横)坐标轴倾斜程度的量。它通常用直线(或曲线的切线)与(横)坐标轴夹角的正切,或两点的纵坐标之差与横坐标之差的比来表示。
该直线的斜率为k=(y1-y2)/(x1-x2)
我这里使用了固定的斜率,使所有的雨滴方向一致,如果想将其改为随机值的同学,可以下载源码自行修改。
在创建雨滴对象的时候,以下步骤使我们需要做的:
斜率赋值(我这里是指定的,因此不用计算随机斜率)
计算x轴、y轴移动速度随机值
计算雨滴长度随机值(同时计算x轴,y轴长度值)
计算x,y坐标随机值(为了营造雨滴更好的出场效果,这里设置了y轴的起点坐标为y-雨滴y轴长度)
创建雨滴对象后,我们有了想要的参数,我们可以canvas.drawLine来画雨滴
canvas.drawLine(rainDrop.x, rainDrop.y,rainDrop.slope > 0 ? rainDrop.x + rainDrop.xLength : rainDrop.x - rainDrop.xLength,rainDrop.y + rainDrop.yLength,mRainPaint);
这里需要注意以下,为什么canvas.drawLine中的stopX参数要设置为
rainDrop.slope > 0 ? rainDrop.x + rainDrop.xLength : rainDrop.x - rainDrop.xLength
这是因为,我们的雨滴是一直往下移动即y是增加的,我们上面知道斜率公式为:k=(y1-y2)/(x1-x2)
即y1-y2肯定是大于0的,因此
当斜率小于0的时候,雨滴是这样的,即x1-x2 < 0
当斜率大于0的时候,雨滴是这样的,即x1-x2 > 0
雨滴动画,由于每一个雨滴对象参数已经定义,在进行动画的时候,只需要根据速度,设置x、y轴的下一个点的坐标就行了
if (rainDrop.slope >= 0) {rainDrop.x += rainDrop.speedX;}else{rainDrop.x -= rainDrop.speedX;}rainDrop.y += rainDrop.speedY;
优化
我们知道,在频繁的创建雨滴的时候,如果每次都创建新对象的话, 可能会增加不必要的内存使用,而且很容易引起频繁的gc,甚至是内存抖动。
因此这里我增加了一个回收功能
//首先判断栈中是否存在回收的对象,若存在,则直接复用,若不存在,则创建一个新的对象private RainDrop obtainRainDrop{if (mRecycler.isEmpty){return new RainDrop;}return mRecycler.pop;}//回收到一个栈里面,若这个栈数量超过最大可显示数量,则popprivate void recycle(RainDrop rainDrop){if (rainDrop == ){return;}if (mRecycler.size >= mRainDropMaxNumber){mRecycler.pop;}mRecycler.push(rainDrop);}
欢迎Github follow,star以表激励。//p1-tt.byteimg.com/origin/pgc-image/R8mL1mJDazbAIk.jpg" style="width: 650px;">
近期文章:
等等,先别走!「码个蛋」又有活动了!参与活动不但可以培养自己的好习惯,还能拿到「码个蛋」IP系列专属奖品,速度要快...
今日问题:
说说你第一次接触Android自定义view的感觉?
留言格式:
打卡x 天,答:xxx。
告诉你一个小技巧:
只需3步,你将不会错过任何一篇文章!
版权声明:CosMeDna所有作品(图文、音视频)均由用户自行上传分享,仅供网友学习交流。若您的权利被侵害,请联系删除!
本文链接://www.cosmedna.com/article/752127219.html