内容简介:前天的浏览 GitHub 时发现一个模仿 Gif 的 Loading 特效的项目,感觉效果很不错,也比较有创意,如下:GitHub 上好几个做这个效果的项目,但是很少有完全实现的,有的还有 Bug,于是花了 2 天实现了一下。效果如下:
前天的浏览 GitHub 时发现一个模仿 Gif 的 Loading 特效的项目,感觉效果很不错,也比较有创意,如下:
GitHub 上好几个做这个效果的项目,但是很少有完全实现的,有的还有 Bug,于是花了 2 天实现了一下。
效果如下:
GitHub 项目在这里 LeavesLoading
2. 分析
实现要求:
- 叶子
- 随机产生
- 飘动轨迹为正弦函数,并且随机振幅
- 飘动时伴随自旋转,更符合物理规律
- 遇到进度条似乎是融入的
- 风扇
- 可旋转
- Loading == 100% 时显示一个动画
- 细节
- 风扇和叶子自适应 View 大小
- 叶子在视觉上不能飘出 RountRect 边界
3. 核心实现
3.1 随机产生叶子
本质是事先产生一定数量叶子,这些叶子的漂动时的振幅、相位、旋转方向等等都是随机的,并且飘动是周期性地即叶子飘动到最左边时,又重新回到最右边。
Leaf 类:
private class Leaf{ float x,y;//坐标 AmplitudeType type;//叶子飘动振幅 int rotateAngle;//旋转角度 RotateDir rotateDir;//旋转方向 long startTime;//起始时间 int n;//初始相位 } 复制代码
Leaf 生成方法:
Leaf generateLeaf(){ Leaf leaf = new Leaf(); //随机振幅 int randomType = mRandom.nextInt(3); switch (randomType){ case 0: //小振幅 leaf.type = AmplitudeType.LITTLE; break; case 1: //中等振幅 leaf.type = AmplitudeType.MIDDLE; break; default: //大振幅 leaf.type = AmplitudeType.BIG; break; } //随机旋转方向 int dir = mRandom.nextInt(2); switch (dir){ case 0: //逆时针 leaf.rotateDir = RotateDir.ANTICLOCKWISE; break; default: //顺时针 leaf.rotateDir = RotateDir.CLOCKWISE; break; } //随机起始角度 leaf.rotateAngle = mRandom.nextInt(360); leaf.n = mRandom.nextInt(20); mAddTime += mRandom.nextInt((int)mLeafFloatTime); leaf.startTime = System.currentTimeMillis() + mAddTime; return leaf; } 复制代码
3.2 叶子飘动轨迹为正弦函数
确定 Leaf 在某个时刻的坐标 ( x , y ):
/** * 获取叶子的(x,y)位置 * @param leaf 叶子 * @param currentTime 当前时间 */ private void getLeafLocation(Leaf leaf,long currentTime){ long intervalTime = currentTime - leaf.startTime;//飘动时长 if (intervalTime <= 0){ // 此 Leaf 还没到飘动时间 return; }else if (intervalTime > mLeafFloatTime){ // Leaf 的飘动时间大于指定的飘动时间,即叶子飘动到了最左边,应回到最右边 leaf.startTime = currentTime + new Random().nextInt((int)mLeafFloatTime); } // 计算移动因子 float fraction = (float) intervalTime / mLeafFloatTime; leaf.x = (1-fraction)*mProgressLen; leaf.y = getLeafLocationY(leaf); if (leaf.x <= mYellowOvalHeight / 4){ //叶子飘到最左边,有可能会超出 RoundRect 边界,所以提前特殊处理 leaf.startTime = currentTime + new Random().nextInt((int)mLeafFloatTime); leaf.x = mProgressLen; leaf.y = getLeafLocationY(leaf); } } 复制代码
要想让 Leaf 飘动轨迹为正弦函数,关键在于确定 Leaf 的 Y 轴坐标:
/** * 获取叶子的Y轴坐标 * @param leaf 叶子 * @return 经过计算的叶子Y轴坐标 */ private float getLeafLocationY(Leaf leaf){ float w = (float) (Math.PI * 2 / mProgressLen);//角频率 float A;//计算振幅值 switch (leaf.type){ case LITTLE: A = mLeafLen/3; break; case MIDDLE: A = mLeafLen*2/3; break; default: A = mLeafLen; break; } // (mHeight-mLeafLen)/2 是为了让 Leaf 的Y轴起始位置居中 return (float) (A * Math.sin(w * leaf.x + leaf.n)+(mHeight-mLeafLen)/2); } 复制代码
3.3 叶子飘动时自旋转
这里就涉及到了 Leaf 的绘制,其实 Gif 中的叶子和风扇都可以使用 Canves 直接绘制图案,但是这样就会有两个问题:
- 难画:想要画出满意图形,并且还要旋转、缩放、平移可要下一番功夫。
- 灵活性低:如果想换其他样式又得重新设计绘制过程。
因此这里采用 Canves.drawBitmap()
的方式绘制,直接使用已有的图片作为叶子和风扇,同时利用 Canves.drawBitmap()
的一个重载的方法可以很方便的实现旋转、缩放、平移:
void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) ; 复制代码
就是通过这里的 Matrix 矩阵,它内部封装了 postScale()
、 postTranslate
、 postRotate()
等方法,可以帮助我们快速的对 Bitmap 进行旋转、缩放、平移还有其他操作。使用时要记得配合 Canves 的 save()
和 restore()
使用,否则达不到想要的效果。
对这方面不熟的朋友可以看看 HenCoder 的自定义 View 教学 1-4 。
绘制 Leaf 的方法:
private void drawLeaves(Canvas canvas){ long currentTime = System.currentTimeMillis(); for (Leaf leaf : mLeafList) { if (currentTime > leaf.startTime && leaf.startTime != 0){ // 获取 leaf 当前的坐标 getLeafLocation(leaf,currentTime); canvas.save(); Matrix matrix = new Matrix(); // 缩放 自适应 View 的大小 float scaleX = (float) mLeafLen / mLeafBitmapWidth; float scaleY = (float) mLeafLen / mLeafBitmapHeight; matrix.postScale(scaleX,scaleY); // 位移 float transX = leaf.x; float transY = leaf.y; matrix.postTranslate(transX,transY); // 旋转 // 计算旋转因子 float rotateFraction = ((currentTime - leaf.startTime) % mLeafRotateTime) /(float)mLeafRotateTime; float rotate; switch (leaf.rotateDir){ case CLOCKWISE: //顺时针 rotate = rotateFraction * 360 + leaf.rotateAngle; break; default: //逆时针 rotate = -rotateFraction * 360 + leaf.rotateAngle; break; } // 旋转中心选择 Leaf 的中心坐标 matrix.postRotate(rotate,transX + mLeafLen / 2,transY + mLeafLen / 2); canvas.drawBitmap(mLeafBitmap,matrix,mBitmapPaint); canvas.restore(); } } 复制代码
3.4 Loading == 100% 出现动画
增加一个判断字段 isLoadingCompleted ,在 onDraw()
中选择对应绘制策略。
isLoadingCompleted 在 setProgress()
中根据 progress 设置:
/** * 设置进度(自动刷新) * @param progress 0-100 */ public void setProgress(int progress){ if (progress < 0){ mProgress = 0; }else if (progress > 100){ mProgress = 100; }else { mProgress = progress; } if (progress == 100){ isLoadingCompleted = true; }else { isLoadingCompleted = false; } // 255 不透明 mCompletedFanPaint.setAlpha(255); postInvalidate(); } 复制代码
LeavesLoading.onDraw()
部分实现:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); ...... if (isLoadingCompleted){ //绘制加载完成特效 drawCompleted(canvas); }else { //绘制扇叶 drawFan(canvas,mFanLen,mBitmapPaint); } //刷新 postInvalidate(); } 复制代码
drawCompleted()
实现:
private void drawCompleted(Canvas canvas) { // 每次绘制风扇透明度递减10 int alpha = mCompletedFanPaint.getAlpha() - 10; if (alpha <= 0){ alpha = 0; } mCompletedFanPaint.setAlpha(alpha); // 文字透明度刚好与风扇相反 mCompletedTextPaint.setAlpha(255-alpha); // 计算透明因子 float fraction = alpha / 255f; // 叶片大小 和 文字大小 也是相反变化的 float fanLen = fraction * mFanLen; float textSize = (1 - fraction) * mCompletedTextSize; mCompletedTextPaint.setTextSize(textSize); //测量文字占用空间 Rect bounds = new Rect(); mCompletedTextPaint.getTextBounds( LOADING_COMPLETED, 0, LOADING_COMPLETED.length(), bounds); // 与 drawLeaf() 相似,不再赘述 drawFan(canvas, (int) fanLen, mCompletedFanPaint); //画文字 canvas.drawText( LOADING_COMPLETED, 0, LOADING_COMPLETED.length(), mFanCx-bounds.width()/2f, mFanCy+bounds.height()/2f, mCompletedTextPaint); } 复制代码
流程:计算风扇和文字透明度 -> 计算风扇和文字大小以及文字占用空间 -> 绘制 ,注释写得比较清楚就不赘述了。
以上所述就是小编给大家介绍的《Android 自定义 View 之 LeavesLoading》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Android 自定义 View (04自定义属性)
- Vue自定义组件(简单实现一个自定义组件)
- Android 自定义View:深入理解自定义属性(七)
- Qt编写自定义控件20-自定义饼图 原 荐
- SpringBoot2 | SpringBoot自定义AutoConfiguration | SpringBoot自定义starter(五)
- 『互联网架构』软件架构-springboot自定义视图和自定义Starter(90)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。