Android Shader渲染以及实现水波纹霓虹文字雷达等效果

Posted lylodlig

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Shader渲染以及实现水波纹霓虹文字雷达等效果相关的知识,希望对你有一定的参考价值。

Shader概述

Shader是绘图过程中的着色器,实现绘制各种不同的效果,比如镜像,水波纹,雷达等等,Shader有以下五个子类:
- BitmapShader用于Bitmap图片的渲染
- ComposeShader用于混合渲染
- LinearGradient用于线性渲染
- RadialGradient用于环形渲染
- SweepGradient用于梯度渲染

Shader的三种模式TileMode

  • CLAMP 当绘制的区域超过了原始的大小,超出的区域就会用边缘的颜色进行拉伸
  • REPEAT 重复水平或者竖直方向的图片
  • MIRROR 用图片的镜像填充

BitmapShader

构造方法

BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY)
- bitmap:需要着色的位图
- tileX:X方向的填充模式
- tileY:Y方向的填充模式

实例

这里写图片描述
这是一个充值后的影魔,直接看看代码的实现:

public class ShaderView extends View {
    Bitmap mBitmap;
    BitmapShader mBitmapShader;
    Paint mPaint;
    int mWidth;
    int mHeight;

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

    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.MIRROR);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setShader(mBitmapShader);
        canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
    }
}

这里的X轴采用的是CLAMP,所以右边是拉伸边缘的像素点,Y轴采用的是MIRROR,上下都是镜像的。
现在我们把drawRect注释点,来绘制一个圆

//        canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
        canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mPaint);

这里写图片描述
可以看到以上的效果,所以可不可以这样理解呢,这里的BitmapShader就是给绘制的内容加上我们所设置的Bitmap作为背景。

RadialGradient

主要用于在某一区域内实现环形的渐变效果,RadialGradient的意思是放射渐变,即它会向一个放射源一样,从一个点开始向外从一个颜色渐变成另一种颜色。

构造方法

  1. RadialGradient(float centerX, float centerY, float radius,int centerColor, int edgeColor, TileMode tileMode)
    - centerX:渐变中心点X坐标
    - centerY:渐变中心点Y坐标
    - radius:渐变半径
    - centerColor:渐变中心的颜色,取值类型必须是八位的0xAARRGGBB色值
    - edgeColor:渐变结束的颜色
    - tileMode:填充的模式
  2. RadialGradient(float centerX, float centerY, float radius,int colors[], float stops[], @NonNull TileMode tileMode)
    • int[] colors:表示所需要的渐变颜色数组
    • float[] stops:表示每个渐变颜色所在的位置百分点,取值0-1

示例

下面是两种构造函数实现的
这里写图片描述这里写图片描述
代码

public class RadialGradientView extends View {
    private RadialGradient mRadialGradient;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    int mWidth;
    int mHeight;

    public RadialGradientView(Context context) {
        super(context);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
//        mRadialGradient = new RadialGradient(mWidth / 2, mHeight / 2, mWidth / 2, 0xffff0000, 0xff00ff00, Shader.TileMode.CLAMP);
        int[] colors = new int[]{0xffff0000, 0xff00ff00, 0xff0000ff, 0xffffff00};
        float[] stops = new float[]{0f, 0.3f, 0.7f, 1f};
        mRadialGradient = new RadialGradient(mWidth / 2, mHeight / 2, mWidth / 2, colors, stops, Shader.TileMode.REPEAT);
        mPaint.setShader(mRadialGradient);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mPaint);
    }
}

再次修改一下

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        int[] colors = new int[]{0xffff0000, 0xff00ff00, 0xff0000ff, 0xffffff00};
        float[] stops = new float[]{0f, 0.3f, 0.7f, 1f};
        mRadialGradient = new RadialGradient(mWidth / 2, mHeight / 2, 200, colors, stops, Shader.TileMode.REPEAT);
        mPaint.setShader(mRadialGradient);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(new Rect(0,0,mWidth,mHeight),mPaint);
    }

效果如下
这里写图片描述

水波纹效果实现

