效果如下
1.動畫分析:
根據複雜動畫分解成簡單的動畫
上面的兩個動畫可以分解成最外層擴展動畫以及裏面六邊形動畫
最外層動畫分解:
一個六邊形從中心出發到外邊,然後到另一個角,然後收回。裏面的動畫可以遮擋外邊的六邊形,最裏面的六邊形路徑動畫被裏面六邊形黑色背景遮擋。
中心六邊形動畫:
可以分成4層,最下層爲六邊形半透明背景,第二層爲風扇或者粒子,第三層爲六邊形掃描動畫,最上層爲百分比
2.動畫實現
電量
方案1:使用圖片 計算文字位置,通過繪製Bitmap實現 優點:可以實現奇特文字效果
方案2:繪製文字 計算文字位置,繪製文字 優點:1.修改方便。2.佔用內存小。
選用方案二
String levelStr = String.valueOf(mBatteryLevel);
Rect levelRect = new Rect();
mBatteryPaint.setTextSize(mBatteryTextSize);
mBatteryPaint.getTextBounds(levelStr, 0, levelStr.length(), levelRect);
Rect percentRect = new Rect();
mBatteryPaint.setTextSize(mBatteryTextSize/2);
mBatteryPaint.getTextBounds(mPercentStr, 0, mPercentStr.length(), percentRect);
float space = mBatteryTextSize/5;
float drawX = mCenter.x-(levelRect.width()+percentRect.width()+space)/2f;
float drawY = mCenter.y+levelRect.height()/2f;
mBatteryPaint.setTextSize(mBatteryTextSize);
canvas.drawText(levelStr,drawX, drawY, mBatteryPaint);
mBatteryPaint.setTextSize(mBatteryTextSize/2);
canvas.drawText(mPercentStr,drawX+levelRect.width()+space, drawY, mBatteryPaint);
六邊形背景
方案1:使用圖片 計算六邊形位置,通過繪製Bitmap實現 優點:只需要計算圖片位置,實現快
方案2:繪製六邊形路徑 計算六邊形的頂點,根據頂點繪製實心六邊形 優點:1.修改方便。2.佔用內存小。
六邊形頂點位置計算如下
六邊形背景頂點路徑代碼如下:
final PointF regularHexagonBg = new PointF(mCenter.x+mRadius*mBackgroundRatio, mCenter.y);
for(int index=0;index<POLYGON_COUNT;index++){
if(index==0){
mRegularHexagonBgPath.reset();
mRegularHexagonBgPath.moveTo(startBgPoint.x, startBgPoint.y);
}
PointF point = Util.rotatePoint(regularHexagonBg, mCenter, START_ANGLE+ANGLE*index);
mRegularHexagonBgPath.lineTo(point.x, point.y);
if(index==POLYGON_COUNT-1){
mRegularHexagonBgPath.lineTo(startBgPoint.x, startBgPoint.y);
mRegularHexagonBgPath.close();
}
}
六邊形掃描動畫
方案1:使用圖片 提供掃描背景圖片和蒙版圖片,旋轉背景圖片+蒙版實現 優點:只需要計算圖片位置和旋轉角度,實現快 方案2:繪製六邊形路徑 使用SweepGradient+CornerPathEffect的畫筆繪製路徑 優點:1.修改方便。2.佔用內存小。
定義SweepGradient+CornerPathEffect的畫筆
private final int[] mSweepColors = new int[]{
Color.argb(255, 22, 72, 126),
Color.argb(255, 26, 109, 189),
Color.argb(255, 25, 157, 238),
Color.argb(255, 9, 223, 235),
Color.argb(255, 23, 223, 205),
Color.argb(255, 31, 223, 175),
Color.argb(255, 40, 223, 145),
};
mRegularHexagonGradient = new SweepGradient(mCenter.x, mCenter.y, mSweepColors, null);
mRegularHexagonPaint.setShader(mRegularHexagonGradient);
mRegularHexagonPaint.setPathEffect(new CornerPathEffect(mRadius/10));
根據進度旋轉SweepGradient然後繪製路徑
mRegularHexagonMatrix.reset();
mRegularHexagonMatrix.setRotate(mScanProgress*360, mCenter.x, mCenter.y);
mRegularHexagonGradient.setLocalMatrix(mRegularHexagonMatrix);
canvas.drawPath(mRegularHexagonPath, mRegularHexagonPaint);
風扇動畫
方案1:使用圖片 提供風扇圖片,繪製旋轉的風扇圖片實現 優點:只需要計算圖片位置和旋轉角度,實現複雜度小
方案2:繪製風扇葉路徑 使用LinearGradient的畫筆繪製風扇葉路徑 優點:1.修改方便。2.佔用內存小。
LinearGradient的畫筆定義
mInitAngle = mRandom.nextInt(360);
mRegularHexagon = new PointF(mCenter.x+mRadius, mCenter.y);
mGradient = new LinearGradient(mCenter.x, mCenter.y,
mCenter.x, mCenter.y-mRegularHexagonMinRadius,
new int[]{Color.argb(150, 0, 255, 185), Color.argb(0, 255, 255, 255)}
, new float[]{0, 0.85f}, Shader.TileMode.CLAMP);
mAirFanPaint.setDither(true);
mAirFanPaint.setAntiAlias(true);
mAirFanPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mAirFanPaint.setShader(mGradient);
計算扇葉路徑與外邊六邊形路徑的交集並繪製
final float[] src = new float[]{
mCenter.x, mCenter.y+mRadius, mCenter.x, mCenter.y, mCenter.x+mRadius,
mCenter.y+mRadius, mCenter.x+mRadius, mCenter.y
};
final float[] dst = new float[]{
mCenter.x, mCenter.y+mRadius/2, mCenter.x, mCenter.y, mCenter.x+mRadius,
mCenter.y+mRadius, mCenter.x+mRadius, mCenter.y
};
for(int index = 0;index<FAN_COUNT; index++){
final float angle = mAngle+index*360/FAN_COUNT;
mMatrix.reset();
mMatrix.setPolyToPoly(src, 0, dst, 0, 4);
mMatrix.setRotate(angle, mCenter.x, mCenter.y);
mGradient.setLocalMatrix(mMatrix);
final double startAngle = Math.PI*2*angle/360F;
PointF startPoint = Util.rotatePoint(mRegularHexagon, mCenter, startAngle);
PointF endPoint = Util.rotatePoint(startPoint, mCenter, mRotateAngle);
mAirFanPath.reset();
mAirFanPath.moveTo(mCenter.x, mCenter.y);
mAirFanPath.lineTo(startPoint.x, startPoint.y);
mAirFanPath.addArc(mRectF, angle, mFanRotateAngle);
PointF rationPoint= Util.ratioPoint(mCenter, endPoint, 0.65f);
mAirFanPath.lineTo(rationPoint.x, rationPoint.y);
mAirFanPath.lineTo(mCenter.x, mCenter.y);
mAirFanPath.close();
mAirFanPath.op(mRegularHexagonPath, Path.Op.INTERSECT);
canvas.drawPath(mAirFanPath, mAirFanPaint);
}
canvas.drawCircle(mCenter.x, mCenter.y, 10, mAirFanPaint);
粒子動畫
方案1:使用圖片 通過代碼繪製一張粒子圖片,根據粒子位置與角度轉換Matrix繪製圖片 優點:速度快一點點 方案2:繪製粒子路徑 通過PathMeasure切片路徑,繪製不同粗細路徑實現粒子效果 優點:佔用內存小一點
隨機生成粒子的位置與路徑,以及它的生命週期,生命起始時間
private void reset(boolean init, long currentTime){
final int halfRadius = (int) (mRadius/2f);
final int randomInt = mRandom.nextInt(halfRadius*10)/10;
final int mAngle = mRandom.nextInt(3600)/10;
PointF point = new PointF(mCenter.x+randomInt+halfRadius-(mRadius-mRegularHexagonMinRadius)/2, mCenter.y);
mStartPointF = Util.rotatePoint(point, mCenter, mAngle);
point = new PointF(mCenter.x+(randomInt+halfRadius)/5, mCenter.y);
mEndPointF = Util.rotatePoint(point, mCenter, mAngle);
mPath = new Path();
mPath.moveTo(mStartPointF.x, mStartPointF.y);
mPath.lineTo(mEndPointF.x , mEndPointF.y);
lifeTime = (long) (mRandom.nextInt(500)*mParticleLifeTime/2000f+mParticleLifeTime);
mPathMeasure = new PathMeasure(mPath, false);
mPathLenth = mPathMeasure.getLength();
mParticleProgress = (init?mRandom.nextInt(10000):0)/10000f;
mLiveStartTime = init? (currentTime-(long)(lifeTime*mParticleProgress)):currentTime;
}
先生成一張粒子圖片Bitmap
private Bitmap getParticleBitmap(int width, int height){
Bitmap particleBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
if(width > height){
float radius = height/3f;
int startColor = PARTICLE_COLOR;
int endColor = startColor & 0x44ffffff;
Paint paint = new Paint(Paint.FAKE_BOLD_TEXT_FLAG);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setMaskFilter(new BlurMaskFilter(radius*0.7f, BlurMaskFilter.Blur.NORMAL));
paint.setDither(true);
paint.setShader(new LinearGradient(width, 0, 0, 0,
new int[]{startColor, endColor} , null, Shader.TileMode.CLAMP));
PointF leftPoint = new PointF(0, height/2f);
PointF rightPoint = new PointF(width-height/2, height/2f);
double dAngle = Math.acos(radius/(width-height));
Path path = new Path();
path.moveTo(leftPoint.x, leftPoint.y);
PointF nextPoint = Util.rotatePoint(new PointF(rightPoint.x-radius, rightPoint.y), rightPoint,dAngle);
path.lineTo(nextPoint.x, nextPoint.y);
float startAngle = (float) (dAngle*180/Math.PI)+180;
float sweepAngle = (360-startAngle)*2;
RectF oval = new RectF(rightPoint.x-radius, rightPoint.y-radius, rightPoint.x+radius, rightPoint.y+radius);
path.addArc(oval, startAngle, sweepAngle);
path.lineTo(leftPoint.x, leftPoint.y);
path.close();
Canvas canvas = new Canvas(particleBitmap);
canvas.drawPath(path, paint);
}
return particleBitmap;
}
方案二中使用路徑繪製方式關鍵代碼
final float length = endLeng-startLeng;
final int segmentSize = (int) Math.ceil(length / DEFAULT_SEGMENT_LENGTH);
float startD = startLeng;
float stopD;
for(int index=0; index<segmentSize; index++){
float itemRogress = ((float)index)/segmentSize;
stopD = startLeng+(index+1)*DEFAULT_SEGMENT_LENGTH;
if(stopD > endLeng){
stopD = endLeng;
}
mDstPath.reset();
boolean segment = mPathMeasure.getSegment(startD, stopD, mDstPath, true);
if(segment){
mParticlePaint.setStrokeWidth((1-itemRogress)*(particleSize-0.1f)+0.1f);
mParticlePaint.setAlpha((int) ((isInit?itemRogress:(1-itemRogress))*(particleAlpha-20)+20));
canvas.drawPath(mDstPath, mParticlePaint);
}
}
外部線條移動動畫
方案1:使用圖片 通過幀動畫 優點:實現快
方案2:計算路徑 通過PathMeasure切片路徑,以及通過Matrix縮放Canvas實現 優點:內存小很多
該動畫實際爲正方體框被兩個平行面切出的邊框 實現方式: 光線行走的路徑如右圖。當內部的光線路徑擴展後會擋住外部的光線路徑。
根據此思路,1.計算路徑。2.在繪製時候根據內部的擴展計算可見區域。3.計算光線應該的位置與剛纔計算可見區域的交集路徑。4.繪製路徑
路徑代碼
private void initPath(PointF regularHexagon, double angle){
mPoint = Util.rotatePoint(regularHexagon, mCenter, angle);
double gap = Math.asin(mLineWidth*0.492f/mRadius);
mLeftPoint = Util.rotatePoint(regularHexagon, mCenter, angle-ANGLE+(USE_CLIP_PATH?0:gap));
mRightPoint = Util.rotatePoint(regularHexagon, mCenter, angle+ANGLE-(USE_CLIP_PATH?0:gap));
mLeftPath.reset();
mLeftPath.moveTo(mCenter.x, mCenter.y);
PointF lp = Util.rotatePoint(mPoint, mCenter, -(USE_CLIP_PATH?0:gap));
mLeftPath.lineTo(lp.x, lp.y);
mLeftPath.lineTo(mLeftPoint.x, mLeftPoint.y);
mLeftPath.lineTo(mCenter.x, mCenter.y);
mLeftPath.close();
mLeftPathMeasure = new PathMeasure(mLeftPath, false);
mLeftPathLeng = mLeftPathMeasure.getLength();
mRightPath.reset();
mRightPath.moveTo(mCenter.x, mCenter.y);
PointF rp = Util.rotatePoint(mPoint, mCenter, (USE_CLIP_PATH?0:gap));
mRightPath.lineTo(rp.x, rp.y);
mRightPath.lineTo(mRightPoint.x, mRightPoint.y);
mRightPath.lineTo(mCenter.x, mCenter.y);
mRightPath.close();
mRightPathMeasure = new PathMeasure(mRightPath, false);
mRightPathLeng = mRightPathMeasure.getLength();
}
計算應該繪製的路徑
public void addToPath(Path path) {
float startProgress = mMeteorProgress;
float endProgress = startProgress + mSpaceRatio;
final float littleScale = getLittleScale();
final float start = littleScale/(mCanvasScale*3);
final float end = (mCanvasScale*3-littleScale)/(mCanvasScale*3);
if(startProgress < start){
startProgress = start;
}
if(endProgress > end ){
endProgress = end;
}
if(endProgress > startProgress){
float startLeng = startProgress*mLeftPathLeng;
float endLeng = endProgress*mLeftPathLeng;
mLeftPathMeasure.getSegment(startLeng, endLeng, path, true);
startLeng = startProgress*mRightPathLeng;
endLeng = endProgress*mRightPathLeng;
mRightPathMeasure.getSegment(startLeng, endLeng, path, true);
}
}
public void onDraw(Canvas canvas) {
int saveCount = canvas.save();
Matrix m = new Matrix();
m.setScale(mCanvasScale, mCanvasScale,mCenter.x, mCenter.y);
canvas.setMatrix(m);
if(USE_CLIP_PATH){
canvas.clipPath(mFullPath);
}
mDrawPath.reset();
for(RadiancePath radiance :mRadiances ){
radiance.addToPath(mDrawPath);
}
canvas.drawPath(mDrawPath, mRadiancePaint);
canvas.restoreToCount(saveCount);
}
所有的動畫都實現完了,然後調整下代碼就打工完成。