版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
这个水波纹动画主要分为两大块,水波纹跟运动的小船。
1.水波纹采用正余弦函数的图像,当进行绘制的时候,图中水平红线部分的一个波长分为两个贝塞尔曲线进行绘制。重复直至充满手机屏幕。 2.往屏幕最左边超出多画一个波长。动画执行的时候整个绘制图形不停的往右运动,当左边超出屏幕的波长整个进入屏幕时候,重新执行。
小船随水波纹上下移动,我们可以在竖直方向上去一个很小的矩形区域(竖直红线),取这个矩形区域与水波纹的相交区域,把小船绘制的这个区域的最高点即可。(小船中心跟水波纹会有偏差,但是很小,基本看不出来)
图片的缩放这边是随意采用了2,严谨的话因根据比例进行计算,然后进行缩放。
onMeasure 方法主要是进行宽高的测量与记录。另外是对水位的初始位置 originY 进行赋值,当 xml 中没有对水位位置属性进行设置时候,水位应该处于屏幕底部,即 originY 为屏幕高度。可是,当初始化属性的时候,onMeasure 方法尚未执行,屏幕高度也未可知,所以只能在这边对 originY 重新进行赋值。
onDraw 方法每次都要重新获取水波纹的路径,水平初始位置计算上 dx,动画只需要改变 dx 的值,即可实现水波纹的水平移动;竖直方向计算上 dy;则可以实现水位的上升。 小船的位置确认,取一个宽度很小(0.1)的矩形与水波纹区域相交,取相交区域的最左上方点作为小船的中心进行绘制。(这样小船的中心实际与水波纹路径有所偏差,但是基本感觉不出来)
动画这边先不解释,后面再说。
完整 WaveView 代码。
public class WaveView extends View { //波长 private int waveLength; //波峰的高度 private int waveHeight; //小船图片的id private int waveView_boatBitmap; //水位的初始位置 private int duration; //水位上涨速度 private int originY; //波浪画笔 private Paint paint; //波浪路径 private Path path; //自定义控件的宽高 private int width; private int height; //x 和 y 的偏移量 private int dx; private int dy; //小船图片 private Bitmap mBitmap; //小船位置获取辅助区域 private Region region; public WaveView(Context context) { this(context, null); } public WaveView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public WaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, 0, defStyleAttr); } public WaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initAttrs(context,attrs); init(); } private void initAttrs(Context context, AttributeSet attrs) { TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.WaveView); waveLength = (int) typeArray.getDimension(R.styleable.WaveView_waveLength, 400); waveHeight = (int) typeArray.getDimension(R.styleable.WaveView_waveHeight, 200); originY = (int) typeArray.getDimension(R.styleable.WaveView_originY, 2000); duration = (int) typeArray.getDimension(R.styleable.WaveView_duration, 0); waveView_boatBitmap = (int) typeArray.getDimension(R.styleable.WaveView_boatBitmap, 0); typeArray.recycle(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; // 缩放图片 if(waveView_boatBitmap>0){ mBitmap = BitmapFactory.decodeResource(getResources(), waveView_boatBitmap, options); }else{ mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg, options); } } private void init() { paint = new Paint(); paint.setColor(getResources().getColor(R.color.waterColor)); paint.setStyle(Paint.Style.FILL_AND_STROKE); path = new Path(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); width = MeasureSpec.getSize(widthMeasureSpec); height = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(width, height); //没有设置水位初始位置的时候,从最底部开始 if(originY == 0){ originY = height; } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); setPathData(); canvas.drawPath(path, paint); Rect bounds = region.getBounds(); canvas.drawBitmap(mBitmap, bounds.left - mBitmap.getWidth()/2, bounds.top - mBitmap.getHeight()/2, paint); } //设置波浪路径 private void setPathData() { //每次获取 Path 前需要重置 path.reset(); //往左边多画一个波长,否则动画效果会导致部分空白 path.moveTo(-waveLength + dx, originY - dy); //水平方向从 -waveLength + dx 开始画知道画满整个屏幕宽度 for (int i = -waveLength; i < width; i += waveLength) { //一个波长分上半弧与下半弧 //画上半弧 path.rQuadTo(waveLength/4, waveHeight, waveLength/2, 0); //画下半弧 path.rQuadTo(waveLength/4, -waveHeight, waveLength/2, 0); } path.lineTo(width, height); path.lineTo(0, height); path.close(); float x = width/2; region = new Region(); Region clip = new Region((int)(x-0.1), 0, (int)x, height*2); //用一个矩形区域去切割一个path路径得到一个矩形区域 region.setPath(path, clip); } public void startAnimation() { //dx不断地增加 ValueAnimator animator = ValueAnimator.ofFloat(0,1); animator.setDuration(5000); animator.setInterpolator(new LinearInterpolator()); animator.setRepeatCount(ValueAnimator.INFINITE); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float fraction = (float) animation.getAnimatedValue(); dx = (int) (waveLength*fraction); dy += duration; postInvalidate(); } }); animator.start(); } }代码连接:http://download.csdn.net/download/qq_18983205/9931662
后一节对小船效果进行优化,是小船的船头随波浪上下起伏: