Android 自定义View学习(六)——Paint 关于Shader的学习


1.Shader 着色器

着色器就是用来上色的,可以用来实现一系列的渐变、渲染效果,有5个子类

  1. BitmapShader 位图Shader
  2. LinerGradient 线性Shader
  3. RadialGradient 光束Shader
  4. SweepGradient 梯度Shader
  5. ComposeShader 混合Shader

BitmapShader是唯一个可以用来给一个图片着色,其他四个就是渐变、渲染效果


2.BitmapShader 位图着色器

构造方法:
BitmapShader(@NonNull Bitmap bitmap, TileMode tileX, TileMode tileY)
构造方法中除了一个Bitmap外,还需要两个TileMode枚举类型的参数,一个代表在x轴的模式,一个在y轴的模式

2.1 TileMode 瓷砖模式

TileMode是Shader中的一个枚举,有三个值

  • CLAMP 拉伸,图片的最后的一个像素,不断重复
  • REPEAT 重复,横向、纵向不断重复
  • MIRROR 镜像,横向不断翻转重复,纵向不断翻转重复
   private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        final Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.w);
        final BitmapShader shader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mPaint.setShader(shader);
    }
    /**
     * 利用 clmp得到圆形图片
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float x = getWidth() / 2;
        float y = getHeight() / 2;
        float radius = Math.min(getWidth(), getHeight()) / 2;
        canvas.drawCircle(x, y, radius,mPaint);
    }

利用CLMP得到一个圆形图片


image.png

image.png

这时图片的下面部分已经出现了问题,明显是最后一个元素被拉伸过多,使用这种方式得到圆形图片,拉伸后的图片要大于控件的大小,这样最后一个元素既然被拉伸变形,也看不到


2.2 BitmapShader下三种模式的效果

  • REPEAT 重复
    简单修改代码,将CLAMP变为REPEAT,图片资源变为R.mipmap.ic_launcher,画的图形由圆形变为矩形,布局中的宽度改为match_parent
    REPEAT.png

  • MIRROR 镜像
    上下对称,出现镜像
    image.png

上面的情况两个TileMode都是同一个值

BitmapShader总是先应用Y轴上的模式后,再应用X轴上的模式


3 LinearGradient 线性渐变

构造方法,有两个:

LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,TileMode tile)

LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[],TileMode tile)

3.1第一种构造方法简单使用:

  private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        LinearGradient shader = new LinearGradient(0, 0, 600, 600, Color.CYAN, Color.BLUE, Shader.TileMode.REPEAT);
        mPaint.setShader(shader);
    }
  
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0,0,600,600,mPaint);
    }
  • x0 绘制x轴起始点
  • y0 绘制y轴起始点
  • x1 绘制x轴结束点
  • y1 绘制y轴结束点
  • color0 起始颜色
  • color1 结束颜色
  • tile 瓷砖模式
    LinearGradient第1种构造方法.png

    效果很容易理解,在控件中从0,0左上角点到600,600右下角两种颜色渐变

3.2第二种构造方法简单使用:

两个构造方法的区别在于,第4参数,是一个int[],第5个参数为一个float[],使用这个构造方法可以设置多种颜色的渐变

  • colors 颜色int值数组
  • postions 数组中的值有效范围是0f~1f,渐变结束所在区域的比例,1f的结束位置,与x1,y1有关
    LinearGradient第2种构造方法.png

    三种颜色,在起始点(0,0),二分之一点(300,300),结束点(600,600)的渐变效果

3.3 图片倒影效果:

思路:

  1. 绘制原图,考虑绘制坐标,图片缩放
  2. 绘制倒影,利用Matrix
  3. 绘制渐变层,利用PortDuffXfermode和LinearGradient
image.png

代码如下:

public class ReflectView extends View {
    private Paint mPaint;
    private Bitmap dstBitmap, srcBitmap;
    private PorterDuffXfermode xfermode;
    private int x, y;

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

    private void init() {
        //原图Bitmap
        dstBitmap = decodeBitmapFormRes(getResources(), R.drawable.wa,540, 960);
        //垂直翻转
        Matrix matrix = new Matrix();
        matrix.setScale(1f, -1f);
        //倒影Bitmap
        srcBitmap = Bitmap.createBitmap(dstBitmap, 0, 0, dstBitmap.getWidth(), dstBitmap.getHeight(), matrix, true);
        //初始化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
         //屏幕宽度
        int screenW = getResources().getDisplayMetrics().widthPixels;
        //起始点
        x = screenW / 2 - dstBitmap.getWidth() / 2;
        y = 0;
        //设置渐变矩形
        mPaint.setShader(new LinearGradient(x, dstBitmap.getHeight(), x, dstBitmap.getHeight() + dstBitmap.getHeight() / 2, 0xDD000000, Color.TRANSPARENT, Shader.TileMode.CLAMP));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制背景
        canvas.drawColor(Color.BLACK);
        //绘制原图
        canvas.drawBitmap(dstBitmap, x, y, null);
        //绘制倒影图片
        canvas.drawBitmap(srcBitmap, x, dstBitmap.getHeight(), null);
        mPaint.setXfermode(xfermode);
        //绘制渐变层
        canvas.drawRect(x, dstBitmap.getHeight(), x + dstBitmap.getWidth(), dstBitmap.getHeight() * 2, mPaint);
        mPaint.setXfermode(null);
    }

    /**
     * 测量
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(300, 300);
        } else if (wSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(300, hSpecSize);
        } else if (hSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(wSpecSize, 300);
        }
    }

    /**
     * 图片的缩放
     */
    private Bitmap decodeBitmapFormRes(Resources resources, int resId, int targetWidth, int targetHeight) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inJustDecodeBounds = false;
        BitmapFactory.decodeResource(resources, resId, options);
        int inSample = calculateInSample(options, targetWidth, targetHeight);
        options.inSampleSize = inSample;
        return BitmapFactory.decodeResource(resources, resId, options);
    }

    private int calculateInSample(BitmapFactory.Options options, int targetWidth, int targetHeight) {
        if (targetWidth <= 0 || targetHeight <= 0) {
            return 1;
        }
        int inSample = 1;
        final int rawWidth = options.outWidth;
        final int rawHeight = options.outHeight;
        if (rawWidth > targetWidth || rawHeight > targetHeight) {
            final int halfWidth = rawWidth / 2;
            final int halfHeight = rawHeight / 2;
            while ((halfWidth / inSample >= targetWidth) && (halfHeight / inSample >= targetHeight)) {
                inSample *= 2;
            }
        }
        return inSample;
    }
}

