Android 用path 的贝塞尔曲线 绘制 在波涛汹涌的勇闯天涯的 小船
tips1:先用path 贝塞尔曲线 绘制会动的波浪
tips2: 根据波浪周期 , 计算x 所对应的 y 高度
tips3: 启动动画 startAnimation()
最后别忘记下载这个小船哦,别忘记别忘记
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import androidx.annotation.Nullable;
import com.lam.sup.lib.widget.R;
import com.lam.sup.lib.widget.utils.DensityUtil;
import com.lam.sup.lib.widget.utils.MeasureUtils;
/**
* 乘风破浪的小船
* @author : Lambert
* date : 2021/3/4 6:43 PM
*/
public class WaveSlipView extends View {
private Paint mPaint; //用于绘制第1条波浪
private Paint mPaint2; //用于绘制第2条波浪
private int mWidth;
private int mHeight;
private int mWaveHeight; //波浪高度可调节
private int mWaveDx; //波长的长度(这里设置为屏幕的宽度)
private int dx; //偏移量
private Bitmap mSlipBitMap; //船
int slipHeight, slipWidth; //小船图片宽高
public WaveSlipView(Context context) {
this(context, null);
}
public WaveSlipView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveSlipView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.parseColor("#1296db"));
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAlpha(80);
mPaint2 = new Paint();
mPaint2.setAntiAlias(true);
mPaint2.setDither(true);
mPaint2.setColor(Color.parseColor("#1296db"));
mPaint2.setStyle(Paint.Style.FILL);
mPaint2.setAlpha(80);
//波长的长度(这里设置为屏幕的宽度)
mWaveDx = getResources().getDisplayMetrics().widthPixels;
mSlipBitMap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_slip);
slipWidth = mSlipBitMap.getWidth();
slipHeight = mSlipBitMap.getHeight();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//控件的宽高
mWidth = MeasureUtils.measureView(widthMeasureSpec, mWaveDx);
mHeight = MeasureUtils.measureView(heightMeasureSpec, 300);
//水波的高度
mWaveHeight = DensityUtil.dip2px(getContext(), 16);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制波浪
drawWave(canvas);
//绘制小船位置
drawSlip(canvas);
}
//绘制波浪
private void drawWave(Canvas canvas) {
Path path = new Path();
path.reset();
Path path2 = new Path();
path2.reset();
Log.d("wave23", " -mWaveDx :" + (-mWaveDx));
Log.d("wave23", " - + dx:" + (+dx));
Log.d("wave23", " -mWaveDx + dx:" + (-mWaveDx + dx));
int x_2 = 50;
path.moveTo(-mWaveDx + dx, mHeight / 2);
path2.moveTo(-mWaveDx + dx - mWaveDx / 4, mHeight / 2);
for (int i = -mWaveDx; i < getWidth() + mWaveDx; i += mWaveDx) {
path.rQuadTo(mWaveDx / 4, -mWaveHeight, mWaveDx / 2, 0);
path.rQuadTo(mWaveDx / 4, mWaveHeight, mWaveDx / 2, 0);
path2.rQuadTo(mWaveDx / 4, -mWaveHeight, mWaveDx / 2, 0);
path2.rQuadTo(mWaveDx / 4, mWaveHeight, mWaveDx / 2, 0);
}
//绘制封闭的区域
path.lineTo(mWidth, mHeight);
path.lineTo(0, mHeight);
//path.close() 绘制封闭的区域
path.close();
canvas.drawPath(path, mPaint);
//绘制封闭的区域
path2.lineTo(mWidth, mHeight);
path2.lineTo(0, mHeight);
//path.close() 绘制封闭的区域
path2.close();
canvas.drawPath(path2, mPaint2);
}
float t = 0; //用于计算 x/y 的高度比
//绘制小船位置
private void drawSlip(Canvas canvas) {
int ht = 0;
float dx2 = Math.abs(dx);
int pointW = getWidth() / 2; //半个周期
float wx = pointW / 2; //半个周期中最高点
if (dx2 > 0 && dx2 < wx) {
// 1/4 周期 (最高点 x= 1/2 π ,y = 1)
t = dx2 / wx;
} else if (dx2 > wx && dx2 < wx * 2) {
// 1/2 周期 (x= 1 π ,y =0)
t = 1 - (dx2 - wx) / wx;
} else if (dx2 > wx * 2 && dx2 < wx * 3) {
// 3/4 周期 (y 最低点 x= 3/2 π ,y = -1)
t = -(dx2 - wx * 2) / wx;
} else if (dx2 > wx * 3 && dx2 < wx * 4) {
// 1个周期 (x= 2 π ,y =0)
t = -1 + (dx2 - wx * 3) / wx;
}
Log.d("wave23", " dx2 :" + dx2);
Log.d("wave23", " t :" + t);
//根据周期x/y 比率计算出高度
ht = (int) (mWaveHeight / 2 * t);
// h 是 x所对应的 y 的高度点
int h = mHeight / 2 - slipHeight - ht;
//绘制小船位置
canvas.drawBitmap(mSlipBitMap, mWidth / 2 - slipWidth / 2, h, null);
}
public void startAnimation() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, mWaveDx);
valueAnimator.setDuration(3500);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//水平方向的偏移量
dx = (int) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
}
}
public class DensityUtil {
public static int dip2px(Context var0, float var1) {
float var2 = var0.getResources().getDisplayMetrics().density;
return ( int ) (var1 * var2 + 0.5F);
}
}
public class MeasureUtils {
/**
* 用于View的测量
* @param measureSpec 测量模式和大小
* @param defaultSize 默认的大小
* @return
*/
public static int measureView(int measureSpec, int defaultSize) {
int measureSize;
//获取用户指定的大小以及模式
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
//根据模式去返回大小
if (mode == MeasureSpec.EXACTLY) {
//精确模式(指定大小以及match_parent)直接返回指定的大小
measureSize = size;
} else {
//UNSPECIFIED模式、AT_MOST模式(wrap_content)的话需要提供默认的大小
measureSize = defaultSize;
if (mode == MeasureSpec.AT_MOST) {
//AT_MOST(wrap_content)模式下,需要取测量值与默认值的最小值
measureSize = Math.min(measureSize, size);
}
}
return measureSize;
}
}