Android – Path画搜索动画

今天画的这个搜索动画是在一个Path教程中看到的,就去试着画了一下。
Path动画教程
教程中代码地址
如果要画出今天的这个动画效果,需要了解Path的PathMeasure和getSegment()这个两个方法,如果有不了解的同学可以去上面的教程中去学习一下。

本文代码借用了原作者的部分思想,雷同点请别惊讶~~(╯﹏╰)b

实现效果图:

search_loading_view.gif

① 先看初始化部分:

private Paint mPaint;
    //内部小圆Path
    private Path mPCircle;
    //外部大圆Path(效果中并没有画出来)
    private Path mPSearch;
    //用来测量Path
    private PathMeasure mPathMeasure;
    //开始动画
    private ValueAnimator mStartValueAnimator;
    //搜索动画
    private ValueAnimator mSearchValueAnimator;
    //结束动画
    private ValueAnimator mEndValueAnimator;
    //外部大圆半径
    private int mBigRadius;
    //内部小圆半径
    private int mSmallRadius;
    //动画随时间改变的值
    private float mAnimatorValue;
    //初始化状态值(界面会显示为一个搜索图标)
    private State mCurrState = State.NONE;  
    //动画的各个状态,用枚举值表示
    enum State {
        //无动画
        NONE,
        //开始动画
        START,
        //搜索动画
        SEARCH,
        //结束动画
        END
    }
   public SearchView3(Context context) {
        this(context, null);
    }

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

    public SearchView3(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    
private void init() {
        mPaint = new Paint();
        mPaint.setColor(Color.WHITE);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(15);
        mPaint.setStyle(Paint.Style.STROKE);

        mPathMeasure = new PathMeasure();
        mPSearch = new Path();
        mPCircle = new Path();

        mBigRadius = 100;
        mSmallRadius = 30;

        //内部小圆
        RectF rectFSearch = new RectF(-mSmallRadius, -mSmallRadius, mSmallRadius, mSmallRadius);
        //实测,这里不能写360,否则取不到需要的值,效果展示也会出现问题,下同![7C2F1378-6877-4260-9F7D-0CFA6B49E6E6.png](http://upload-images.jianshu.io/upload_images/2726727-e2ac5681bfd570ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

        mPSearch.addArc(rectFSearch, 45, 359.9f);
        //外部大圆
        RectF rectFCircle = new RectF(-mBigRadius, -mBigRadius, mBigRadius, mBigRadius);
        mPCircle.addArc(rectFCircle, 45, 359.9f);
        float[] pos = new float[2];
        mPathMeasure.setPath(mPCircle, false);
        mPathMeasure.getPosTan(0, pos, null);
        //小圆的起点连接大圆的起点
        mPSearch.lineTo(pos[0], pos[1]);
        initAnimatorCircle();
    }

这时候咱们调用onDraw方法:

canvas.drawPath(mPSearch, mPaint);
canvas.drawPath(mPCircle, mPaint);

这时候咱们就会到如下效果:


img.png

所以,我们只要不画外部的大圆即可得到搜索放大镜

下面我们要让它动起来了

      switch (mCurrState) {
            case NONE:
                canvas.drawPath(mPSearch, mPaint);
                break;
            case START://开始动画
                Path dstSearch = new Path();
                PathMeasure pmSearch = new PathMeasure(mPSearch, false);
                pmSearch.getSegment(pmSearch.getLength() * mAnimatorValue, pmSearch.getLength(), dstSearch, true);
                canvas.drawPath(dstSearch, mPaint);
                break;
            case SEARCH://搜索动画
                Path dst = new Path();
                PathMeasure pathMeasure = new PathMeasure(mPCircle, false);
                float v1 = (float) ((pathMeasure.getLength() * mAnimatorValue) - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * 100f));
                pathMeasure.getSegment(v1, pathMeasure.getLength() * mAnimatorValue, dst, true);
                canvas.drawPath(dst, mPaint);
                break;
            case END://结束动画
                Path dstEnd = new Path();
                PathMeasure pmEnd = new PathMeasure(mPSearch, false);
                pmEnd.getSegment(pmEnd.getLength() * mAnimatorValue, pmEnd.getLength(), dstEnd, true);
                canvas.drawPath(dstEnd, mPaint);
                break;
        }

下面是三种状态的分解效果图:
开始动画

Start.gif

搜索动画

Search.gif

