Android 使用 Path 实现搜索动态加载动画效果

2,201 阅读10分钟
原文链接: blog.csdn.net

今天实现一个搜索动态加载数据的动画效果,还是先看效果吧,用文字描述干巴巴的,看图说话什么都明白了,


实现这个就是使用Path中的getSegment()不断的去改变它截取片段的start和stop,再结合动画,今天就分步骤实现它,看完以后你也会觉的不是很难,只是没想到这么实现而已,所以要多见识,所谓眼界决定你的高度,还是延续我写博客的习惯,一步步分析,第一步就是绘制如下图:


如果单纯的绘制这个图很简单很简单的,绘制一个圆,然后再绘制一根线就搞定,但是要考虑这里的效果,就不能这么干了,如果你看了上面的gif图就知道,其实这是2个同心圆,然后前一个path的起点和后一个path的起点相连接就是形成一条直线了,但是path中的图形内容也就是这个圆是怎么绘制出来的呢?如果是绘制圆的话,上面的线起点和终点位置怎么去计算,这是个问题,但是我们绘制圆还可以使用绘制椭圆的形式也是可以绘制达到圆的效果,从45度开始绘制一个圆,是不是这个线的起点搞定了,分析图如下:


那么好,根据上面的分析开始写代码绘制出一个静态的搜索图:

package com.tuya;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/12/17.
 */
public class DynamicSearchView2 extends View {
    private Paint paint;
    private int width;//view的宽度
    private int height;//view的高度
    private Path searchPath;
    private Path circlePath;
    private float BigCircleRectWidth;//搜索圆对应的外切正方形边长
    private PathMeasure pathMeasure;
    private float[] pos;
    public DynamicSearchView2(Context context) {
        this(context,null);
    }
    public DynamicSearchView2(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public DynamicSearchView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStrokeWidth(3);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
        initPath();
    }
    /**
     * 初始化path
     */
    private void initPath() {
        searchPath = new Path();
        circlePath = new Path();
        if(width>height){//长方形
            BigCircleRectWidth = height;
        }else if(width<height){
            BigCircleRectWidth = width;
        }else{
            BigCircleRectWidth = width;
        }
        float smallbordWidth =BigCircleRectWidth/8;
        RectF searchRect = new RectF(-smallbordWidth,-smallbordWidth,smallbordWidth,smallbordWidth);

        searchPath.addArc(searchRect,45,360);
        float bigBordWidth = smallbordWidth*2;
        RectF circleRect = new RectF(-bigBordWidth,-bigBordWidth,bigBordWidth,bigBordWidth);
        circlePath.addArc(circleRect,45,-360);
        pathMeasure = new PathMeasure(circlePath,false);
        pos = new float[2];
        pathMeasure.getPosTan(0,pos,null);
        searchPath.lineTo(pos[0],pos[1]);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(width/2,height/2);//平移画布把这个view的中心点当做原点
        canvas.drawPath(searchPath,paint);
        canvas.drawPath(circlePath,paint);
    }
}

效果图:


本来这个外圆是不需要draw上去的,我在这绘制上去只是告诉你这二个圆是有一定的联系,哪为什么这根线是这样的呢?我们在绘制这个圆的时候是从45度开始绘制360刚好是一周,形成了一个圆,现在做个测试不要360,就写个330度,效果如下:


这个时候你会发现这条线是对的,导致问题其实是这样的,如图分析:


把绘制椭圆的关键代码:

searchPath.addArc(searchRect,45,358);

circlePath.addArc(circleRect,45,-358);

不要写成360,改为358试试,效果图:


发现这线是不是正常了,至于外面的圆还有点缺口,第一你可以把358改成359应该没事了,还有就是我们其实真实的效果并不需要这个外面的圆,所以不改也没事,那么好,第一步算是完成了,现在想想第二步怎么实现,先把第二步的效果用gif展示看下,不然光想没思路,就像你看美女,第一眼看那,是吧,就不多说了!要有画面感,


还是画布分析:


哪我们只要改变startD这个离起始点的位置值就ok,当然有很多种方法,但是Android中基本上都是使用值动画,ok,根据这个思路实现这个第二步逻辑:

package com.tuya;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/12/17.
 */
