自定义view:波浪

本文介绍如何使用Android的ValueAnimator和贝塞尔曲线实现波浪动画效果。通过不断改变关键点的位置并重新绘制视图来模拟波浪滚动的过程。文章提供了两个自定义View的实现示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原理图:

这里写图片描述

图中的矩形为手机屏幕。
通过属性动画类ValueAnimator不断改变点0的横坐标,随着点0横坐标向右移动,点1,点2,点3,点4,点5,以及四个控制点的坐标随着点0的移动同时位移相同距离,每一次坐标点更新,我们调用一次invalidate()方法,调用draw重新绘制视图,绘制四段贝塞尔曲线。最后点0移动一个波长到原先点2的位置,这样就完成了一次动画。
这样,通过循环不断的动画效果,我们就实现了波浪的效果。

参考:
android贝塞尔曲线之波浪效果
Android贝塞尔曲线————波浪效果(大波浪)
Android-贝塞尔曲线

自定义view1:


package com.android.imooc;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;


public class MyWaveView extends View {

    private Paint paint;

    //view 的宽高
    private int mWidth;//等于一个波长
    private int mHeight;

    //浪
    private int waveHeight = 100;// 波幅

    //偏移
    private float offset = 0f;//偏移量
    private int baseLine = 0;// 基线,用于控制水位上涨下落的
    private Path mPath;
    public MyWaveView(Context context) {
        this(context, null);
    }

    public MyWaveView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyWaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public MyWaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        init();
    }

    private void init() {
        mPath = new Path();//构造方法中创建对象
        paint = new Paint();
        paint.setDither(true);
        paint.setAntiAlias(true);
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.FILL);//填充
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mWidth = w;
        mHeight = h;
        baseLine = mHeight / 2;//view的一半高度为基线

        Log.e("111", "===onSizeChanged===");
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.e("111", "===onLayout===");

        ValueAnimator mAnimator = ValueAnimator.ofFloat(0, mWidth);//移动一个波长
        mAnimator.setInterpolator(new LinearInterpolator());
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                offset = (float) animation.getAnimatedValue();//不断的设置偏移量,并重画
                postInvalidate();
            }
        });
        mAnimator.setDuration(2000);
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("111", "===onDraw===");

        mPath.reset();//重置path
        mPath.moveTo(-mWidth / 2 * 3, baseLine);//起始坐标

        //关键部分
        for (int i = -3; i < 2; i++) {
            int startX = i * mWidth / 2;
            //二阶贝塞尔曲线
            mPath.quadTo(startX + mWidth / 4 + offset,//控制点的X
                    getWaveHeight(i),//控制点的Y
                    startX + mWidth / 2 + offset,//结束点的X
                    baseLine//结束点的Y
            );
        }
        mPath.lineTo(mWidth, mHeight);
        mPath.lineTo(0, mHeight);
        mPath.close();
        canvas.drawPath(mPath, paint);

        //参考线
        paint.setColor(Color.RED);
        paint.setStrokeWidth(10);
        canvas.drawLine(0, mHeight / 2, mWidth, mHeight / 2, paint);
        paint.setColor(Color.BLUE);
    }

    private int getWaveHeight(int num) {
        if (num % 2 == 0) {
            return baseLine + waveHeight;
        }
        return baseLine - waveHeight;
    }

}

自定义view2:


import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;

public class MyWaveViewI extends View {

    private Paint paint;

    //view 的宽高
    private int mWidth;//等于一个波长
    private int mHeight;

    //波幅
    private float mWaveHeight = 100f;

    private int baseLine = 0;// 基线,用于控制水位上涨下落的

    private float offset;

    private PointF startP;
    private PointF p0, p1, p2, p3, p4, p5;
    private PointF ctrlP0, ctrlP1, ctrlP2, ctrlP3, ctrlP4;

    public MyWaveViewI(Context context) {
        this(context, null);
    }

    public MyWaveViewI(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyWaveViewI(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public MyWaveViewI(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        init();
    }

    private void init() {

        paint = new Paint();
        paint.setDither(true);//抗抖动
        paint.setAntiAlias(true);//抗锯齿
        paint.setColor(Color.RED);//色值
        paint.setStyle(Paint.Style.FILL);//填充

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.e("111", "===onSizeChanged===");

        mWidth = w;
        mHeight = h;
        //view的一半高度为基线
        baseLine = mHeight / 2;

        //起点
        startP = new PointF(-mWidth * 3 / 2, baseLine);
        p0 = new PointF(-mWidth * 3 / 2, baseLine);
        p1 = new PointF(-mWidth, baseLine);
        p2 = new PointF(-mWidth / 2f, baseLine);
        p3 = new PointF(0, baseLine);
        p4 = new PointF(mWidth / 2f, baseLine);
        p5 = new PointF(mWidth, baseLine);
        //贝塞尔曲线控制点
        ctrlP0 = new PointF(-mWidth * 5 / 4f, baseLine + mWaveHeight);
        ctrlP1 = new PointF(-mWidth * 3 / 4f, baseLine - mWaveHeight);
        ctrlP2 = new PointF(-mWidth / 4f, baseLine + mWaveHeight);
        ctrlP3 = new PointF(mWidth / 4f, baseLine - mWaveHeight);
        ctrlP4 = new PointF(mWidth * 3 / 4f, baseLine + mWaveHeight);

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.e("111", "===onLayout===");

        ValueAnimator mAnimator = ValueAnimator.ofFloat(0, mWidth);//移动一个波长
        mAnimator.setInterpolator(new LinearInterpolator());
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                offset = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        mAnimator.setDuration(2000);
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("111", "===onDraw===");

        Path mPath = new Path();

        mPath.moveTo(startP.x, startP.y);

        mPath.quadTo(ctrlP0.x + offset, ctrlP0.y, p1.x + offset, p1.y);
        mPath.quadTo(ctrlP1.x + offset, ctrlP1.y, p2.x + offset, p2.y);
        mPath.quadTo(ctrlP2.x + offset, ctrlP2.y, p3.x + offset, p3.y);
        mPath.quadTo(ctrlP3.x + offset, ctrlP3.y, p4.x + offset, p4.y);
        mPath.quadTo(ctrlP4.x + offset, ctrlP4.y, p5.x + offset, p5.y);

        mPath.lineTo(p5.x, mHeight);
        mPath.lineTo(p3.x, mHeight);
        mPath.lineTo(p3.x, p3.y);
        mPath.close();
        canvas.drawPath(mPath, paint);

        //参考线
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(10);
        canvas.drawLine(0, mHeight / 2, mWidth, mHeight / 2, paint);
        paint.setColor(Color.RED);
    }


}

布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <com.android.imooc.MyWaveView
        android:layout_width="240dp"
        android:layout_height="240dp"
        android:background="#e4e416" />

    <View
        android:layout_width="match_parent"
        android:layout_height="20px"
        android:background="#ffffff" />

    <com.android.imooc.MyWaveViewI
        android:id="@+id/wv1"
        android:layout_width="240dp"
        android:layout_height="240dp"
        android:background="#e4e416" />


</LinearLayout>

效果图:

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值