这里写图片描述
代码如下,就不多说了

public class RippleView extends TextView {
    private int mX, mY;
    private ObjectAnimator mAnimator;
    private int DEFAULT_RADIUS = 50;
    private int mCurRadius = 0;
    private RadialGradient mRadialGradient;
    private Paint mPaint = new Paint();

    public RippleView(Context context) {
        super(context);
    }

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


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mX != event.getX() || mY != event.getY()) {
            mX = (int) event.getX();
            mY = (int) event.getY();
            setRadius(DEFAULT_RADIUS);
        }

        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            //如果不返回true,后续的事件收不到
            return true;
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            if (mAnimator != null && mAnimator.isRunning()) {
                mAnimator.cancel();
            }
            if (mAnimator == null) {
                //这里第一个对象传递当前对象,在当前对象中设置了setRadius方法,所以这里传递radius
                //每当值变化时就会调用这个setRadius方法
                mAnimator = ObjectAnimator.ofInt(this, "radius", DEFAULT_RADIUS, getWidth());
            }
            mAnimator.setInterpolator(new AccelerateInterpolator());
            mAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    setRadius(0);
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                }

                @Override
                public void onAnimationRepeat(Animator animation) {
                }
            });
            mAnimator.start();
        }

        return super.onTouchEvent(event);
    }

    //注意这里的方法名必须是setRadius
    public void setRadius(final int radius) {
        mCurRadius = radius;
        if (mCurRadius > 0) {
            mRadialGradient = new RadialGradient(mX, mY, mCurRadius, 0x00FFFFFF, 0xFF58FAAC, Shader.TileMode.CLAMP);
            mPaint.setShader(mRadialGradient);
        }
        invalidate();
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(mX, mY, mCurRadius, mPaint);
    }
}

LinearGradient

线性渲染,对某一区域实现线性渐变效果。

构造函数

  1. LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,TileMode tile)
    • x0,y0是渐变的起点坐标
    • x1,y1是渐变的终点坐标
    • color0是开始颜色
    • color1是结束颜色
  2. LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[],TileMode tile)
    • colors和positions意义和之前的RadialGradient一样。

示例

这里写图片描述

public class LinearGradientView extends View {
    Paint mPaint = new Paint();
    LinearGradient mLinearGradient;
    int[] colors = new int[]{
            0xFFFF0000,
            0xffFF7F00,
            0xffFFFF00,
            0xff00FF00,
            0xff00FFFF,
            0xff0000FF,
            0xff8B00FF};

    public LinearGradientView(Context context) {
        super(context);
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mLinearGradient == null) {
            mLinearGradient = new LinearGradient(0, 0, 0, 400, colors, null, Shader.TileMode.CLAMP);
            mPaint.setShader(mLinearGradient);
        }
        canvas.drawRect(new Rect(0,0,getWidth(),getHeight()),mPaint);
    }
}

霓虹文字效果

这里写图片描述
这里实现主要是继承TextView,获得它绘制文字的Paint,给这个Paint设置LinearGradient的Shader,把这个Shader从左边开始向右移动,实现霓虹效果。

public class LinearGradientText extends TextView {
    Paint mPaint;
    LinearGradient mLinearGradient;
    private Matrix mMatrix;
    private int mX;

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

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

    private void init() {
        //注意这里必须是TextView的Paint,因为绘制文字就是用这个Paint
        mPaint = getPaint();
        mMatrix = new Matrix();
    }

    private void initAnimtor(int width) {
        ValueAnimator animator = ValueAnimator.ofInt(0, width * 2);  //我们设置value的值为0-getMeasureWidth的3 倍
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mX = (Integer) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.setRepeatMode(ValueAnimator.RESTART);   //重新播放
        animator.setRepeatCount(ValueAnimator.INFINITE);   //无限循环
        animator.setDuration(2000);
        animator.start();
    }

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

        //设置LinearGradient,绘制的范围这里设置的是-w到w,相当于两个宽度,然后把Shader向右移动实现了效果
        mLinearGradient = new LinearGradient(-w, 0, w, 0, new int[]{getCurrentTextColor(), Color.RED, Color.YELLOW, Color.BLUE, getCurrentTextColor(),}
                , null, Shader.TileMode.CLAMP);
        mPaint.setShader(mLinearGradient);