4.RadialGradient 光束渐变

两个构造方法:

RadialGradient(float centerX, float centerY, float radius,  int centerColor, int edgeColor, @NonNull TileMode tileMode)

RadialGradient(float centerX, float centerY, float radius, @NonNull int colors[], @Nullable float stops[], @NonNull TileMode tileMode)

private void init() {
        final RadialGradient shader = new RadialGradient(300f,300f,300,Color.RED,Color.YELLOW, Shader.TileMode.CLAMP);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //LinearGradient shader = new LinearGradient(0, 0, 600, 600,colors,positions, Shader.TileMode.REPEAT);
        mPaint.setShader(shader);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0,0,600,600,mPaint);
    }
RadialGradient第1种构造方法.png
  • centerX 渐变中心点的X轴坐标
  • centerY 渐变中心点的Y轴坐标
  • radius 渐变区域的半径
    控件大小为600 * 600(300f,300f)为控件的中心,半径为300

第二种构造方法简单使用:

  private void init() {
        final int[] colors = new int[]{Color.YELLOW,Color.BLUE ,Color.RED};
        final float[] positions = new float[]{0f, 0.5f ,1f};
        final RadialGradient shader = new RadialGradient(300f,300f,300,colors,positions, Shader.TileMode.CLAMP);
         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
         mPaint.setShader(shader);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0,0,600,600,mPaint);
    }
RadialGradient第2种构造方法.png

colors中的元素个数要和positions中的元素个数相等


5. SweepGradient 梯度渐变

两个构造方法:

SweepGradient(float cx, float cy, int color0, int color1) 
SweepGradient(float cx, float cy, int colors[], float positions[])

cx,cy是旋转点的x,y轴坐标,渐变过程总是顺时针方向旋转

第一种构造方法简单使用:

private void init() {
        final SweepGradient shader = new SweepGradient(300f,300f,Color.RED,Color.YELLOW);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setShader(shader);
         mPaint.setShader(shader);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0,0,600,600,mPaint);
    }

第一种构造方法简单使用

控件的大小为600 * 600,300f * 300f就是整个控件的中心,可以试试其他的值


第二种构造方法简单使用:


image.png
 private void init() {
        final int[] colors = new int[]{Color.MAGENTA, Color.CYAN,Color.YELLOW,Color.BLUE ,Color.RED};
        final float[] positions = new float[]{0f, 0.25f,0.5f ,0.75f,1f};
        final SweepGradient shader = new SweepGradient(300f, 300f, colors, positions);

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setShader(shader);
         mPaint.setShader(shader);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0,0,600,600,mPaint);
    }

6. ComposeShader 混合渐变

两个构造方法:
···
ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)
ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)
···
构造方法中,前两个参数相同,都是需要一个着色器,差别在于第三个参数。第一个构造方法需要一个PorterDuff.Mode,而第二个构造构造方法需要PorterDuffXfermode

前面的三种的渐变,都是一种单一的渐变,ComposeShader可以把前面两种渐变混合进一种渐变效果
简单使用:

image.png

private void init() {
    setLayerType(View.LAYER_TYPE_SOFTWARE, null);//关闭硬件加速
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    final LinearGradient linearGradient = new LinearGradient(0, 0, 600, 600,Color.GREEN ,Color.BLUE ,Shader.TileMode.CLAMP);
    final RadialGradient radialGradient = new RadialGradient(300f,300f,300,Color.RED,Color.YELLOW, Shader.TileMode.CLAMP);
       
    final ComposeShader shader = new ComposeShader(linearGradient, radialGradient, PorterDuff.Mode.SCREEN);
    mPaint.setShader(shader);
}

必须要把硬件加速关闭,ComposeShader混合渐变效果才会出现,否则屏幕一片空白,手边暂时没有了别的手机,不知道会不会也是这样

第3个参数,new PorterDuffXfermode(PorterDuff.Mode.SCREEN)PorterDuff.Mode.SCREEN这两种写法有啥区别,暂时也没看出来

你可能感兴趣的:(Android 自定义View学习(六)——Paint 关于Shader的学习)