前天的浏览 GitHub 时发现一个模仿 Gif 的 Loading 特效的项目,感受效果很不错,也比较有创意,以下:java
GitHub 上好几个作这个效果的项目,可是不多有彻底实现的,有的还有 Bug,因而花了 2 天实现了一下。git
效果以下:github
GitHub 项目在这里 LeavesLoadingcanvas
实现要求:segmentfault
叶子dom
风扇ide
细节函数
本质是事先产生必定数量叶子,这些叶子的漂动时的振幅、相位、旋转方向等等都是随机的,而且飘动是周期性地即叶子飘动到最左边时,又从新回到最右边。post
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; }
肯定 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); }
这里就涉及到了 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(); } }
增长一个判断字段 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); }
流程:计算风扇和文字透明度 -> 计算风扇和文字大小以及文字占用空间 -> 绘制 ,风扇逐渐变透明和变小,文字逐渐变清晰和变大,注释写得比较清楚就不赘述了。
文章中若有出现任何错误,欢迎你们到评论区留言指正。
若是以为 LeavesLoading 对您有任何帮助,但愿能够在 GitHub 获得您的 Star !
Thanks: