参考文章:https://blog.csdn.net/chzphoenix/article/details/77767534
先上效果图:
Video_20190327_024709_134.gif
带进度波浪和右上角标的圆球WaveCircleView
public class WaveCircleView extends View {
private final static String TAG = "WaveCircleView";
private Paint mCirclePaint;
private Paint mWavePaint;
private Paint mCornerMarkBgPaint;
private Paint mCornerMarkPaint;
// 下载图标bitmap
private Bitmap mDownloadBitmap;
// 下载图标显示矩形区域
private RectF mDownloadBitmapRectF;
// 角标背景颜色
private int mCornerMarkBgColor = 0xffF0494F;
// 角标文字颜色
private int mCornerMarkTxtColor = 0xffffffff;
// 角标数量
private int mCornerMarkNum = 0;
// 角标背景矩形
private RectF mCornerMarkBgRectF;
// 圆圈颜色
private int mCircleColor = 0xff222222;
// 圆圈宽度
private int mCircleWidth = 4;
// 波浪颜色(透明度太低在混合模式下显示不出来,坑死人)
private int mWaveColor = 0x8839B939;
// 进度(0-100)
private int mProgress = 0;
// 是否正在浪
private boolean mIsFlashing;
// 波浪1的大小
private int mWaveHeight1;
// 波浪2的大小
private int mWaveHeight2;
// 波浪1的周期
private float mWaveCycle1;
// 波浪2的周期
private float mWaveCycle2;
// 当前波浪一的偏移
private int mOffset1 = 0;
// 当前波浪二的偏移
private int mOffset2 = 0;
// 波浪一的偏移速度
private int mSpeed1;
// 波浪一的偏移速度
private int mSpeed2;
// 遮罩模式
PorterDuffXfermode mPorterDuffXfermode;
// 遮罩bitmap
private Bitmap mMaskBitmap;
// progress属性动画
private ObjectAnimator mProgressObjectAnimator;
// 波浪逐渐平静下来的动画
private ObjectAnimator mWaveStopAnimator;
// 一直浪的动画
private ValueAnimator mFlashingAnimator;
// 圆圈的直径
private int mOriginHeight;
// 角标距圆圈顶部的距离
private int mCornerOutTopHeight;
// 角标宽度
private int mCornerWidth;
// 角标高度
private int mCornerHeight;
public WaveCircleView(Context context) {
this(context, null);
}
public WaveCircleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveCircleView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 初始化
*/
private void init() {
mCirclePaint = new Paint();
mCirclePaint.setStyle(Paint.Style.STROKE);
mCirclePaint.setColor(mCircleColor);
mCirclePaint.setStrokeWidth(mCircleWidth);
mCirclePaint.setAntiAlias(true);
mWavePaint = new Paint();
mWavePaint.setColor(mWaveColor);
mWavePaint.setStyle(Paint.Style.FILL);
mWavePaint.setAntiAlias(true);
mCornerMarkBgPaint = new Paint();
mCornerMarkBgPaint.setColor(mCornerMarkBgColor);
mCornerMarkBgPaint.setAntiAlias(true);
mCornerMarkPaint = new Paint();
mCornerMarkPaint.setColor(mCornerMarkTxtColor);
mCornerMarkPaint.setTextSize(ResourceUtil.sp2px(getContext(),10));
mCornerMarkPaint.setTextAlign(Paint.Align.CENTER);
mCornerMarkPaint.setAntiAlias(true);
mPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
mDownloadBitmap =
((BitmapDrawable)getResources().getDrawable(R.drawable.ic_xiazai_jiantou)).getBitmap();
initFlashingAnimator();
mCornerOutTopHeight = ResourceUtil.dip2px(getContext(),2);
mCornerWidth = ResourceUtil.dip2px(getContext(),18);
mCornerHeight = ResourceUtil.dip2px(getContext(),12);
}
private void initFlashingAnimator() {
mFlashingAnimator = ValueAnimator.ofInt(100);
mFlashingAnimator.setDuration(1000);
mFlashingAnimator.setRepeatCount(ValueAnimator.INFINITE);
mFlashingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if(mIsFlashing) {
mOffset1 += mSpeed1;
mOffset2 += mSpeed2;
invalidate();
}
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec)
+ ResourceUtil.dip2px(getContext(),9),
MeasureSpec.getMode(widthMeasureSpec)),
MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec)
+ mCornerOutTopHeight,
MeasureSpec.getMode(heightMeasureSpec)));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mOriginHeight = h - mCornerOutTopHeight - mCircleWidth*2;
Log.d(TAG,
"onSizeChanged() called with: w = [" + w + "], h = [" + h + "], oldw = [" + oldw + "], oldh = [" + oldh + "]");
if(mOriginHeight > 0){
mSpeed1 = mOriginHeight / 20;
mSpeed2 = mOriginHeight / 30;
mWaveHeight1 = ResourceUtil.dip2px(getContext(),10);
mWaveHeight2 = ResourceUtil.dip2px(getContext(),5);
if(mOriginHeight/10 < mWaveHeight1){
mWaveHeight1 = mOriginHeight / 10;
mWaveHeight2 = mOriginHeight / 20;
}
mCornerMarkBgRectF = new RectF(w-mCornerWidth,
0,
w,
mCornerHeight);
initStopAnimator(mWaveHeight1,mWaveHeight2);
mWaveCycle1 = (float) (3 * Math.PI / mOriginHeight);
mWaveCycle2 = (float) (4 * Math.PI / mOriginHeight);
mMaskBitmap = Bitmap.createBitmap(mOriginHeight,mOriginHeight,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mMaskBitmap);
canvas.drawOval(new RectF(0,0,
mOriginHeight,
mOriginHeight),
mWavePaint);
mDownloadBitmapRectF = new RectF(mOriginHeight/4f+mCircleWidth,
mCornerOutTopHeight+mOriginHeight/4f+mCircleWidth,
mOriginHeight*3/4f+mCircleWidth,
h-mOriginHeight/4f-mCircleWidth);
}
}
public int getCornerMarkNum() {
return mCornerMarkNum;
}
public void setCornerMarkNum(int mCornerMarkNum) {
this.mCornerMarkNum = mCornerMarkNum;
if(!mIsFlashing){
invalidate();
}
}
public boolean getIsFlashing() {
return mIsFlashing;
}
// 用于databinding
public void setIsFlashing(boolean mIsFlashing) {
if(mIsFlashing){
start();
}else{
stop();
}
}
public int getWaveHeight1() {
return mWaveHeight1;
}
@Keep
public void setWaveHeight1(int mWaveHeight1) {
this.mWaveHeight1 = mWaveHeight1;
}
public int getWaveHeight2() {
return mWaveHeight2;
}
@Keep
public void setWaveHeight2(int mWaveHeight2) {
this.mWaveHeight2 = mWaveHeight2;
}
public int getWaveProgress() {
return mProgress;
}
@Keep
public void setWaveProgress(int mProgress) {
// 如果进度跨度达到20,启用过渡动画
if(Math.abs(mProgress - this.mProgress) >= 20){
progressAnimStart(mProgress);
}else {
this.mProgress = mProgress;
}
}
/**
* 开始浪
*/
public void start(){
mIsFlashing = true;
if(!mFlashingAnimator.isStarted() || !mFlashingAnimator.isRunning()){
mFlashingAnimator.start();
}
}
/**
* 停止浪
*/
public void stop(){
mFlashingAnimator.cancel();
mWaveStopAnimator.start();
}
/**
* 创建波浪停止动画
* 两条波浪振幅逐渐减小
*/
private void initStopAnimator(final int waveHeightA, final int waveHeightB){
PropertyValuesHolder holderA = PropertyValuesHolder.ofInt("WaveHeight1", 0);
PropertyValuesHolder holderB = PropertyValuesHolder.ofInt("WaveHeight2", 0);
mWaveStopAnimator = ObjectAnimator.ofPropertyValuesHolder(this, holderA, holderB);
mWaveStopAnimator.setDuration(1000);
mWaveStopAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
mIsFlashing = false;
mWaveHeight1 = waveHeightA;
mWaveHeight2 = waveHeightB;
}
@Override
public void onAnimationCancel(Animator animation) {
mWaveHeight1 = waveHeightA;
mWaveHeight2 = waveHeightB;
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
mWaveStopAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//改变曲线的偏移,达到波浪运动的效果
mOffset1 += mSpeed1;
mOffset2 += mSpeed2;
invalidate();
}
});
}
/**
* 缓慢涨/降进度
* @param progress
*/
public void progressAnimStart(int progress){
if(mProgressObjectAnimator != null && mProgressObjectAnimator.isRunning()){
mProgressObjectAnimator.cancel();
}
if(mWaveStopAnimator != null && mWaveStopAnimator.isRunning()){
mWaveStopAnimator.cancel();
}
if(!mIsFlashing) {
start();
}
mProgressObjectAnimator = ObjectAnimator.ofInt(this,"WaveProgress",progress);
mProgressObjectAnimator.setDuration(1000);
mProgressObjectAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float radius = mOriginHeight/2f;
// 画圆圈
canvas.drawCircle(mCircleWidth+radius,
mCircleWidth+radius + mCornerOutTopHeight,mCircleWidth/2f+radius,
mCirclePaint);
canvas.drawColor(Color.TRANSPARENT);
// 将以下内容存储为一个层
int sc = canvas.saveLayer(mCircleWidth,mCircleWidth+mCornerOutTopHeight,
mOriginHeight+mCircleWidth,
mCornerOutTopHeight+mOriginHeight+mCircleWidth,null,Canvas.ALL_SAVE_FLAG);
if(mIsFlashing){
for (int i = 0; i <= mOriginHeight; i++) {
canvas.drawLine(i+mCircleWidth,
(float) getWaveY(i,mOffset1,mWaveHeight1,mWaveCycle1) + mCornerOutTopHeight + mCircleWidth,
i+mCircleWidth,
mOriginHeight + mCircleWidth + mCornerOutTopHeight,
mWavePaint);
canvas.drawLine(i+mCircleWidth,
(float) getWaveY(i,mOffset2,mWaveHeight2,mWaveCycle2) + mCornerOutTopHeight + mCircleWidth,
i+mCircleWidth,
mOriginHeight + mCircleWidth + mCornerOutTopHeight,
mWavePaint);
}
}else{// 如果没有浪就风平浪静
float height = (1 - mProgress / 100.0f) * mOriginHeight;
canvas.drawRect(mCircleWidth, height + mCornerOutTopHeight + mCircleWidth,
mOriginHeight + mCircleWidth, getHeight() - mCircleWidth, mWavePaint);
canvas.drawRect(mCircleWidth, height + mCornerOutTopHeight + mCircleWidth, mOriginHeight + mCircleWidth
, getHeight() - mCircleWidth, mWavePaint);
}
// 画遮罩
mWavePaint.setXfermode(mPorterDuffXfermode);
canvas.drawBitmap(mMaskBitmap,mCircleWidth,mCornerOutTopHeight+mCircleWidth,
mWavePaint);
mWavePaint.setXfermode(null);
canvas.restoreToCount(sc);
// 画下载图标
canvas.drawBitmap(mDownloadBitmap,null,mDownloadBitmapRectF
,null);
if(mCornerMarkNum != 0) {
// 画角标背景
canvas.drawRoundRect(mCornerMarkBgRectF, mCornerHeight/2f,
mCornerHeight/2f, mCornerMarkBgPaint);
// 画角标文字
canvas.drawText(String.valueOf(mCornerMarkNum>99 ? 99 : mCornerMarkNum),
mOriginHeight+2*mCircleWidth,
ResourceUtil.dip2px(getContext(), 9),
mCornerMarkPaint);
}
}
/**
* 波浪的函数,用于求y值
* 函数为a*sin(b*(x + c))+d
* @param x x轴
* @param offset 偏移
* @param waveHeight 振幅
* @param waveCycle 周期
* @return
*/
private double getWaveY(int x, int offset, int waveHeight, float waveCycle) {
if(mProgress == 100){
return 0;
}
return waveHeight * Math.sin(waveCycle * (x + offset)) + (1 - mProgress / 100.0) * mOriginHeight;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if(mProgressObjectAnimator != null && mProgressObjectAnimator.isRunning()){
mProgressObjectAnimator.cancel();
mProgressObjectAnimator.removeAllListeners();
mProgressObjectAnimator = null;
}
if(mFlashingAnimator != null && mFlashingAnimator.isRunning()){
mFlashingAnimator.cancel();
mFlashingAnimator.removeAllListeners();
mFlashingAnimator = null;
}
if(mWaveStopAnimator != null && mWaveStopAnimator.isRunning()){
mWaveStopAnimator.cancel();
mWaveStopAnimator.removeAllListeners();
mWaveStopAnimator = null;
}
}
}
原理:
画出两条正弦曲线与x轴的交叉部分,然后用一个圆形遮罩取出想要的部分。然后通过调整振幅和偏移达到波浪效果。
写于2019-3-27
网友评论