老規矩,先看效果圖吧
device-2018-12-08-162806.gif
View效果是在巨人肩膀上改進的,源自:https://blog.csdn.net/q1242027878/article/details/73832074?utm_source=blogxgwz7
說一下修改的地方:
1.新增圓心的圖片繪制,圖片設置屬性app:imageSrc="";
2.修復點擊事件無效的bug,原因在于其checkIsInCircle()方法判斷是否點擊在此view中心圓圈內邏輯不對;
3.修復原view畫波浪擴散圈第一個會最后一個會重疊問題(仔細觀察原view的動態圖會發現某個圈重疊顯得更粗),原因在與其計算繪制間隔位置有錯
4.新增從中間實心圈位置邊緣開始繪制擴散波紋,并且在第一個和第二個波浪間實現淡出效果,否則看起來第一個波浪出現比較生硬
下面開始貼代碼:
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.theaty.wavetest.R;
import java.util.Timer;
import java.util.TimerTask;
public class WaveView extends View {
private static final String TAG = "ICE";
private Paint mPaint;
private int mRadius;//里面圓圈的半徑
private Context mContext;
private int mWidth;//控件的寬度
/**
* 設置波浪圓圈的寬度
*/
private int mStrokeWidth = 2;
/**
* 中心圓填充顏色
*/
private int mFillColor;
/**
* 最中間的圓的邊框顏色
*/
private int mCircleStrokeColor;
/**
* 字體大小
*/
private int mTextSize = 30;
/**
* 中間圖片padding值大小,即中心圖片距離四周的距離
*/
private int mImagePadding = 50;
/**
* 中間圖片的id
*/
private int mImageSrc = 0;
/**
* 多個擴散波浪間隙大小
*/
private int gapSize;
/**
* 第一個擴散波浪的半徑
*/
private int firstRadius;
/**
* 控制生成擴散波浪的數量
*/
private int numberOfCircle = 4;
/**
* 擴散波浪顏色
*/
private int mLineColor;
/**
*繪制的文字
*/
private String mText;
/**
* 中間文字顏色
*/
private int mTextColor;
private Paint mTextPaint;
private boolean isFirstTime = true;
//點擊事件監聽器
private float mDownX, mDownY;
private OnClickListener mClickListener;
/**
*是否繪制圖片
**/
private boolean isShowBitmap = true;
/**
* 設置波浪擴散的速度,單位毫秒,值越小擴散越快
*/
private int period = 100;
public WaveView(Context context) {
this(context, null);
}
public WaveView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.WaveView);
mText = ta.getString(R.styleable.WaveView_text);
if (mText == null) mText = "";
mTextColor = ta.getColor(R.styleable.WaveView_textColor, Color.BLACK);
mTextSize = ta.getDimensionPixelSize(R.styleable.WaveView_textSize, mTextSize);
mFillColor = ta.getColor(R.styleable.WaveView_fillColor, Color.WHITE);
mLineColor = ta.getColor(R.styleable.WaveView_waveColor, Color.BLACK);
mCircleStrokeColor = ta.getColor(R.styleable.WaveView_strokeColor, mFillColor);
mImagePadding = ta.getDimensionPixelSize(R.styleable.WaveView_imagePadding, mImagePadding);
mImageSrc = ta.getResourceId(R.styleable.WaveView_imageSrc, R.drawable.ic_scan);
ta.recycle(); //注意回收
init(context);
}
private void init(Context context) {
mContext = context;
mWidth = dip2px(50);
mStrokeWidth = 2;
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStyle(Paint.Style.STROKE);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setStrokeCap(Paint.Cap.ROUND);
mTextPaint.setStyle(Paint.Style.STROKE);
mTextPaint.setTextSize(mTextSize);
mTextPaint.setColor(mTextColor);
numberOfCircle = 4;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
switch (widthMode) {
case MeasureSpec.EXACTLY:
//match_parent 或者 精確的數值
mWidth = width;
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
mWidth = Math.min(mWidth, height);
break;
}
mRadius = mWidth / numberOfCircle;
gapSize = (mWidth / 2 - mRadius) / numberOfCircle;
firstRadius = mRadius;// + gapSize;
setMeasuredDimension(mWidth, mWidth);
}
//將圖片按比例縮放
private Bitmap scaleBitmap(int total, Bitmap bitmap) {
int width = total;
//一定要強轉成float 不然有可能由于精度不夠 出現 scale為0 的錯誤
float scale = (float) width / (float) bitmap.getWidth();
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
}
@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);
if (!isShowBitmap) {
//畫文字
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);
} else {
//畫中間的圖片
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mImageSrc);
//這樣縮放僅僅是確認中間的圖片的寬高大小位置,如果再使用這圖片會失真,所以畫的時候還是用原圖
bitmap = scaleBitmap(mRadius - mImagePadding, bitmap);
int bWidth = bitmap.getWidth();
int bHeight = bitmap.getHeight();
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), mImageSrc), null, new RectF(-bWidth, -bHeight, bWidth, bHeight), mPaint);
}
//畫周圍的波浪
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 = 1.0;
if (radius > mRadius + gapSize) {
//后面圓的透明度是淡出
x = (mWidth / 2 - radius) * 1.0 / (mWidth / 2 - mRadius);
} else {
//第一個圓的透明度是淡入效果
x = (radius - mRadius * 1.0) / gapSize;
}
//255*0.8表示最白的時候都是80%
mPaint.setAlpha((int) (255 * 0.8 * x));
canvas.drawCircle(0, 0, radius, mPaint);
}
}
//檢查波浪的半徑 如果小于圓圈,那么加上圓圈的半徑
private int checkRadius(int radius) {
if (radius < mRadius) {
return radius + mRadius;// + gapSize;
}
return radius;
}
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, period);
}
/**
* 設置最中間的圓圈的顏色
*
* @param mCircleStrokeColor
*/
public void setmCircleStrokeColor(int mCircleStrokeColor) {
this.mCircleStrokeColor = mCircleStrokeColor;
}
@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 = (getBottom() - getTop()) / 2;
boolean b = Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2) < Math.pow(mRadius, 2);
return b;
}
public void setText(String text) {
this.mText = text;
invalidate();
}
public int getTextColor() {
return mTextColor;
}
public void setTextColor(int mTextColor) {
this.mTextColor = mTextColor;
}
public String getText() {
return mText;
}
public int getLineColor() {
return mLineColor;
}
public void setLineColor(int mLineColor) {
this.mLineColor = mLineColor;
}
public int getGapSize() {
return gapSize;
}
public void setGapSize(int gapSize) {
this.gapSize = gapSize;
}
public int getFillColor() {
return mFillColor;
}
public void setFillColor(int mFillColor) {
this.mFillColor = mFillColor;
}
public int getTextSize() {
return mTextSize;
}
public void setTextSize(int mTextSize) {
this.mTextSize = mTextSize;
}
public int getStrokeWidth() {
return mStrokeWidth;
}
public void setStrokeWidth(int mStrokeWidth) {
this.mStrokeWidth = mStrokeWidth;
}
public int getPeriod() {
return this.period;
}
public void setPeriod(int period) {
this.period = period;
}
public int getmCircleStrokeColor() {
return this.mCircleStrokeColor;
}
public int getmImagePadding() {
return this.mImagePadding;
}
public void setmImagePadding(int mImagePadding) {
this.mImagePadding = mImagePadding;
}
}
attrs.xml屬性文件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="WaveView">
<attr name="textSize" ></attr>
<attr name="text"></attr>
<attr name="textColor"></attr>
<attr name="fillColor" format="color"/>
<attr name="waveColor" format="color"/>
<attr name="strokeColor" format="color"/>
<attr name="imagePadding" format="dimension"/>
<attr name="imageSrc" format="dimension"/>
</declare-styleable>
</resources>
在activity中的使用:
布局文件:
<com.theaty.wavetest.view.WaveView
android:id="@+id/wave"
android:layout_width="250dp"
android:layout_height="250dp"
app:imagePadding="25dp"
app:fillColor="#FFF"
app:strokeColor="@color/colorMain55"
app:text="點我匹配"
app:textColor="@color/colorAccent"
app:textSize="18sp"
app:imageSrc="@drawable/avatar"
app:waveColor="@color/colorAccent" />
Activtiy:
WaveView waveView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
waveView = findViewById(R.id.wave);
waveView.setPeriod(120);
waveView.setTextSize(waveView.dip2px(18));
waveView.setStrokeWidth(1);
waveView.setTextColor(getResources().getColor(R.color.colorPrimary));
waveView.setmCircleStrokeColor(getResources().getColor(R.color.white));
waveView.setLineColor(getResources().getColor(R.color.white));
waveView.setFillColor(getResources().getColor(R.color.colorMain55));
waveView.setGapSize(30);
waveView.startAnimation();
waveView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "點擊了wave", Toast.LENGTH_SHORT).show();
}
});
}
代碼理解也不難,有問題可以留言一起交流交流, ^_^