在实现搜索功能的时候,比如蓝牙搜索,附近热点搜索等,通常我们需要一个比较友好的界面,以下通过自定义View来实现一个搜索界面。
当实现一个这样的动画的时候,思路是这样的呢?将整个View拆分,可以分为三个部分。
第一部分: 实现中间的图片
第二部分: 实现扩散的圆
第三部分: 实现游标转动
这样一个酷炫的搜索效果就出来了,用到的资源文件主要有两张图片:
首先自定义一个类继承自View,实现对应的构造方法,添加自定义属性。上篇有详细的实现流程。
相关代码如下:
定义两张图片的属性
<declare-styleable name="SearchAnimation">
<attr name="drawable_search_center" format="reference"/>
<attr name="drawable_search_cursor" format="reference"/>
declare-styleable>
获取属性:
/**
* 取得相关自定义属性
* @param context
* @param attrs
*/
private void initAttrs(Context context,AttributeSet attrs){
mContext = context;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SearchAnimation);
drawableSearchCenter = typedArray.getDrawable(R.styleable.SearchAnimation_drawable_search_center);
drawableSearchCursor = typedArray.getDrawable(R.styleable.SearchAnimation_drawable_search_cursor);
typedArray.recycle();
}
1 绘制中心图片
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//首先来绘制中心的图片
dsCenterW = drawableSearchCenter.getIntrinsicWidth(); //获取到中心图片的宽高
dsCenterH = drawableSearchCenter.getIntrinsicHeight();
centerX = ViewSizeHelper.getDeviceWidth((Activity) mContext)/2; //获取到屏幕的宽高
centerY = ViewSizeHelper.getDeviceHeight((Activity) mContext)/2;
drawableSearchCenter.setBounds(centerX- (dsCenterW/2),centerY- (dsCenterH/2), centerX+ (dsCenterW/2),centerY+(dsCenterH/2)); //指定Drawable的绘制区域
drawableSearchCenter.draw(canvas); //绘制drawable到画布上
}
中心图片就简单的实现了。
2 实现扩散圆
圆的动态变化,使用ValueAnimator来实现。通过改变圆的半径,不断的重绘就可以实现这种效果了,
canvas.save();
canvas.drawCircle(centerX, centerY, radius, mPaint);
canvas.restore();
重点是实现radius的变化,这里使用ValueAnimator,自定义Evalutor来实现(使用ofObject )
下面来看如何实现自定义的Evalutor
首先定义一个类
/**
* Created by Mirko on 2016/11/9 20:47.
*/
public class SearchRadius {
private int radius;
public SearchRadius(int radius){
this.radius = radius;
}
public int getRadius() {
return radius;
}
public void setRadius(int radius) {
this.radius = radius;
}
}
创建自定义的Evalutor
/**
* Created by Mirko on 2016/11/9 20:50.
*/
public class RadiusEvaluator implements TypeEvaluator<SearchRadius> {
@Override
public SearchRadius evaluate(float fraction, SearchRadius startValue, SearchRadius endValue) {
int start = startValue.getRadius();
int end = endValue.getRadius();
int curValue = (int)(start + fraction * (end - start)); //根据初始值和结束值计算出当前值。
return new SearchRadius(curValue);
}
}
完成上述步骤后,来实现圆的扩散动画,代码如下:
private void doAnimCicle1(){
//这里起始圆以中心圆的高度作直径
ValueAnimator valueAnimator = ValueAnimator.ofObject(new RadiusEvaluator(),new SearchRadius(dsCenterH/2),new SearchRadius(centerX));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circleRadius1 = (SearchRadius)animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(delayTime); //控制动画的执行时间
valueAnimator.setInterpolator(new DecelerateInterpolator()); //这里使用减速插值器
valueAnimator.setRepeatMode(ValueAnimator.RESTART); //设置重复方式
valueAnimator.setRepeatCount(ValueAnimator.INFINITE); //设置无限重复
valueAnimator.start();
}
然后在初始化的时候调用 doAnimCicle1方法来启动动画
实现代码比较简单,在这里通过ofObject 自定义Evalutor的方式来实现,熟悉一下自定义Evalutor的用法,此处使用ofFloat
就可以实现,实际使用 ofFloat 就可以了。
现在在onDraw 里面绘制当前的圆
canvas.save();
canvas.drawCircle(centerX, centerY, circleRadius1.getRadius(), mPaint);
canvas.restore();
效果如下:
一个圆绘制成功了,接着就可以绘制余下的圆了,实现原理一样,
private void doAnimCicle2(){
ValueAnimator valueAnimator = ValueAnimator.ofObject(new RadiusEvaluator(),new SearchRadius(dsCenterH/2),new SearchRadius(centerX));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circleRadius1 = (SearchRadius)animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(delayTime); //控制动画的执行时间
valueAnimator.setStartDelay(delayTime/4); //设置延时开始的时间
valueAnimator.setInterpolator(new DecelerateInterpolator()); //这里使用减速插值器
valueAnimator.setRepeatMode(ValueAnimator.RESTART); //设置重复方式
valueAnimator.setRepeatCount(ValueAnimator.INFINITE); //设置无限重复
valueAnimator.start();
}
... 省略其他的实现。
在这里通过延时启动动画,实现圆的先后绘制顺序setStartDelay根据实际需要设置,在这里,将delayTime 分成4份,均匀分配,看起来均匀变化。
4个圆的效果有了,此时并没有颜色慢慢变淡的效果,只需要在每次绘制圆形的时候将画笔的透明度改变就可以了
mPaint.setAlpha(((centerX-circleRadius2.getRadius())*circleAlpha)/centerX);
这里计算出的结果根据圆的半径增大而减小。
3 实现游标转动
//绘制光标的图片
canvas.save();
dsCursorW = drawableSearchCursor.getIntrinsicWidth();
dsCursorH = drawableSearchCursor.getIntrinsicHeight();
drawableSearchCursor.setBounds(centerX- (dsCursorW/2),centerY- (dsCursorH/2), centerX+ (dsCursorW/2),centerY+(dsCursorH/2));
drawableSearchCursor.draw(canvas);
canvas.restore();
此时,游标图片已经绘制好了,下面来实现它的转动,只需要不断的旋转画布,就可以实现转动效果。
先来获取旋转角度的变化值
private void doAnimCursor(){
ValueAnimator valueAnimator = ValueAnimator.ofInt(0,centerX);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circleRadius = (int)animation.getAnimatedValue();
degree = (circleRadius*2)*360/centerX; //画布从0到360度进行旋转,*2表示一个圆动作完,游标转动2圈
invalidate();
}
});
valueAnimator.setDuration(delayTime);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.start();
}
在获取到变化的角度值后通过
canvas.rotate(degree,centerX,centerY); //旋转画布,实现游标以中心点旋转
就可以实现一个动态的效果。至此,就已经实现了开篇的效果
SearchAnimation
/**
* Created by Mirko on 2016/11/9 19:09.
*/
public class SearchAnimation extends View{
private Context mContext;
private Paint mPaint;
private int centerX,centerY;//屏幕的中心点
private int dsCenterW,dsCenterH;//中心图片的宽高
private int dsCursorW,dsCursorH;//光标图片的宽高
private int strokWidth = 5; //画笔大小
private int circleAlpha = 70;
private int delayTime = 8000; //一个圆动画执行的时间
private float degree;
private int circleRadius ;
private SearchRadius circleRadius1 = new SearchRadius(0);
private SearchRadius circleRadius2 = new SearchRadius(0);
private SearchRadius circleRadius3 = new SearchRadius(0);
private SearchRadius circleRadius4 = new SearchRadius(0);
private float circleRadiusF;
private ValueAnimator valueAnimator;
private Drawable drawableSearchCursor;
private Drawable drawableSearchCenter;
public SearchAnimation(Context context) {
super(context);
}
public SearchAnimation(Context context, AttributeSet attrs) {
super(context, attrs);
init(context,attrs);
}
public SearchAnimation(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context,attrs);
}
private void init(Context context,AttributeSet attrs){
initAttrs(context,attrs);
initPaint();
initAnim();
}
/**
* 取得相关自定义属性
* @param context
* @param attrs
*/
private void initAttrs(Context context,AttributeSet attrs){
mContext = context;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SearchAnimation);
drawableSearchCenter = typedArray.getDrawable(R.styleable.SearchAnimation_drawable_search_center);
drawableSearchCursor = typedArray.getDrawable(R.styleable.SearchAnimation_drawable_search_cursor);
typedArray.recycle();
}
private void initPaint(){
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0X46FC3232);
}
private void initAnim(){
dsCenterW = drawableSearchCenter.getIntrinsicWidth(); //获取到中心图片的宽高
dsCenterH = drawableSearchCenter.getIntrinsicHeight();
centerX = ViewSizeHelper.getDeviceWidth((Activity) mContext)/2; //获取到屏幕的宽高
centerY = ViewSizeHelper.getDeviceHeight((Activity) mContext)/2;
doAnimCicle1();
doAnimCicle2();
doAnimCicle3();
doAnimCicle4();
doAnimCursor();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//首先来绘制中心的图片
drawableSearchCenter.setBounds(centerX- (dsCenterW/2),centerY- (dsCenterH/2), centerX+ (dsCenterW/2),centerY+(dsCenterH/2));
drawableSearchCenter.draw(canvas);
//绘制光标的图片
canvas.save();
canvas.rotate(degree,centerX,centerY); //旋转画布,实现游标的旋转
dsCursorW = drawableSearchCursor.getIntrinsicWidth();
dsCursorH = drawableSearchCursor.getIntrinsicHeight();
drawableSearchCursor.setBounds(centerX- (dsCursorW/2),centerY- (dsCursorH/2), centerX+ (dsCursorW/2),centerY+(dsCursorH/2));
drawableSearchCursor.draw(canvas);
canvas.restore();
//绘制圆,4个
mPaint.setStrokeWidth(strokWidth);
canvas.save();
mPaint.setAlpha(((centerX-circleRadius1.getRadius())*circleAlpha)/centerX); //设置透明度
canvas.drawCircle(centerX, centerY, circleRadius1.getRadius(), mPaint);
canvas.restore();
canvas.save();
mPaint.setAlpha(((centerX-circleRadius2.getRadius())*circleAlpha)/centerX);
canvas.drawCircle(centerX, centerY, circleRadius2.getRadius(), mPaint);
canvas.restore();
canvas.save();
mPaint.setAlpha(((centerX-circleRadius3.getRadius())*circleAlpha)/centerX);
canvas.drawCircle(centerX, centerY, circleRadius3.getRadius(), mPaint);
canvas.restore();
canvas.save();
mPaint.setAlpha(((centerX-circleRadius4.getRadius())*circleAlpha)/centerX);
canvas.drawCircle(centerX, centerY, circleRadius4.getRadius(), mPaint);
canvas.restore();
}
private void setAnimParams(ValueAnimator valueAnimator){
valueAnimator.setDuration(delayTime);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.start();
}
private void doAnimCicle1(){
ValueAnimator valueAnimator = ValueAnimator.ofObject(new RadiusEvaluator(),new SearchRadius(dsCenterH/2),new SearchRadius(centerX));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circleRadius1 = (SearchRadius)animation.getAnimatedValue();
invalidate();
}
});
setAnimParams(valueAnimator);
}
private void doAnimCicle2(){
ValueAnimator valueAnimator = ValueAnimator.ofObject(new RadiusEvaluator(),new SearchRadius(dsCenterH/2),new SearchRadius(centerX));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circleRadius2 = (SearchRadius)animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.setStartDelay(delayTime/4);
setAnimParams(valueAnimator);
}
private void doAnimCicle3(){
ValueAnimator valueAnimator = ValueAnimator.ofObject(new RadiusEvaluator(),new SearchRadius(dsCenterH/2),new SearchRadius(centerX));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circleRadius3 = (SearchRadius)animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.setStartDelay(delayTime/2);
setAnimParams(valueAnimator);
}
private void doAnimCicle4(){
ValueAnimator valueAnimator = ValueAnimator.ofObject(new RadiusEvaluator(),new SearchRadius(dsCenterH/2),new SearchRadius(centerX));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circleRadius4 = (SearchRadius)animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.setStartDelay(delayTime/4*3);
setAnimParams(valueAnimator);
}
private void doAnimCursor(){
ValueAnimator valueAnimator = ValueAnimator.ofInt(0,centerX);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circleRadius = (int)animation.getAnimatedValue();
degree = (circleRadius*2)*360/centerX; //画布从0到360度进行旋转
invalidate();
}
});
valueAnimator.setDuration(delayTime);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.start();
}
}
activity_search.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#e8e8e8"
android:orientation="vertical">
<com.mirko.customeitem.view.SearchAnimation
android:layout_width="match_parent"
android:layout_height="match_parent"
app:drawable_search_cursor="@drawable/search_circle"
app:drawable_search_center="@drawable/search_circle_center" />
LinearLayout>