public class DynamicSearchView2 extends View {
    private Paint paint;
    private int width;//view的宽度
    private int height;//view的高度
    private Path searchPath;
    private Path circlePath;
    private float BigCircleRectWidth;//搜索圆对应的外切正方形边长
    private PathMeasure pathMeasure;
    private float[] pos;
    private float animPercent;//
    private ValueAnimator serchStartAnim;
    private long animDuration = 2000;//动画时间
    public DynamicSearchView2(Context context) {
        this(context,null);
    }
    public DynamicSearchView2(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public DynamicSearchView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    private void init() {
        initPaint();
        initAnim();
        initAnimListener();
        startAnim();
    }
    /**
     * 开始执行动画
     */
    private void startAnim() {
        serchStartAnim.start();
    }
    /**
     * 动画监听
     */
    private void initAnimListener() {
        serchStartAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                 //获取动画在单位时间内,每次执行的值
                animPercent = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
    }
    /**
     * 初始化动画
     */
    private void initAnim() {
        serchStartAnim = ValueAnimator.ofFloat(0,1).setDuration(animDuration);
    }
    /**
     * 初始化画笔
     */
    private void initPaint() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStrokeWidth(3);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
        initPath();
    }
    /**
     * 初始化path
     */
    private void initPath() {
        searchPath = new Path();
        circlePath = new Path();
        if(width>height){//长方形
            BigCircleRectWidth = height;
        }else if(width<height){
            BigCircleRectWidth = width;
        }else{
            BigCircleRectWidth = width;
        }
        float smallbordWidth =BigCircleRectWidth/8;
        RectF searchRect = new RectF(-smallbordWidth,-smallbordWidth,smallbordWidth,smallbordWidth);

        searchPath.addArc(searchRect,45,358);
        float bigBordWidth = smallbordWidth*2;
        RectF circleRect = new RectF(-bigBordWidth,-bigBordWidth,bigBordWidth,bigBordWidth);
        circlePath.addArc(circleRect,45,-358);
        pathMeasure = new PathMeasure(circlePath,false);
        pos = new float[2];
        pathMeasure.getPosTan(0,pos,null);
        searchPath.lineTo(pos[0],pos[1]);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
          canvas.translate(width/2,height/2);//平移画布把这个view的中心点当做原点
          drawSearch(canvas);
    }
    private void drawSearch(Canvas canvas) {
        Path dst = new Path();
        pathMeasure.setPath(searchPath,false);
        pathMeasure.getSegment(pathMeasure.getLength()*animPercent,pathMeasure.getLength(),dst,true);
        canvas.drawPath(searchPath,paint);
    }
}
效果:


现在还我们效果还差外圆的大圆的效果了,那么大圆是在小圆动画执行完毕后再去做旋转效果的,那好,我们只要监听动画就可以,画图:

package com.tuya;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/12/17.
 */
