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

    }

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏非著名程序员

AutoFlowLayout-多功能流式布局与网格布局控件

近期工作需要用到流式布局,网上也有很多关于这方面的资料。发现流式布局与网格布局的自定义很有意思,是学习自定义控件的一个很好的方式,所以就撸了个几百行代码的控件,...

29510
来自专栏Hellovass 的博客

手动测量 View 的宽高

手动调用 View 的 measure(int widthMeasureSpec,int heightMeasureSpec) 方法来得到 View 的宽高。

1106
来自专栏向治洪

android自定义xmls文件属性

在使用到自定义View的xml布局文件中需要加入xmlns:前缀=http://schemas.android.com/apk/res/你的自定义View所在...

1855
来自专栏三好码农的三亩自留地

教你搞定Android自定义ViewGroup

我们知道ViewGroup就是View的容器类,我们经常用的LinearLayout,RelativeLayout等都是ViewGroup的子类,因为ViewG...

561
来自专栏向治洪

Android仿拼多多拼团堆叠头像

序言 做电商的都知道,作为商品的一种售卖方式,拼团是是提供商品售卖的一种及时有效的方式,而在拼团市场中,拼多多无疑是做的最好的一家。于是,研究拼多多的售卖方式之...

3878
来自专栏Android中高级开发

Android开发之漫漫长途 Ⅴ——Activity的显示之ViewRootImpl的PreMeasure、WindowLayout、EndMeasure、Layout、Draw

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索...

853
来自专栏向治洪

滑动开关按钮SlideSwich

iphone上有开关控件,很漂亮,其实android4.0以后也有switch控件,但是只能用在4.0以后的系统中,这就失去了其使用价值,而且我觉得它的界面也...

24110
来自专栏Android开发指南

Android刮刮卡自定义控件

3149
来自专栏指尖下的Android

安卓凹凸自定义View

那到这个需求感觉还是很简单的,让美术出了一张图,然后我把这个背景图做成了.9图,然而,并没有什么卵用,最大的原因就是background被拉伸、挤压,高度在不同...

942
来自专栏Android相关

Android-LinearLayout中getChildMeasureSpec解析

该函数的注释是:执行最难的一步:测量子View大小,测量出指定的MeasureSpec 给一个单独的子View,这个方法要计算出子View正确的HeightMe...

551

扫码关注云+社区