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;
}
下面是三种状态的分解效果图:
开始动画
搜索动画
结束动画
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();
}
}
好了,剩下的就是去直接调用就好了,到这里,内容就全部结束了。