public class DynamicSearchView2 extends View {
    private static final String TAG = "DynamicSearchView2";
    private Paint paint;
    private int width;//view的宽度
    private int height;//view的高度
    private Path searchPath;
    private Path circlePath;
    private float BigCircleRectWidth;//搜索圆对应的外切正方形边长
    private PathMeasure pathMeasure;
    private float[] pos;
    private float animPercent;//
    private ValueAnimator serchStartAnim;
    private ValueAnimator bigCircleAnim;//外面大圆运动的动画
    private long animDuration = 2000;//动画时间
    private int drawTag = 1;//区分是绘制搜索框还是外层圆
    public DynamicSearchView2(Context context) {
        this(context,null);
    }
    public DynamicSearchView2(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public DynamicSearchView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    private void init() {
        initPaint();
        initAnim();
        initAnimListener();
        startAnim();
    }
    /**
     * 开始执行动画
     */
    private void startAnim() {
        drawTag = 1;
        serchStartAnim.start();
        invalidate();
    }
    /**
     * 开启大圆执行动画
     */
    public void startBigCirCleAnim(){
        serchStartAnim.removeAllUpdateListeners();//把上一个动画监听移除 以免总成诡异的bug
        bigCircleAnim.start();
        drawTag = 2;
    }
    /**
     * 动画监听
     */
    private void initAnimListener() {
        serchStartAnim.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {
                startBigCirCleAnim();
            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        serchStartAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //获取动画在单位时间内,每次执行的值
                animPercent = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        bigCircleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //获取动画在单位时间内,每次执行的值
                animPercent = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
    }
    /**
     * 初始化动画
     */
    private void initAnim() {
        bigCircleAnim = ValueAnimator.ofFloat(0,1).setDuration(animDuration);
        serchStartAnim = ValueAnimator.ofFloat(0,1).setDuration(animDuration);
    }
    /**
     * 初始化画笔
     */
    private void initPaint() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStrokeWidth(3);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
        initPath();
    }
    /**
     * 初始化path
     */
    private void initPath() {
        searchPath = new Path();
        circlePath = new Path();
        if(width>height){//长方形
            BigCircleRectWidth = height;
        }else if(width<height){
            BigCircleRectWidth = width;
        }else{
            BigCircleRectWidth = width;
        }
        float smallbordWidth =BigCircleRectWidth/8;
        RectF searchRect = new RectF(-smallbordWidth,-smallbordWidth,smallbordWidth,smallbordWidth);

        searchPath.addArc(searchRect,45,358);
        float bigBordWidth = smallbordWidth*2;
        RectF circleRect = new RectF(-bigBordWidth,-bigBordWidth,bigBordWidth,bigBordWidth);
        circlePath.addArc(circleRect,45,-358);
        pathMeasure = new PathMeasure(circlePath,false);
        pos = new float[2];
        pathMeasure.getPosTan(0,pos,null);
        searchPath.lineTo(pos[0],pos[1]);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(width/2,height/2);//平移画布把这个view的中心点当做原点
        drawSearch(canvas);
    }
    private void drawSearch(Canvas canvas) {
        if(drawTag==1){
            drawSearchGraph(canvas);
        }else if(drawTag==2){
            drawBigCircleGraph(canvas);
        }
    }
    /**
     * 绘制外层大圆
     * @param canvas
     */
    private void drawBigCircleGraph(Canvas canvas) {
        pathMeasure.setPath(circlePath, false);
        Path dst2 = new Path();
        float stop = pathMeasure.getLength() * animPercent;
        float start = (float) (stop - ((0.5 - Math.abs(animPercent - 0.5)) * 200f));
        pathMeasure.getSegment(start, stop, dst2, true);
        canvas.drawPath(dst2, paint);
    }
    /**
     * 绘制搜索框
     * @param canvas
     */
    private void drawSearchGraph(Canvas canvas) {
        pathMeasure.setPath(searchPath,false);
        Path dst = new Path();
        pathMeasure.getSegment(pathMeasure.getLength()*animPercent,pathMeasure.getLength(),dst,true);
        canvas.drawPath(dst,paint);
    }
}
效果:


发现转一圈就到头了,如果有特定的需求肯定是要控制整个转圈的圈数,如果是网络加载的话,除非网络特别的好,先不管了,因为等下还要写周报,也是很痛苦的

现在还差最后一步就是大圆的运动完后要绘制搜索框出来,其实这个和第一步效果刚好是相关的,

package com.tuya;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/12/17.
 */
public class DynamicSearchView2 extends View {
    private static final String TAG = "DynamicSearchView2";
    private Paint paint;
    private int width;//view的宽度
    private int height;//view的高度
    private Path searchPath;
    private Path circlePath;
    private float BigCircleRectWidth;//搜索圆对应的外切正方形边长
    private PathMeasure pathMeasure;
    private float[] pos;
    private float animPercent;//
    private ValueAnimator serchStartAnim;
    private ValueAnimator bigCircleAnim;//外面大圆运动的动画
    private ValueAnimator startDrawSearchAnim;//最后一步绘制搜索框
    private long animDuration = 2000;//动画时间
    private int drawTag = 1;//区分是绘制搜索框还是外层圆
    public DynamicSearchView2(Context context) {
        this(context,null);
    }
    public DynamicSearchView2(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public DynamicSearchView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    private void init() {
        initPaint();
        initAnim();
        initAnimListener();
        startAnim();
    }
    /**
     * 开始执行动画
     */
    private void startAnim() {
        drawTag = 1;
        serchStartAnim.start();
        invalidate();
    }
    /**
     * 开启大圆执行动画
     */
    public void startBigCirCleAnim(){
        serchStartAnim.removeAllUpdateListeners();//把上一个动画监听移除 以免总成诡异的bug
        bigCircleAnim.start();
        drawTag = 2;
    }
    /**
     * 最后绘制搜索框的动画
     */
    public void drawSearchAanim(){
        bigCircleAnim.removeAllUpdateListeners();//把上一个动画监听移除 以免总成诡异的bug
        startDrawSearchAnim.start();
        drawTag = 3;
    }
    /**
     * 动画监听
     */
    private void initAnimListener() {
        bigCircleAnim.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {
                drawSearchAanim();
            }
            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        serchStartAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //获取动画在单位时间内,每次执行的值
                animPercent = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });

