美文网首页
Android 自定义View学习(六)——Paint 关于Sh

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

作者: Yue_Q | 来源:发表于2018-10-28 18:22 被阅读0次

    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 关于Sh

          本文链接:https://www.haomeiwen.com/subject/myiptqtx.html