Android自定义控件开发入门与实战(9)贝济埃曲线、setShadowLayer阴影、BluMaskFilter发光效果

第七章、绘图进阶

贝济埃曲线

贝济埃曲线是一个强大的工具,它能利用moveTo LineTo连接的生硬的路径变得平滑,也能够实现很多炫酷的效果,比如水波纹等等。
贝济埃曲线在数学的数值分析领域中,是计算机图形学中相当重要的参数曲线,更高维度的广泛化贝济埃曲线称为被贝济埃曲面。

贝济埃曲线的公式

  1. 一阶贝济埃曲线
    B(t) = (1-t)P0+tP1 , t∈[0,1]
    B(t)是值(即形成的轨迹),t表示时间,P0表示起点,P1表示终点,可以看一下下面的图:

    就是在起点和终点连成的这条直线上匀速的运动。
  2. 二阶贝济埃曲线
    B(t) = (1-t)²P0+2t(1-t)P1+t²P2 , t∈[0,1]
    这个公式有点完全平方公式展开的影子。P0和P2是起始点和终止点,而P1是控制点,效果图如下:
    在这里插入图片描述
    假设当时间停在t = 0.5的时刻:
    在这里插入图片描述
    首先P0和P1形成了一条一阶贝济埃曲线,而之前说过一阶贝济埃曲线就是点在该曲线上做匀速运动,假如在P0P1上匀速运动的点是Q0,其次P1P2形成了一条一阶贝济埃曲线,在P1P2上匀速运动的点是Q1,最后Q1Q2又形成了一条贝济埃曲线,在这上面运动的点其实就是我们求出的B(t)。
  3. 三阶贝济埃曲线
    B(t) = P0(1-t)³+3P1t(1-t)²+3P2t²(1-t)+P3t³ , t∈[0,1]
    嘿嘿,看到这个公式我都没兴趣记了。首先效果是下面这样的
    在这里插入图片描述
    其实嘞,就是在二阶上面多加一个控制点,然后又多了一条贝济埃曲线咯。

至于四阶和五阶,Android中是用不到的,所以没有必要去深入了解。而一到三阶最常用。
其实最后都是在一阶贝济埃曲线上进行计算的。

贝济埃曲线之quadTo
在Path类中有4个函数与贝济埃曲线相关,分别如下:

//二阶贝塞尔曲线
public void quadTo(float x1,float y1,float x2,float y2);
public void rQuadTo(float dx1,float dy1,float dx2,float dy2);
//三阶贝济埃曲线
public void cubicTo(float x1,float y1,float x2,float y2,float x3,float y3);
public void rCubicTo(float x1,float y1,float x2, float y2,float x3,float y3);

这里主要讲解二阶贝济埃曲线,三阶用的比较少。
首先:

public void quadTo(float x1,float y1,float x2,float y2);

(x1,y1)表示的是控制点的坐标,而(x2,y2)指的是终点坐标。那起始点在哪里呢?
起始点由Path.moveTo()来控制,也就是说,当前画笔所在位置就是起始点位置。如果没有指定,则以画布的左上角(0,0)为起点。

简单的示例就不做了,这里来看一哈 捕捉手势轨迹这个示例。根据拦截OnTouchEvent来实现。
首先写一个View,然后重写其onTouchEvent和onDraw方法:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.WHITE);
        canvas.drawPath(mPath, mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                mPath.moveTo(event.getX(), event.getY());
                return true;
            }
            case MotionEvent.ACTION_MOVE:
                mPath.lineTo(event.getX(), event.getY());
                invalidate();
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }

这里在onTouchEvent中获取到ACTION_DOWN时将返回true,表示该View消费了按下动作,并且后续的动作都会传递到该view。如果返回false,后续将接受不到任何触摸动作。
然后我们在该View上写一下试试。
在这里插入图片描述
如上图,这就很粗糙,因为其画出来的每个点都不是平滑过渡,导致看起来会有点 锯齿、马赛克的感觉。
如果我们这里进行优化,对两点之间的连线进行平滑过渡,就必须要使用二阶贝济埃曲线来完成。
原理还是看看下面的二阶贝塞尔曲线。
在这里插入图片描述
假设A 、B 、C是我们手指触摸的三个点,那么lineTo就是A-B-C连起来,这时候我们为了实现平滑,将AB的中点作为起点Q0,BC的中点作为起点Q1,以B作为控制点,然后Q0Q1所形成的二阶贝济埃曲线就是平滑的线,其中AQ0 Q1C将会抛弃,因为绘制中相邻的点其实隔得很小,所以这点就忽略不计了。

更改onTouchEvent的代码:

  private float mPreX, mPreY;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                mPath.moveTo(event.getX(), event.getY());
                mPreX = event.getX();
                mPreY = event.getY();
                return true;
            }
            case MotionEvent.ACTION_MOVE:
                float endX = (mPreX + event.getX()) / 2;
                float endY = (mPreY + event.getY()) / 2;
                mPath.quadTo(mPreX, mPreY, endX, endY);
                mPreX = event.getX();
                mPreY = event.getY();
                invalidate();
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }

效果如下:
在这里插入图片描述
。。。。。。。。。。。。。。。。。。。。。
。。。原谅我手搓…但是顺滑还是感觉的出来的(RNG夏季赛给老子冲鸭!)

rQuadTo()
是quadTo的相对路径版,里面的值都是根据上一个位置来增加偏移值的。
比如
path.moveTo(300,400)
path.quadTo(500,300,500,500)
等同于
path.moveTo(300,400)
path.rQuadTo(200,-100,200,100)

示例:波浪效果,直接上代码

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

        mPath = new Path();
        mPaint = new Paint();
        mPaint.setColor(Color.GREEN);
        mPaint.setStyle(Paint.Style.FILL);

        startAnim();
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPath.reset();
        int originY = 300;
        int halfWaveLen = mItemWaveLength / 2;
        mPath.moveTo(-mItemWaveLength + dx, originY);
        for (int i = -mItemWaveLength; i <= getWidth() + mItemWaveLength; i += mItemWaveLength) {
            mPath.rQuadTo(halfWaveLen / 2, -100, halfWaveLen, 0);
            mPath.rQuadTo(halfWaveLen / 2, 100, halfWaveLen, 0);
        }
        mPath.lineTo(getWidth(),getHeight());
        mPath.lineTo(0,getHeight());
        mPath.close();
        canvas.drawPath(mPath, mPaint);
    }

    public void startAnim() {
        ValueAnimator animator = ValueAnimator.ofInt(0, mItemWaveLength);
        animator.setDuration(2000);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                dx = (int) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.start();
    }

首先mItemWaveLength表示整个波浪的长度,已经画好3个波浪,在动画推进时使用,在利用lineto和close将波浪下面填充,得出的效果如下:
在这里插入图片描述

setShadowLayer与阴影效果

Android专门开发了一个添加阴影效果的函数setShadowLayer(),可以实现如下效果:

  • 定制阴影模糊程度
  • 定制阴影偏移距离
  • 清除和显示阴影

构造函数如下:

public void setShadowLayer(float radius, float dx,float dy,int color)

参数注解如下:

  • radius:模糊半径,radius越大越模糊,越小越清晰。如果radius为0,则阴影消失不见
  • dx:阴影的横向偏移距离,正右负左
  • dy:阴影纵向偏移距离,正下负上
  • color:阴影的颜色(对图片无效)

setShadowLayer()函数使用了高斯模糊算法,高斯模糊算法的定义很简单,自己去找。

对于字体和绘制的view的阴影颜色都是通过该函数的color来定义的,但是对于图片,图片的阴影就是图片的副本,并保留了边上的部分。并且Paint.setShadowLayer函数所指定的颜色对图片起不了作用。

setShadowLayer函数只有对文字绘制阴影支持硬件加速,其他都不支持硬件加速,所以一般都会将其关闭。

清除阴影
清除阴影比较简单,可以设置radius为0,也可以使用下面的函数:

public void clearShadowLayer();

只需要在重绘时,调用Paint.clearShadowLayer()就能去除阴影。

我们可以在xml文件为控件添加shadowRadius、shadowDx…等属性来设置阴影信息。

BlurMaskFilter发光效果与图片阴影

发光效果就是文字、图形、图片的边缘部分进行采样模糊。所以它们边缘是什么颜色的,发出的光就是什么颜色的,有下面的特点:

  • 发光效果也是用高斯算法,并且只会影响边缘图像部分。
  • 发光效果是无法指定发光颜色的,采用边缘部分的颜色取样来进行模糊发光,所以,边缘是什么颜色的,发出的光就是什么颜色的。

setMaskFilter()

public MaskFilter setMaskFilter(MaskFilter maskfilter)

setMaskFilter函数中的MaskFilter是没有具体实现的,是通过派生子类来实现具体的不同功能。MaskFilter有两个派生类:BurMaskFilter和EmbossMaskFilter,其中BlurMaskFilter能够实现发光效果,而EmbossMaskFilter用于实现浮雕效果,这里不讲解。

setMaskFilter也是不支持硬件加速der,所以使用时要关掉。
BlurMaskFilter的构造函数如下:

public BlurMaskFilter(float radius,Blur style)

参数:

  • radius:表示模糊半径
  • Blur style:发光样式,有Blur.INNER(内发光)、Blur.SOLID(外发光)、Blur.NORMAL(内外发光)、Blur.OUTER(仅显示发光)

我们做一个内发光的:

 public LightView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setMaskFilter(new BlurMaskFilter(50, BlurMaskFilter.Blur.INNER));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(500, 500, 200, mPaint);
    }

效果如下:
在这里插入图片描述
其他的都是字面意思上的啦。

学到这里就知道如何为图片添加纯色阴影了:

  • 绘制一幅跟图片一样大小的灰色图像(通过extractAlpha来获取原Bitmap的透明度)
  • 对灰色图像应用BlurMaskFilter使其内外发光
  • 偏移原图形一段距离绘制阴影
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值