前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android – Path画搜索动画

Android – Path画搜索动画

作者头像
code_horse
发布2018-07-02 14:08:20
9360
发布2018-07-02 14:08:20
举报
文章被收录于专栏:Android NoteAndroid Note

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

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

实现效果图:

search_loading_view.gif

① 先看初始化部分:

代码语言:javascript
复制
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();
    }
代码语言:javascript
复制
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方法:

代码语言:javascript
复制
canvas.drawPath(mPSearch, mPaint);
canvas.drawPath(mPCircle, mPaint);

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

img.png

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

下面我们要让它动起来了

代码语言:javascript
复制
      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

设置动画监听

代码语言:javascript
复制
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方法

代码语言:javascript
复制
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = measureWidth(widthMeasureSpec);
        int height = measureHeight(heightMeasureSpec);
        setMeasuredDimension(width, height);
    }
代码语言:javascript
复制
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;
}
代码语言:javascript
复制
    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已经画好了,下面添加两个调用方法:

代码语言:javascript
复制
    //开始动画
    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();
        }
    }

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

代码语言:javascript
复制
     @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();
        }

    }

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.12.18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档