使用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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章