博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 自定义View之烧瓶loading动画
阅读量:6259 次
发布时间:2019-06-22

本文共 5517 字,大约阅读时间需要 18 分钟。

我们首先看下效果

画瓶子

首先,创建一个自定义view,我们知道,在view的大小发生改变后,会回调接口

/*** This is called during layout when the size of this view has changed. If* you were just added to the view hierarchy, you're called with the old* values of 0.** @param w Current width of this view.* @param h Current height of this view.* @param oldw Old width of this view.* @param oldh Old height of this view.*/protected void onSizeChanged(int w, int h, int oldw, int oldh) {}复制代码

因此,我们可以在该方法里面,取得view的宽高后进行瓶子的初始化,大概的思路是:

  • 计算瓶底圆半径大小
  • 计算瓶颈高度大小
  • 计算瓶盖高度大小
  • path添加瓶底圆轨迹
  • path添加瓶颈轨迹
  • path添加瓶盖轨迹

在Android中,默认的0°为数学上圆的90°,这里不明白的请百度

关于瓶底扇形圆弧,这里测试得出取-70° 到 250°,即瓶底圆和屏颈相交的两个点,为比较美观的,因此这里取了这个角度。

关于path.addArc,首先这里的参数代表

  • oval 圆弧形状边界,可以当做是一个矩形边界
  • startAngle 起始角度
  • sweepAngle 旋转角度(注意,这里不是最终的角度,而是要旋转的角度)
/*** Add the specified arc to the path as a new contour.** @param oval The bounds of oval defining the shape and size of the arc* @param startAngle Starting angle (in degrees) where the arc begins* @param sweepAngle Sweep angle (in degrees) measured clockwise*/public void addArc(RectF oval, float startAngle, float sweepAngle) {    addArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle);}复制代码

详细可查看以下代码注释

//获取view的中心点float centerX = w / 2;float centerY = h / 2;//瓶底圆半径为view的宽度的1/5float flaskBottomCircleRadius = w / 5f;//瓶颈高度为半径的2/3float neckHeight = flaskBottomCircleRadius * 2f / 3;//瓶盖高度为瓶颈高度的3/10float headHeight = 0.3f * neckHeight;//重置pathmFlaskPath.reset();//计算瓶子在view中的中心点y坐标float flaskCenterY = centerY + (neckHeight + headHeight) / 2;//**********************************************************瓶底部分******************************************************//瓶底和瓶颈的左边和右边的相交的两个点的坐标float[] leftEndPos = new float[2];float[] rightEndPos = new float[2];//瓶底圆底部点的坐标float[] bottomPos = new float[2];//计算三个点的坐标leftEndPos[0] = (float) (flaskBottomCircleRadius * Math.cos(250 * Math.PI / 180f) + centerX);leftEndPos[1] = (float) (flaskBottomCircleRadius * Math.sin(250 * Math.PI / 180f) + flaskCenterY);rightEndPos[0] = (float) (flaskBottomCircleRadius * Math.cos(-70 * Math.PI / 180f) + centerX);rightEndPos[1] = (float) (flaskBottomCircleRadius * Math.sin(-70 * Math.PI / 180f) + flaskCenterY);bottomPos[0] = (float) (flaskBottomCircleRadius * Math.cos(90 * Math.PI / 180f) + centerX);bottomPos[1] = (float) (flaskBottomCircleRadius * Math.sin(90 * Math.PI / 180f) + flaskCenterY);//计算出圆弧所在的区域RectF flaskArcRect = new RectF(centerX - flaskBottomCircleRadius, flaskCenterY - flaskBottomCircleRadius,centerX + flaskBottomCircleRadius, flaskCenterY + flaskBottomCircleRadius);//添加底部圆弧轨迹mFlaskPath.addArc(flaskArcRect, -70, 320);//***********************************************************************************************************************//首先将path移至左边相交点mFlaskPath.moveTo(leftEndPos[0], leftEndPos[1]);//添加左边的瓶颈线mFlaskPath.lineTo(leftEndPos[0], leftEndPos[1] - neckHeight);//通过贝塞尔曲线添加左边瓶盖轨迹mFlaskPath.quadTo(leftEndPos[0] - flaskBottomCircleRadius / 8, leftEndPos[1] - neckHeight - headHeight / 2,    leftEndPos[0], leftEndPos[1] - neckHeight - headHeight);//移动至右边瓶盖定点mFlaskPath.lineTo(rightEndPos[0],rightEndPos[1] - neckHeight - headHeight);//通过贝塞尔曲线添加右边瓶盖轨迹mFlaskPath.quadTo(rightEndPos[0] + flaskBottomCircleRadius / 8, rightEndPos[1] - neckHeight - headHeight / 2,    rightEndPos[0], rightEndPos[1] - neckHeight);//添加右边的瓶颈线mFlaskPath.lineTo(rightEndPos[0], rightEndPos[1]);复制代码

View的onDraw中描绘瓶子

canvas.drawPath(mFlaskPath, mStrokePaint);复制代码

