使用SurfaceView繪製複雜效果

 

效果如下

 

風扇效果
風扇動畫
粒子效果
粒子動畫

 

 

 

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);
}

 

所有的動畫都實現完了,然後調整下代碼就打工完成。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
發佈了8 篇原創文章 · 獲贊 3 · 訪問量 1410
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章