Android自定义控件练手——波浪效果

这一次要绘制出波浪效果,也是小白的我第一次还望轻喷。首先当然是展示效果图啦:

    一.首先来说说实现思路。

    想到波浪效果,当然我第一反应是用正余弦波来设计啦(也能通过贝塞尔曲线,这里我不提及这个方法但是在demo里这种方法也实现了),肯定要绘制一个静态的波,然后通过不断的对它平移刷新,这样最简单的波浪效果就有了,如果再给它加一个比它提前一定周期的波一起平移,那不是波浪效果的层次就有了。

    二.绘制。

    首先要绘制一个静态的波形图,嗨呀说来简单但是怎么画呢,不要慌先看下面这张丑图:

    通过上面的图我们发现曲线被切分成了无数的竖线,我们可以知道波浪的高低,就是波峰与波谷,根据函数公式,通过不断的绘制竖线,就能得到一个静态波形图。代码如下:

1  //在宽度以内绘制一条条竖线
2         while (drawPoint.x < mWidth) {
3             //第一条波的y坐标
4             drawPoint.y = (float) (waveHeight - waveDeep * Math.sin(drawPoint.x * anglenum));
5             canvas.drawLine(drawPoint.x, drawPoint.y, drawPoint.x, mHeight, mPaint);
6             //跳到下一个点继续
7             drawPoint.x++;
8         }

   这里我们要注意绘制的默认坐标系如下图:

 

   画出静态的波形图之后,我们只要每次让这个波向前或者向后移动一定周期再不断刷新,就能出现波浪效果了。重写view的ondraw方法就有如下:

 1 drawPoint.x = 0;//重置为0,从原点开始绘制
 2         Double rightperiod = Math.PI / 8 * count;//每次平移Math.PI/8个周期
 3         if (count == 16) {//每次平移Math.PI/8个周期,平移第16次,平移了一个完整的周期
 4             count = 0;//平移了一个完整周期归零重新开始计数
 5         } else {
 6             count++;
 7         }
 8 
 9         //在宽度以内绘制一条条竖线
10         while (drawPoint.x < mWidth) {
11             //第一条波的y坐标
12             drawPoint.y = (float) (waveHeight - waveDeep * Math.sin(drawPoint.x * anglenum - rightperiod));
13             //绘制最上面显示主波的竖线
14             canvas.drawLine(drawPoint.x, drawPoint.y, drawPoint.x, mHeight, mPaint);
15             //跳到下一个点继续
16             drawPoint.x++;
17         }
18         //定时更新
19         postInvalidateDelayed(17);

   这样一条会动的波浪就基本完成了,主体功能基本实现,之后再另外画一个平移一定周期的波浪并且通过用属性动画ValueAnimator对波浪波峰波谷与水位变化的调控就能达到上面的效果。下面是完整的代码:

public class WaveFunctionView extends View {
    private Path mPath;//路径
    private Paint mPaint, mPaintMore;//画笔
    private PointF drawPoint, drawPoint2;//绘制点
    private ValueAnimator animator, animatorh;
    private float mWidth, mHeight, waveHeight;//控件宽,控件高,水位
    private float waveDeepmin = 8f;//最小的波峰与波谷
    private float waveDeepMax = 20f;//最大的波峰与波谷
    private float waveDeep = 8f;//波峰与波谷
    private float arcRa = 0;//圆半径
    private Boolean iscircle = true;//是否是圆形图案
    private Boolean antiAlias = true;//是否开启抗锯齿
    public String MAINCOLOR_DEF = "#0000AA", NEXTCOLOR_DEF = "#0000FF";//默认颜色
    private int mainColor = Color.parseColor(MAINCOLOR_DEF), nextColor = Color.parseColor(NEXTCOLOR_DEF);//颜色
    private Double anglenum = Math.PI / 180;
    private int count = 0;//绘制次数

    public WaveFunctionView(Context context) {
        super(context);
        init();
    }

