开始前先作个热身( ˘•灬•˘ )git
本身实现比较容易,可是到了要出博客整理思路,总结要点的时候就挠头,不知云因此,因此最简单的仍是 Read the fucking source codegithub
若是对安卓UI有兴趣的朋友能够加我好友互相探讨,这里有不少自定义view能够参考canvas
思路比较简单,整个view无非两样东西bash
这里又包含两部分动画,一部分是云的左右移动动画,一部分是雨滴移动动画 那咱们这里能够自定义一些属性,若是对自定义属性还不太了解的同窗,搜下百度哈app
<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>
复制代码
云怎么画?post
云的形状不可胜举,我这里只实现了一种简单的形状: 优化
那咱们如何经过画笔将其画出来:动画
1.首先,咱们先画底部,底部是一个圆角的矩形,经过下面方法绘制添加圆角矩形 path.addRoundRect(RectF rect, float rx, float ry, Direction dir)
ui
2.在该圆角的矩形的基础上,再画两个圆,左边的为小圆,右边的为大圆,这样就产生了一个最简单的云的图形, this
在设置了如下代码以后
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 null, 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 null,
* then the the original path is modified
*/
public void transform(Matrix matrix, Path dst) {
long dstNative = 0;
if (dst != null) {
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; //雨滴的斜率
}
复制代码
关于上面参数,这里画张图来示例:
关于斜率 我这里开放了一个设置斜率的接口,表明雨滴的一个倾斜度,能够看到下图的雨滴都是倾斜的,就是经过斜率来设置这个倾斜度
斜率:表示一条直线(或曲线的切线)关于(横)坐标轴倾斜程度的量。它一般用直线(或曲线的切线)与(横)坐标轴夹角的正切,或两点的纵坐标之差与横坐标之差的比来表示。
我这里使用了固定的斜率,使全部的雨滴方向一致,若是想将其改成随机值的同窗,能够下载源码自行修改。
在建立雨滴对象的时候,如下步骤使咱们须要作的:
建立雨滴对象后,咱们有了想要的参数,咱们能够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();
}
//回收到一个栈里面,若这个栈数量超过最大可显示数量,则pop
private void recycle(RainDrop rainDrop){
if (rainDrop == null){
return;
}
if (mRecycler.size() >= mRainDropMaxNumber){
mRecycler.pop();
}
mRecycler.push(rainDrop);
}
复制代码
开源不易,请尊重做者劳动,转载注明出处
欢迎Github follow,star以表激励。