高仿iOSDownloadView

211 阅读3分钟

#最近入手iPhone,看见app stroe下载动画甚感兴趣,决定模仿一波。

##事不宜迟,先看最终效果。

#分析控件主要绘制步骤

  • 1.绘制背景icon
  • 2.绘制外边缘圆环
  • 3.剪切View四边圆角
  • 4.绘制中间扇形进度
  • 5.绘制缕空暂停及其动画
  • 6.绘制取消暂停及其动画
  • 7.绘制下载完成动画

##控件代码

public class DownloadView extends View {

    private Paint mBgPaint;
    private Paint mLayerPaint;
    private Paint mArcPaint;
    private Bitmap mBgBitmap;
    private RectF mArcRectF,mSmallRectF ;
    private Path mPath = new Path();
    private int percent,pointSize ,layerRadius;
    private boolean stop,finish;

    private OnDownloadFinishListener mListener;

    public DownloadView(Context context) {
        this(context,null);
    }

    public DownloadView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public DownloadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {

        //绘制背景icon的画笔
        mBgPaint = new Paint();
        mBgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg);

        //外层剪切圆角的矩形
        RectF rect =  new RectF();
        rect.set(0, 0, mBgBitmap.getWidth(), mBgBitmap.getHeight());
        mPath.addRoundRect(rect,30f,30f, Path.Direction.CW);

        //绘制外圈圆环
        mLayerPaint = new Paint();
        mLayerPaint.setAntiAlias(true);
        mLayerPaint.setColor(getResources().getColor(R.color.layer));
        mLayerPaint.setStyle(Paint.Style.STROKE);
        //将画笔半径设置为view宽度减去100,这样就可以在中心留100的位置绘制其他东西
        mLayerPaint.setStrokeWidth(mBgBitmap.getWidth() - 100);

        //绘制内圈扇形
        mArcPaint = new Paint();
        mArcPaint.setAntiAlias(true);
        mArcPaint.setColor(getResources().getColor(R.color.layer));
        mArcPaint.setStyle(Paint.Style.FILL);
        mArcPaint.setStrokeWidth(10);
        //进度扇形的矩形边框位置设置为中心点-45,这样就和外圆环有5个像素点的间隔
        mArcRectF =  new RectF(mBgBitmap.getWidth()/2-45,mBgBitmap.getHeight()/2-45,
                mBgBitmap.getWidth()/2+45,mBgBitmap.getHeight()/2+45);

        //绘制暂停扇形所用到的RectF
        mSmallRectF = new RectF();

        //加载结束动画使用的半径
        layerRadius = mBgBitmap.getWidth()/2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (finish)return;

        //简单裁剪为圆角View
        canvas.clipPath(mPath);
        //画背景icon
        canvas.drawBitmap(mBgBitmap,0,0,mBgPaint);


        if (stop) {//如果暂停
            if (pointSize < 70){//判断是否需要执行最内层扇形变大的动画
                pointSize += 10;
                invalidate();
            }else {
                //圆点动画执行完成则剪切出 缕空暂停 的效果,剪切效果只对后面的绘制产生影响
                //相关知识 https://blog.csdn.net/eyishion/article/details/53728913
                //        http://hencoder.com/ui-1-4/
                canvas.clipRect(getWidth()/2-20,getHeight()/2-10,getWidth()/2-10,getHeight()/2+10,
                Region.Op.DIFFERENCE);

                canvas.clipRect(getWidth()/2+10,getHeight()/2-10,getWidth()/2+20,getHeight()/2+10,
                Region.Op.DIFFERENCE);

            }
            setSmallRectSize(pointSize);
            canvas.drawArc(mSmallRectF,-90,percent*360/100,true,mArcPaint);

        }else if ( pointSize > 0){//如果不是暂停模式,并且最内层扇形大小没有变为0,则执行扇形变小的动画

            pointSize -= 10;//圆点大小每次自减10

            if (pointSize >=  60){//如果圆点大小大于60以上,则还是需要显示 缕空暂停
                canvas.clipRect(getWidth()/2-20,getHeight()/2-10,getWidth()/2-10,getHeight()/2+10,
                        Region.Op.DIFFERENCE);

                canvas.clipRect(getWidth()/2+10,getHeight()/2-10,getWidth()/2+20,getHeight()/2+10,
                        Region.Op.DIFFERENCE);
            }

            setSmallRectSize(pointSize);
            canvas.drawArc(mSmallRectF,-90,percent*360/100,true,mArcPaint);
            invalidate();

        }

        if (percent < 100){//如果进度还没完成,画内部进度扇形
            //画内圈扇形
            canvas.drawArc(mArcRectF,-90+percent*360/100,360-percent*360/100,true,mArcPaint);

        }else {//否则执行外圈消失的动画
            if (layerRadius < getWidth()){
                layerRadius += 10;
                invalidate();
            }else {
                finish = true;
                if (mListener!=null)
                    mListener.onDownloadFinish();
            }
        }
        //画外圈圆环
        canvas.drawCircle(getWidth()/2,getHeight()/2,layerRadius,mLayerPaint);

    }

    //设置暂停扇形的矩形大小
    private void setSmallRectSize(int size){
        int centerX = getWidth()/2;
        int centerY = getHeight()/2;
        mSmallRectF.set(centerX-size/2,centerY-size/2,centerX+size/2,centerY+size/2);
    }

    public void setDownloadLietener(OnDownloadFinishListener lietener){
        this.mListener = lietener;
    }

    //设置百分比进度
    public void setPercent(@IntRange(from = 0, to = 100)int percent){
        this.percent = percent;
        if (stop){
            stop = false;
        }
        invalidate();
    }

    //暂停
    public void stop(){
        stop = true;
        invalidate();
    }

    //开始
    public void restart(){
        stop = false;
        invalidate();
    }

    //重置
    public void reset(){
        finish = false;
        stop = false;
        percent = 0;
        pointSize = 0;
        layerRadius = getWidth()/2;
        invalidate();
    }
    
    //设置控件大小为icon大小
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(mBgBitmap.getWidth(),mBgBitmap.getHeight());
    }

    public interface OnDownloadFinishListener{
        void onDownloadFinish();
    };

}

##不足点

  • 四边圆角有锯齿,这个是clipPath的必然效果,好像避免不了。
  • icon需要正方形。

#项目github github.com/LeeeYudE/iO…