Android:使用PathMeasure绘制动画效果的搜索按钮
程序员文章站
2022-03-09 19:49:20
...
首先上效果图:
该搜索按钮有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
推荐阅读
-
Android自定义view Path 的高级用法之搜索按钮动画
-
Android中ViewFlipper的使用及设置动画效果实例详解
-
Android使用ListView实现滚轮的动画效果实例
-
Android自定义view仿QQ的Tab按钮动画效果(示例代码)
-
Android使用ListView实现滚轮的动画效果实例
-
Android学习日记——使用约束布局ConstraintLayout制作一个简单的转场动画实现图片扩大效果
-
Android:BottomNavigationView的使用和去掉动画效果
-
使用 CSS3 制作一组超时尚的动画按钮效果_html/css_WEB-ITnose
-
安卓最简单的动画效果实现-使用属性android:animateLayoutChanges="true"
-
Android中使用Animation实现控件的动画效果以及Interpolator和AnimationListener的使用