        serchStartAnim.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {
                startBigCirCleAnim();
            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        serchStartAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //获取动画在单位时间内,每次执行的值
                animPercent = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        bigCircleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //获取动画在单位时间内,每次执行的值
                animPercent = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        startDrawSearchAnim .addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //获取动画在单位时间内,每次执行的值
                animPercent = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
    }
    /**
     * 初始化动画
     */
    private void initAnim() {
        bigCircleAnim = ValueAnimator.ofFloat(0,1).setDuration(animDuration);
        serchStartAnim = ValueAnimator.ofFloat(0,1).setDuration(animDuration);
        startDrawSearchAnim = ValueAnimator.ofFloat(1,0).setDuration(animDuration);
    }
    /**
     * 初始化画笔
     */
    private void initPaint() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStrokeWidth(6);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
        initPath();
    }
    /**
     * 初始化path
     */
    private void initPath() {
        searchPath = new Path();
        circlePath = new Path();
        if(width>height){//长方形
            BigCircleRectWidth = height;
        }else if(width<height){
            BigCircleRectWidth = width;
        }else{
            BigCircleRectWidth = width;
        }
        float smallbordWidth =BigCircleRectWidth/8;
        RectF searchRect = new RectF(-smallbordWidth,-smallbordWidth,smallbordWidth,smallbordWidth);

        searchPath.addArc(searchRect,45,358);
        float bigBordWidth = smallbordWidth*2;
        RectF circleRect = new RectF(-bigBordWidth,-bigBordWidth,bigBordWidth,bigBordWidth);
        circlePath.addArc(circleRect,45,-358);
        pathMeasure = new PathMeasure(circlePath,false);
        pos = new float[2];
        pathMeasure.getPosTan(0,pos,null);
        searchPath.lineTo(pos[0],pos[1]);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(width/2,height/2);//平移画布把这个view的中心点当做原点
        drawSearch(canvas);
    }
    private void drawSearch(Canvas canvas) {
        if(drawTag==1){
            drawSearchGraph(canvas);
        }else if(drawTag==2){
            drawBigCircleGraph(canvas);
        }else if(drawTag==3){
            drawSearchBox(canvas);
        }
    }
    /**
     * 最后一步绘制搜索框 从终点到起点
     * @param canvas
     */
    private void drawSearchBox(Canvas canvas) {
        pathMeasure.setPath(searchPath, false);
        Path dst3 = new Path();
        pathMeasure.getSegment(pathMeasure.getLength() * animPercent, pathMeasure.getLength(), dst3, true);
        canvas.drawPath(dst3, paint);
    }
    /**
     * 绘制外层大圆
     * @param canvas
     */
    private void drawBigCircleGraph(Canvas canvas) {
        pathMeasure.setPath(circlePath, false);
        Path dst2 = new Path();
        float stop = pathMeasure.getLength() * animPercent;
        float start = (float) (stop - ((0.5 - Math.abs(animPercent - 0.5)) * 200f));
        pathMeasure.getSegment(start, stop, dst2, true);
        canvas.drawPath(dst2, paint);
    }
    /**
     * 绘制搜索框
     * @param canvas
     */
    private void drawSearchGraph(Canvas canvas) {
        pathMeasure.setPath(searchPath,false);
        Path dst = new Path();
        pathMeasure.getSegment(pathMeasure.getLength()*animPercent,pathMeasure.getLength(),dst,true);
        canvas.drawPath(dst,paint);
    }
}
效果:


总算写完了!