前言
劳动节快乐!!!O(∩_∩)O
虽然现在不是一个值得庆祝的时间,因为美好的白天已经过去了,再过不久大家就要回到公司或者课堂了。/(ㄒoㄒ)/~~
想做一个随即匹配按钮,同学建议是做一个像波浪一样向外扩散的按钮,同学在网上找了一个效果图,看上去挺简单的,就自己做了一个,下面是效果图:
我觉得用在只需要一个大按钮的界面里面,是挺合适的。
下面就来分享一下思路与代码。
分析动画
- 中间一个圆是不动的,里面有一个文字区域
- 外面的扩散的圆圈透明度是变化的,从里面向外面越来越透明。
- 最开始是只有一条波纹的,慢慢才变成了3条。
思路
- 先画中间的不动的圆圈。
- 绘制文字区域。
- 绘制周围的圆圈,但是半径要慢慢变大,如果有波纹超出了区域,那么绘制到里面,形成一条新的波纹。
- 不断重复上述过程。
代码
为了节省空间,我省去了初始化代码、onMeasure()函数的代码、还有一些很容易懂的成员变量,大家有需要可以在这里查看完整的源代码。
/**
* Created by ICELEE on 5/1/2017.
*/
public class WaveButton extends View {
private static final String TAG = "ICE";
private Paint mPaint;
private int mRadius;//里面圆圈的半径
private int mWidth;//控件的宽度
private int mStrokeWidth;//波浪的宽度
private int mFillColor;//圆圈填充颜色
private int mCircleStrokeColor;//圆圈边缘颜色
private int gapSize;//波浪之间的距离
private int firstRadius;//第一个圆圈的半径
private int numberOfCircle;//显示波浪的数量
private int mLineColor;//波浪线的颜色
private boolean isFirstTime = true;//是否是第一次开启动画
private OnClickListener mClickListener;//点击事件监听器
private float mDownX,mDownY;//手指按下的坐标
//省略了前面两个少参数的构造函数 源码里面是用前面两个调用这个
public WaveButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context){
//省略了各个成员变量的初始化过程
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//省略了测量的代码 在源码里面有简单的测量
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(mWidth/2,mWidth/2);//平移
//画中间的圆
mPaint.setAlpha(255);
mPaint.setColor(mFillColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(0,0,mRadius,mPaint);
//画圆的边
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setColor(mCircleStrokeColor);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(0,0,mRadius,mPaint);
//画文字
Rect rect = new Rect();//文字的区域
mTextPaint.getTextBounds(mText,0,mText.length(),rect);
int height = rect.height();
int width = rect.width();
canvas.drawText(mText,-width/2,height/2,mTextPaint);
//画周围的波浪
firstRadius += 3;//每次刷新半径增加3像素
firstRadius %= (mWidth/2);//控制在控件的范围中
if(firstRadius<mRadius) isFirstTime =false;
firstRadius = checkRadius(firstRadius);//检查半径的范围
mPaint.setColor(mLineColor);
mPaint.setStyle(Paint.Style.STROKE);
//画波浪
for (int i = 0; i < numberOfCircle; i++) {
int radius = (firstRadius + i*gapSize ) % (mWidth/2);
if(isFirstTime && radius>firstRadius) continue;
radius = checkRadius(radius);//检查半径的范围
//用半径来计算透明度 半径越大 越透明
double x = (mWidth/2 -radius)*1.0 /(mWidth/2 - mRadius);
mPaint.setAlpha((int) (255*x));
canvas.drawCircle(0,0,radius,mPaint);
}
}
//检查波浪的半径 如果小于圆圈,那么加上圆圈的半径
private int checkRadius(int radius) {
if(radius<mRadius){
return radius+mRadius + gapSize;
}
return radius;
}
//dp转像素
public int dip2px(float dpValue) {
final float scale = mContext.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
//不断重绘 展示出波浪效果
public void startAnimation(){
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
postInvalidate();
}
},0,50);
}
@Override
public void setOnClickListener(OnClickListener l) {
mClickListener = l;
}
//设置只有点击圆圈才有点击效果 点击波浪不能触发点击效果
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mDownY = event.getY();
return checkIsInCircle((int)mDownX,(int)mDownY);
case MotionEvent.ACTION_UP:
int upX = (int) event.getX(),upY = (int) event.getY();
if(checkIsInCircle(upX,upY) && mClickListener!=null){
mClickListener.onClick(this);//触发点击事件
}
break;
}
return true;
}
/**
* 检查点x,y是否落在圆圈内
* @param x
* @param y
* @return
*/
private boolean checkIsInCircle(int x, int y){
int centerX = (getRight() + getLeft())/2;
int centerY = (getTop() + getBottom())/2;
return Math.pow(x-centerX,2)+Math.pow(y-centerY,2) < Math.pow(mRadius,2);
}
}
懂了思路其实挺容易就能写出这个,主要是如何让波浪动起来。这里再画个图解释解释:
我们要把波浪的半径radius模上mWidth/2,如果模后的值小于mRadius,也就是radius超出了边界,那么我们再加上mRadius就相当于又出来了一条新的波浪。