画水位

根据以上代码,我们已经计算获得了整个瓶子的path,那么我们如何去计算和画水位呢?

  • 计算瓶子path所占的区域
  • 对整个瓶子的path进行canvas裁剪

我们可以通过path.computeBounds()计算出瓶子所占的整个区域

mFlaskPath.computeBounds(mFlaskBoundRect, false);mFlaskBoundRect.bottom -= (mFlaskBoundRect.bottom - bottomPos[1]);复制代码

但是我们这里为什么还要减去一个差值呢?

这是因为,path.addArc()后,如果圆被截断即addArc的并不是一个完整的圆(我们这里瓶底就是一个弧度圆,瓶底与瓶颈之间的交点使瓶底圆截断),会导致path.computeBounds()计算出来的区域多出来一定的空间,这里贴两张示例图:

以下为不减去该差值的效果:

以下为减去该差值的效果:

计算出瓶子的区域后,我们就可以获取水位的区域了

mWaterRect.set(mFlaskBoundRect.left, mFlaskBoundRect.bottom - mFlaskBoundRect.height() * mWaterHeightPercent,mFlaskBoundRect.right, mFlaskBoundRect.bottom);复制代码

利用canvas的裁剪功能,进行水位的绘制

//裁剪整个瓶子的画布canvas.clipPath(mFlaskPath);//画水位canvas.drawRect(mWaterRect, mWaterPaint);复制代码

画水泡

水泡生成和描绘的思路

  • 根据水位区域,在水位底部,随机产生水泡
  • 产生水泡后,将该水泡记录下来,并且根据一个speed进行位移
  • 当水泡离开水位区域,将其在记录中移除
private void createBubble() {	//若水泡数量达到上限或者水位区域为空的时候,不产生水泡	if (mBubbles.size() >= mBubbleMaxNumber	    || mWaterRect.isEmpty()) {	    return;	}		//根据时间间隔,判断是否已到达水泡产生的时间	long current = System.currentTimeMillis();	if ((current - mBubbleCreationTime) < mBubbleCreationInterval){	    return;	}		mBubbleCreationTime = current;		//以下代码为随机计算水泡坐标 + 半径+ 速度	Bubble bubble = obtainBubble();	int radius = mBubbleMinRadius + mOnlyRandom.nextInt(mBubbleMaxRadius - mBubbleMinRadius);		bubble.radius = radius;	bubble.speed = mBubbleMinSpeed + mOnlyRandom.nextFloat() * mBubbleMaxSpeed;		bubble.x = mWaterRect.left + mOnlyRandom.nextInt((int) mWaterRect.width()); //random x coordinate	bubble.y = mWaterRect.bottom - radius - mStrokeWidth / 2; //the fixed y coordinate		mBubbles.add(bubble);}复制代码

利用canvas的裁剪功能,进行水泡的绘制

//裁剪水位画布canvas.clipRect(mWaterRect);//描绘水泡drawBubbles(canvas);复制代码

优化

我们知道,在频繁的创建水泡的时候,如果每次都创建新对象的话, 可能会增加不必要的内存使用,而且很容易引起频繁的gc,甚至是内存抖动。

因此这里我增加了一个回收功能

//首先判断栈中是否存在回收的对象,若存在,则直接复用,若不存在,则创建一个新的对象private Bubble obtainBubble(){	if (mRecycler.isEmpty()){	     return new Bubble();	}		return mRecycler.pop();}//回收到一个栈里面,若这个栈数量超过最大可显示数量,则popprivate void recycle(Bubble bubble){	if (bubble == null){	    return;	}		if (mRecycler.size() >= mBubbleMaxNumber){	    mRecycler.pop();	}		mRecycler.push(bubble);}复制代码

转载地址:http://pxasa.baihongyu.com/

你可能感兴趣的文章
转载——c++中冒号(:)和双冒号(::)的用法
查看>>
使用CSS3改变文本选中的默认颜色
查看>>
【leetcode】560. Subarray Sum Equals K
查看>>
使printf打印信息带有颜色
查看>>
【Android 开发】Android Studio 出现 unable to access android sdk add-on list 的解决方法
查看>>
Linux中磁盘分区——理论篇
查看>>
50道面试题收集(非常清晰)
查看>>
python使用HTMLTestRunner.py生成测试报告
查看>>
Mac下Java JNI (java native interface)调C
查看>>
【BZOJ】2125: 最短路 圆方树(静态仙人掌)
查看>>
Dropout 上
查看>>
leetcode-48-旋转图像
查看>>
水两道搜索
查看>>
2.1.4 Python单例模式
查看>>
java基础_线程
查看>>
React框架Umi实战(2)整合dva开发后台管理系统
查看>>
华为机试题——数组排序,且奇数存在奇数位置,偶数存在偶数位置
查看>>
iOS12-cocoapods引入包时No such module 'xxx'
查看>>
register ASP.NET An error has occurred: 0x8007b799(转)
查看>>
SpringCloud(十二):SringCloud Config-配置Git仓库详解
查看>>