结束动画
End.gif

设置动画监听

private void initAnimatorCircle() {
        mStartValueAnimator = ValueAnimator.ofFloat(0, 1);
        mStartValueAnimator.setDuration(1000);
        mStartValueAnimator.setInterpolator(new LinearInterpolator());


        mSearchValueAnimator = ValueAnimator.ofFloat(1, 0);
        mSearchValueAnimator.setDuration(1500);
        mSearchValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mSearchValueAnimator.setInterpolator(new LinearInterpolator());

        mEndValueAnimator = ValueAnimator.ofFloat(1, 0);
        mEndValueAnimator.setDuration(1000);
        mEndValueAnimator.setInterpolator(new LinearInterpolator());


        ValueAnimator.AnimatorUpdateListener valueAnimator = new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                mAnimatorValue = (float) animation.getAnimatedValue();

                invalidate();
            }
        };


        Animator.AnimatorListener animatorListener = new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {

                switch (mCurrState) {
                    case START:
                        mCurrState = State.SEARCH;
                        mSearchValueAnimator.start();
                        break;
                }

            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        };


        mStartValueAnimator.addUpdateListener(valueAnimator);
        mStartValueAnimator.addListener(animatorListener);

        mSearchValueAnimator.addUpdateListener(valueAnimator);
        mSearchValueAnimator.addListener(animatorListener);

        mEndValueAnimator.addUpdateListener(valueAnimator);
        mEndValueAnimator.addListener(animatorListener);

    }

我这里简单重写了一下onMeasure方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = measureWidth(widthMeasureSpec);
        int height = measureHeight(heightMeasureSpec);
        setMeasuredDimension(width, height);
    }
private int measureWidth(int widthMeasureSpec) {
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int actWidth = 0;
    switch (widthMode) {
        case MeasureSpec.AT_MOST:
            actWidth = mBigRadius * 2 + getPaddingLeft() + getPaddingRight();
            break;
        case MeasureSpec.EXACTLY:
            actWidth = widthSize;
            break;
    }
    return actWidth;
}

    private int measureHeight(int heightMeasureSpec) {
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int actHeight = 0;
        switch (heightMode) {
        //这里我在xml中添加了paddingTop和paddingBottom,如果不加的同学,可以把这里的值稍微设置大一点,因为,值小了,上下效果会显示不完全的,左右同理
            case MeasureSpec.AT_MOST:
                actHeight = mBigRadius * 2 + getPaddingTop() + getPaddingBottom();
                break;
            case MeasureSpec.EXACTLY:
                actHeight = heightSize;
                break;
            case MeasureSpec.UNSPECIFIED:
                actHeight = mBigRadius * 2 + getPaddingTop() + getPaddingBottom();
                break;
        }
        return actHeight;
    }

注意:
因为我的xml根布局是ScrollView,height设置为wrap_content,所以,onMeasure方法中测量Height时,走了MeasureSpec.UNSPECIFIED,而非MeasureSpec.AT_MOST

这时候View已经画好了,下面添加两个调用方法:

    //开始动画
    public void startAnimator() {
        if (mStartValueAnimator.isRunning() || mSearchValueAnimator.isRunning() || mEndValueAnimator.isRunning()) {
            return;
        }
        mCurrState = State.START;
        mStartValueAnimator.start();
        mAnimatorValue = 0;
    }
    //结束动画
    public void cancelSearchAnimator() {
        if (mSearchValueAnimator != null && mSearchValueAnimator.isRunning()) {
            mStartValueAnimator.cancel();
            mSearchValueAnimator.cancel();
            mCurrState = State.END;
            mEndValueAnimator.start();
        }
    }

最后的最后,我们还需要重写一个方法去停止动画:

     @Override
     protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        if (mStartValueAnimator != null && mStartValueAnimator.isRunning()) {
            mStartValueAnimator.cancel();
            mStartValueAnimator.removeAllListeners();
        }

        if (mSearchValueAnimator != null && mSearchValueAnimator.isRunning()) {
            mSearchValueAnimator.cancel();
            mSearchValueAnimator.removeAllListeners();
        }

        if (mEndValueAnimator != null && mEndValueAnimator.isRunning()) {
            mEndValueAnimator.cancel();
            mEndValueAnimator.removeAllListeners();
        }

    }

好了,剩下的就是去直接调用就好了,到这里,内容就全部结束了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容