        initAnimtor(w);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mMatrix.reset();
        mMatrix.preTranslate(mX, 0);
        mLinearGradient.setLocalMatrix(mMatrix);
    }
}

SweepGradient

梯度渲染,是指在某一中心以x轴正方向逆时针旋转一周而形成的扫描效果的渲染形式

构造函数

  1. SweepGradient(float cx, float cy, int colors[], float positions[])
    • cx,cy:中心坐标点
    • colors、positions同样和之前一样
  2. SweepGradient(float cx, float cy, int color0, int color1)
    • cx,cy中心坐标点
    • color0、color1开始和结束的颜色

简单示例

这里写图片描述

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
        mSweepGradient = new SweepGradient(w / 2, h / 2, colors, null);
        mPaint.setShader(mSweepGradient);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawRect(new Rect(0, 0, width, height), mPaint);
    }

雷达扫描实现

这里写图片描述

/**
 * Created by lzy on 2017/4/13.
 */
public class RadarView extends View {

    private static final int MSG_WHAT = 10086;
    private static final int DELAY_TIME = 20;
    //设置默认宽高,雷达一般都是圆形,所以我们下面取宽高会去Math.min(宽,高)
    private final int DEFAULT_WIDTH = 200;
    private final int DEFAULT_HEIGHT = 200;

    private int mRadarRadius;  //雷达的半径

    private Paint mRadarPaint;//雷达画笔

    private Paint mRadarBg;//雷达底色画笔

    private int radarCircleCount = 4;//雷达圆圈的个数,默认4个

    private int mRadarLineColor = Color.WHITE; //雷达线条的颜色,默认为白色

    private int mRadarBgColor = Color.BLACK; //雷达圆圈背景色
    private Shader radarShader;  //paintShader

    //雷达扫描时候的起始和终止颜色
    private int startColor = 0x0000ff00;
    private int endColor = 0xaa00ff00;


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

    public RadarView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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


        mRadarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);     //设置抗锯齿
        mRadarPaint.setColor(mRadarLineColor);                  //画笔颜色
        mRadarPaint.setStyle(Paint.Style.STROKE);           //设置空心的画笔,只画圆边
        mRadarPaint.setStrokeWidth(2);                      //画笔宽度


        mRadarBg = new Paint(Paint.ANTI_ALIAS_FLAG);     //设置抗锯齿
        mRadarBg.setColor(mRadarBgColor);                  //画笔颜色
        mRadarBg.setStyle(Paint.Style.FILL);           //设置空心的画笔,只画圆边


        radarShader = new SweepGradient(0, 0, startColor, endColor);

        matrix = new Matrix();
    }


    //初始化,拓展可设置参数供布局使用
    private void init(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RadarView);
            startColor = ta.getColor(R.styleable.RadarView_startColor, startColor);
            endColor = ta.getColor(R.styleable.RadarView_endColor, endColor);
            mRadarBgColor = ta.getColor(R.styleable.RadarView_bgColor, mRadarBgColor);
            mRadarLineColor = ta.getColor(R.styleable.RadarView_lineColor, mRadarLineColor);
            radarCircleCount = ta.getInteger(R.styleable.RadarView_circleCount, radarCircleCount);
            ta.recycle();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = measureSize(1, DEFAULT_WIDTH, widthMeasureSpec);
        int height = measureSize(0, DEFAULT_HEIGHT, heightMeasureSpec);
        int measureSize = Math.max(width, height);   //取最大的 宽|高
        setMeasuredDimension(measureSize, measureSize);
    }


    /**
     * 测绘measure
     *
     * @param specType    1为宽, 其他为高
     * @param contentSize 默认值
     */
    private int measureSize(int specType, int contentSize, int measureSpec) {
        int result;
        //获取测量的模式和Size
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = Math.max(contentSize, specSize);
        } else {
            result = contentSize;

            if (specType == 1) {
                // 根据传人方式计算宽
                result += (getPaddingLeft() + getPaddingRight());
            } else {
                // 根据传人方式计算高
                result += (getPaddingTop() + getPaddingBottom());
            }
        }

        return result;

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mRadarRadius = Math.min(w / 2, h / 2);

    }

    //旋转的角度
    private int rotateAngel = 0;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);


        canvas.translate(mRadarRadius, mRadarRadius);   //将画板移动到屏幕的中心点

        mRadarBg.setShader(null);
        canvas.drawCircle(0, 0, mRadarRadius, mRadarBg);  //绘制底色(默认为黑色),可以使雷达的线看起来更清晰

        for (int i = 1; i <= radarCircleCount; i++) {     //根据用户设定的圆个数进行绘制
            canvas.drawCircle(0, 0, (float) (i * 1.0 / radarCircleCount * mRadarRadius), mRadarPaint);  //画圆圈
        }

        canvas.drawLine(-mRadarRadius, 0, mRadarRadius, 0, mRadarPaint);  //绘制雷达基线 x轴
        canvas.drawLine(0, mRadarRadius, 0, -mRadarRadius, mRadarPaint);  //绘制雷达基线 y轴


