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下載鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章