Android – Path画搜索动画

作者: Kotyo | 来源:发表于2017-12-18 18:04 被阅读26次

    今天画的这个搜索动画是在一个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](https://img.haomeiwen.com/i2726727/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();
            }
    
        }
    

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

    相关文章

      网友评论

      本文标题:Android – Path画搜索动画

      本文链接:https://www.haomeiwen.com/subject/nyymwxtx.html