欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android:使用PathMeasure绘制动画效果的搜索按钮

程序员文章站 2022-03-09 19:49:20
...

首先上效果图:
Android:使用PathMeasure绘制动画效果的搜索按钮

该搜索按钮有4种状态:
1. 默认状态:一个静态的放大镜;
2. 开始搜索状态:放大镜逐渐缩小为一个点;
3. 正在搜索状态:一个动态的圈;
4. 结束搜索状态:放大镜由一个点恢复初始状态。

此处点击一下模拟开始搜索,再点击一下搜索结束。

代码不多,直接全部贴上:

/**
 * 使用PathMeasure自定义的MySearchView.
 * Created by xxx on 17-5-15.
 */

public class MySearchView extends View {
    private static final int BG_COLOR = Color.parseColor("#FF37A1EC");
    private static final int PADDING = 8;

    private Path bgPath;  //圆角矩形背景
    private Path searchPath;  //放大镜Path
    private Path circlePath;  //圆圈Path
    private static final int PAINT_WIDTH = 4;

    private PathMeasure measure;

    private Paint mPaint;
    private Paint mBgPaint;

    private static final int STATE_INIT = 0;  //初始状态
    private static final int STATE_STARTING = 1;  //开始搜索状态
    private static final int STATE_SEARCHING = 2;  //正在搜索状态
    private static final int STATE_ENDDING = 3;  //结束搜索状态

    //标记当前状态
    private int state = STATE_INIT;

    public MySearchView(Context context) {
        super(context);
    }

    public MySearchView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MySearchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private float searchLen;  //放大镜路径总长度
    private float circleLen;  //大圆圈总长度

    private void init() {
        measure = new PathMeasure();

        //小圆的半径
        int smallR = (mWidth - PADDING * 2) / 4;
        //中心点坐标
        int centerXY = mWidth / 2;

        //放大镜
        searchPath = new Path();
        RectF rect1 = new RectF(centerXY - smallR, centerXY - smallR, centerXY + smallR, centerXY + smallR);
        searchPath.addArc(rect1, 45, 359.9f);
        //放大镜把手
        searchPath.rLineTo(((float) (smallR * Math.sqrt(2) / 2)), ((float) (smallR * Math.sqrt(2) / 2)));

        searchPath_dst = new Path();

        //大圆圈
        circlePath = new Path();
        RectF rect2 = new RectF(centerXY - smallR * 2, centerXY - smallR * 2, centerXY + smallR * 2, centerXY + smallR * 2);
        circlePath.addArc(rect2, 45, 359.9f);

        circlePath_dst = new Path();

        //计算放大镜和圆圈的长度
        measure.setPath(searchPath, false);
        searchLen = measure.getLength();
        measure.setPath(circlePath, false);
        circleLen = measure.getLength();

        bgPath = new Path();
        RectF rect3 = new RectF(0, 0, mWidth, mHeight);
        bgPath.addRoundRect(rect3, 12, 12, Path.Direction.CW);

        mPaint = new Paint();
        mPaint.setColor(Color.WHITE);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(PAINT_WIDTH);
        mPaint.setStyle(Paint.Style.STROKE);

        mBgPaint = new Paint();
        mBgPaint.setColor(BG_COLOR);
        mBgPaint.setAntiAlias(true);
        mBgPaint.setStyle(Paint.Style.FILL);
    }

    //开始搜索
    private void starting() {
        state = STATE_STARTING;
        measure.setPath(searchPath, false);

        //动画
        ValueAnimator animator = ValueAnimator.ofFloat(0, 1).setDuration(START_DURATION);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float v = (float) animation.getAnimatedValue();
                searchPath_dst.reset();
                measure.getSegment(searchLen * v, searchLen, searchPath_dst, true);
                searchPath_dst.rLineTo(0, 0);
                invalidate();

                if (v == 1) {
                    searching();  //进入搜索状态
                }
            }
        });
        animator.start();
    }

    private static final int START_DURATION = 1200;  //开始搜索时动画周期
    private static final int SEARCH_DURATION = 1200;  //正在搜索时动画周期

    //正在搜索
    private void searching() {
        state = STATE_SEARCHING;
        measure.setPath(circlePath, false);

        ValueAnimator animator = ValueAnimator.ofFloat(0, 1).setDuration(SEARCH_DURATION);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float v = (float) animation.getAnimatedValue();
                circlePath_dst.reset();

                float stop = v * circleLen;
                float start = (float) (stop - ((0.5 - Math.abs(v - 0.5)) * 160f));
                measure.getSegment(start, stop, circlePath_dst, true);
                circlePath_dst.rLineTo(0, 0);
                invalidate();
            }
        });
        animator.start();

        postDelayed(new Runnable() {
            @Override
            public void run() {
                if (!isEnd) {
                    searching();
                } else {
                    ending();
                }
            }
        }, SEARCH_DURATION);
    }

    private boolean isEnd = false;

    //结束搜索
    private void ending() {
        state = STATE_ENDDING;
        measure.setPath(searchPath, false);

        //动画
        ValueAnimator animator = ValueAnimator.ofFloat(1, 0).setDuration(START_DURATION);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float v = (float) animation.getAnimatedValue();
                searchPath_dst.reset();
                measure.getSegment(searchLen * v, searchLen, searchPath_dst, true);
                searchPath_dst.rLineTo(0, 0);

                if (v == 0) {
                    state = STATE_INIT;
                    isEnd = false;
                }
                invalidate();
            }
        });
        animator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        drawSearchView(canvas);
    }

    private Path searchPath_dst;
    private Path circlePath_dst;

    private void drawSearchView(final Canvas canvas) {
        canvas.drawPath(bgPath, mBgPaint);

        if (state == STATE_INIT) {
            //初始状态,绘制静态的放大镜
            canvas.drawPath(searchPath, mPaint);
        } else if (state == STATE_STARTING) {
            //开始搜索状态,绘制放大镜缩小到一个点的过程
            canvas.drawPath(searchPath_dst, mPaint);
        } else if (state == STATE_SEARCHING) {
            //正在搜索状态,绘制外层大圆圈转圈
            canvas.drawPath(circlePath_dst, mPaint);
        } else if (state == STATE_ENDDING) {
            //搜索结束,绘制放大镜恢复
            canvas.drawPath(searchPath_dst, mPaint);
        }
    }

    //模拟搜索过程,点击一下开始搜索,再次点击搜索完成
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        if (action == MotionEvent.ACTION_UP) {
            if (state == STATE_INIT) {
                starting();
            } else if (state == STATE_SEARCHING) {
                isEnd = true;
            }
            return true;
        }

        return super.onTouchEvent(event);
    }

    private int mWidth, mHeight;

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        boolean first = (mWidth == 0);
        if (changed) {
            mWidth = getWidth();
            mHeight = getHeight();
        }

        if (first) {
            init();
        }
    }

}

使用方法也很简单,放在xml布局文件中即可,注意需加上android:clickable="true"属性,否则组件无法接收ACTION_UP事件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <com.chinark.appandroidtest.widget.MySearchView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_margin="30dp"
        android:clickable="true" />

</LinearLayout>

参考文章: http://blog.csdn.net/u013831257/article/details/51565591