    public WaveFunctionView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public WaveFunctionView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mWidth = w;//获得控件宽度
        mHeight = h;//获得控件高度
        if (mWidth > mHeight) {//若要裁剪为圆形,以最短的长度为直径
            arcRa = mHeight / 2;
            if (iscircle) {
                mWidth = mHeight;
            }
        } else {
            arcRa = mWidth / 2;
            if (iscircle) {
                mHeight = mWidth;
            }
        }
        waveHeight = mHeight;//初始化开始水位
        ChangeWaveLevel(5);
        super.onSizeChanged(w, h, oldw, oldh);
    }

    //是否是圆形
    public void isCircle(Boolean iscircle) {
        this.iscircle = iscircle;
    }

    //是否开启抗锯齿
    public void setAntiAlias(Boolean antiAlias) {
        this.antiAlias = antiAlias;
        mPaint.setAntiAlias(antiAlias);
        mPaintMore.setAntiAlias(antiAlias);
    }

    //设置主波颜色
    public void setMainWaveColor(int color) {
        mainColor = color;
        mPaint.setColor(color);
    }

    //设置被遮挡的副波颜色
    public void setSecondaryWaveColor(int color) {
        nextColor = color;
        mPaintMore.setColor(color);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        if (iscircle) {//判断是否定义为圆形
            mPath.reset();//重置路径
            mPath.addCircle(arcRa, arcRa, arcRa, Path.Direction.CW);//画以(arcRa,arcRa),半径为arcRa的顺时针的圆
            canvas.clipPath(mPath);//裁剪
        }
        drawPoint.x = 0;//重置为0,从原点开始绘制
        Double rightperiod = Math.PI / 8 * count;//每次平移Math.PI/8个周期
        if (count == 16) {//每次平移Math.PI/8个周期,平移第16次,平移了一个完整的周期
            count = 0;//平移了一个完整周期归零重新开始计数
        } else {
            count++;
        }

        //在宽度以内绘制一条条竖线
        while (drawPoint.x < mWidth) {
            //第一条波的y坐标
            drawPoint.y = (float) (waveHeight - waveDeep * Math.sin(drawPoint.x * anglenum - rightperiod));
            //第二条波的y坐标,比第一条向右移动了Math.PI/2个周期
            drawPoint2.y = (float) (waveHeight - waveDeep * Math.sin(drawPoint.x * anglenum - rightperiod - Math.PI / 2));
            //先绘制被遮挡的副波的竖线
            canvas.drawLine(drawPoint.x, drawPoint2.y, drawPoint.x, mHeight, mPaintMore);
            //绘制最上面显示主波的竖线
            canvas.drawLine(drawPoint.x, drawPoint.y, drawPoint.x, mHeight, mPaint);
            //跳到下一个点继续
            drawPoint.x++;
        }
        //定时更新
        postInvalidateDelayed(17);
    }

    private void init() {
        mPath = new Path();
        mPaint = new Paint();
        mPaint.setColor(mainColor);//设置颜色
        mPaint.setAntiAlias(antiAlias);//抗锯齿(性能影响)
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAlpha(50);
        mPaintMore = new Paint();
        mPaintMore.setAntiAlias(antiAlias);//抗锯齿
        mPaintMore.setStyle(Paint.Style.FILL);
        mPaintMore.setColor(nextColor);//设置颜色
        mPaintMore.setAlpha(30);
        drawPoint = new PointF(0, 0);
        drawPoint2 = new PointF(0, 0);
    }

    public void ChangeWaveLevel(int percent) {
        animator = ValueAnimator.ofFloat(waveDeepmin, waveDeepMax);//设置属性值变化范围,最大波峰波谷与最小
        animator.setDuration(1000);//设置动画时间
        animator.setInterpolator(new LinearInterpolator());//控制动画的变化速率
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                waveDeep = (float) animation.getAnimatedValue();
            }
        });
        animator.setRepeatMode(ValueAnimator.REVERSE);//往返模式
        animator.setRepeatCount(1);
        animatorh = ValueAnimator.ofFloat(waveHeight, mHeight * (10 - percent) / 10);//水位变化
        animatorh.setDuration(2000);//设置动画时间
        animatorh.setInterpolator(new DecelerateInterpolator());//控制动画的变化速率

        animatorh.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                waveHeight = (float) animation.getAnimatedValue();
            }
        });
        animator.start();//开始动画
        animatorh.start();//开始动画
    }
}

GitHub:https://github.com/SteinsGateZero/Mybeisaierwavetest.git

    虽然简单,但是推荐还是得自己动手做一做。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值