Android自定义控件Android开发经验谈Android知识

Android – Path画搜索动画

2017-12-18  本文已影响26人  Kotyo

今天画的这个搜索动画是在一个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();
        }

    }

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

上一篇下一篇

猜你喜欢

热点阅读