//        canvas.rotate(rotateAngel,0,0);
        //设置颜色渐变从透明到不透明
        mRadarBg.setShader(radarShader);
        canvas.concat(matrix);
        canvas.drawCircle(0, 0, mRadarRadius, mRadarBg);
    }

    private Matrix matrix;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            rotateAngel += 3;
            postInvalidate();

            matrix.reset();
            matrix.preRotate(rotateAngel, 0, 0);
            mHandler.sendEmptyMessageDelayed(MSG_WHAT, DELAY_TIME);
        }
    };


    public void startScan() {
        mHandler.removeMessages(MSG_WHAT);
        mHandler.sendEmptyMessage(MSG_WHAT);
    }

    public void stopScan() {
        mHandler.removeMessages(MSG_WHAT);
    }
}

ComposeShader

组合渲染

构造函数

  1. ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)
    • shaderA :渲染器A
    • shaderB :渲染器B
    • Xfermode :两种渲染器组合的模式,Xfermode对象
  2. ComposeShader(Shader shaderA, Shader shaderB, Mode mode)

    • Mode :两种渲染器组合的模式,ProterDuff.Mode对象

    简单实例

    这里写图片描述
    这里是结合了BitmapShader和LinearGradient实现的效果

    public class ComposeShaderView extends View {
    Bitmap mBitmap;
    BitmapShader mBitmapShader;
    Paint mPaint;
    
    LinearGradient mLinearGradient;
    ComposeShader mComposeShader;
    
    int mWidth;
    int mHeight;
    
    public ComposeShaderView(Context context) {
        super(context);
        init();
    }
    
    public ComposeShaderView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    
    private void init() {
        mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test_3);
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
    }
    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        mLinearGradient = new LinearGradient(0, 0, w, h, new int[] {
                Color.WHITE, Color.LTGRAY, Color.TRANSPARENT, Color.GREEN }, null, Shader.TileMode.CLAMP);
        mComposeShader = new ComposeShader(mBitmapShader, mLinearGradient, PorterDuff.Mode.MULTIPLY);
        mPaint.setShader(mComposeShader);
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    
        canvas.drawOval(0, 0, mWidth, mHeight, mPaint);
    }
    }

    关于Mode可以看看这里
    Demo下载链接

以上是关于Android Shader渲染以及实现水波纹霓虹文字雷达等效果的主要内容,如果未能解决你的问题,请参考以下文章

求一个unity Shader,点击出现水波纹动画,并衰减,边缘反弹

Android UIPaint Gradient 渐变渲染 ③ ( RadialGradient 环形渐变渲染 | 在给定中心和半径的情况下绘制径向渐变的着色器 | 水波纹效果 )

Android 自定义view实现水波纹效果

android: Android水波纹点击效果

Android特效专辑——点击水波纹效果实现,逻辑清晰实现简单

Android 按钮实现按压水波纹效果