(七)Paint 高级渲染

来源:互联网 发布:小米笔记本 显卡知乎 编辑:程序博客网 时间:2024/06/13 08:41

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

这边继续 Paint 的一些高级用法 Shader,Canvas 的 drawXXXX 这个方法是画具体的形状,画笔的 shader 定义的就是图形的着色和外观。

一、 BitmapShader 位图图像渲染

BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY);
第一个参数为要渲染的图片,后面两个分别是 X 方向和 Y 方向的拉伸处理。

BitmapShader 可以设置一张图片,当我们调用 Canvas 的 drawXXXX 这个方法来进行形状的绘制的时候,会把这张图片填充到我们绘画的形状中。当图片过大的时候,从左上角对齐开始绘制,当形状过大,会对图片进行设置的 TileMode 拉伸处理,这里 TileMode 有三种拉伸模式:

TileMode 拉伸形式    CLAMP ---是拉伸最后一个像素铺满    MIRROR ---在横向纵向不足处不断翻转镜像平铺    REPEAT ---类似电脑壁纸,横向纵向不足的重复放置

这里写图片描述

上下两个矩形,横向都是采用 CLAMP 填充方式,在竖直方向上,上面一个矩形采用的是 MIRROR 填充方式,下一个矩形采用的是 REPEAT 填充方式。

1.圆形头像

先来看下效果:
这里写图片描述

public class MyGradientView extends View{    //加载的图片    private Bitmap bitmap;    private Paint mPaint;    //图片的宽高    private int mWidth;    private int mHeight;     //测量的宽高    float measureWidth;    float measureHeight;    //缩放比例    float scale;    public MyGradientView(Context context, AttributeSet attrs) {        super(context, attrs);        mPaint = new Paint();        //加载图片,并获取图片的宽高        bitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.timg)).getBitmap();        mWidth = bitmap.getWidth();        mHeight = bitmap.getHeight();    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        // TODO Auto-generated method stub        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        measureWidth = MeasureSpec.getSize(widthMeasureSpec);        measureHeight = MeasureSpec.getSize(heightMeasureSpec);        //考虑到宽高可能不一致,我们取宽高比的最小值        scale = Math.min(measureWidth/mWidth, measureHeight/mHeight);    }    @Override    protected void onDraw(Canvas canvas) {        // TODO Auto-generated method stub        super.onDraw(canvas);        //new一个 Shader ,宽高都采用 CLAMP 填充方式        BitmapShader shader = new BitmapShader(bitmap, TileMode.CLAMP,                 TileMode.CLAMP);        mPaint.setShader(shader);        mPaint.setAntiAlias(true);        //这边用矩阵进行设置图片的缩放,这个不在这边细讲        Matrix matrix = new Matrix();        //scale = 缩放大小 / 原大小         matrix.setScale(scale,scale);        shader.setLocalMatrix(matrix);        //以 View 的中心为圆心,宽高较小值的一般为半径画圆        canvas.drawCircle(measureWidth/2, measureHeight/2, Math.min(measureWidth/2, measureHeight/2), mPaint);    }}

布局文件 xml 中直接引用即可。

2.放大镜

这里写图片描述

这边就在上方的 demo 中添加放大效果。

public class MyGradientView extends View{    //加载的图片    private Bitmap bitmap;    private Paint mPaint;    //图片的宽高    private int mWidth;    private int mHeight;     //测量的宽高    float measureWidth;    float measureHeight;    //缩放比例    float scale;    //放大镜的半径    private static final int RADIUS  = 100;    // 放大后的图    private Bitmap mBitmapScale;    //制作的圆形的图片(放大的局部),盖在Canvas上面    private ShapeDrawable mShapeDrawable;    //记录背景图片移动的矩阵    private Matrix matrix;    public MyGradientView(Context context, AttributeSet attrs) {        super(context, attrs);        mPaint = new Paint();        //加载图片,并获取图片的宽高        bitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.timg)).getBitmap();        mWidth = bitmap.getWidth();        mHeight = bitmap.getHeight();        matrix = new Matrix();    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        // TODO Auto-generated method stub        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        measureWidth = MeasureSpec.getSize(widthMeasureSpec);        measureHeight = MeasureSpec.getSize(heightMeasureSpec);        //考虑到宽高可能不一致,我们取宽高比的最小值        scale = Math.min(measureWidth/mWidth, measureHeight/mHeight);    }    @Override    protected void onDraw(Canvas canvas) {        // TODO Auto-generated method stub        super.onDraw(canvas);        //new一个 Shader ,宽高都采用 CLAMP 填充方式        BitmapShader shader = new BitmapShader(bitmap, TileMode.CLAMP,                 TileMode.CLAMP);        mPaint.setShader(shader);        mPaint.setAntiAlias(true);        //这边用矩阵进行设置图片的缩放,这个不在这边细讲        Matrix matrix = new Matrix();        //scale = 缩放大小 / 原大小         matrix.setScale(scale,scale);        shader.setLocalMatrix(matrix);        //以 View 的中心为圆心,宽高较小值的一般为半径画圆        canvas.drawCircle(measureWidth/2, measureHeight/2, Math.min(measureWidth/2, measureHeight/2), mPaint);        //加上一个判断,避免每次都要去加载放大的图片        if(mBitmapScale == null){            //获取放大的图形(显示的背景图片缩小了,这里直接用原图)            mBitmapScale = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth(),                    bitmap.getHeight(), true);            BitmapShader bitmapShader = new BitmapShader(mBitmapScale, TileMode.CLAMP, TileMode.CLAMP);            //设置放大后显示的区域            //ShapeDrawable用法这边也不具体讲,            mShapeDrawable = new ShapeDrawable(new OvalShape());            mShapeDrawable.getPaint().setShader(bitmapShader);            mShapeDrawable.setBounds(0, 0, RADIUS * 2, RADIUS * 2);        }        mShapeDrawable.draw(canvas);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        // TODO Auto-generated method stub        int x = (int) event.getX();        int y = (int) event.getY();        //对放大的图片进行平移,将放大的图片往相反的方向挪动        matrix.setTranslate(-x / scale, -y / scale);        mShapeDrawable.getPaint().getShader().setLocalMatrix(matrix);        // 切出手势区域点位置的圆        mShapeDrawable.setBounds(x - RADIUS, y - RADIUS, x + RADIUS, y + RADIUS);        invalidate();        return true;    }}

二、LinearGradient 线性渲染

LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, TileMode tile);
前四个参数分别是起始点的坐标和渐变结束点的坐标,第五个参数是渐变颜色数组,第六个参数是渐变发送变化的位置数组(值再0-1之间),为空的话表示均匀分布。

这是一个从左上角到右下角的渐变:
这里写图片描述

霓虹灯

这里写图片描述

这里重要的一点是在 onDraw 方法中重新设置 mLinearGradient 的平移,触发 onSizeChanged 方法,从而再次调用 onDraw 。

public class LinearGradientTextView extends TextView{    private TextPaint mPaint;    private int measureWidth;    private LinearGradient mLinearGradient ;    //效果的宽度    private int width = 100;    //效果平移的矩阵    private Matrix mMatrix;    //平移的距离    private float mTranslate;    //移动速度    private float DELTAX = 20;    public LinearGradientTextView(Context context) {        super(context);    }    public LinearGradientTextView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        mPaint = getPaint();        mMatrix = new Matrix();        //线性渲染初始化        mLinearGradient = new LinearGradient(-width, 0, 0, 0,                 new int[]{0x22ffffff, 0xffffffff, 0x22ffffff}, null, TileMode.CLAMP);        //为画笔设置线性渲染        mPaint.setShader(mLinearGradient);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        // TODO Auto-generated method stub        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        measureWidth = MeasureSpec.getSize(widthMeasureSpec);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //平移距离的叠加        mTranslate += DELTAX;        //超出边沿就反向        if (mTranslate < 0 || mTranslate > measureWidth){            DELTAX = -DELTAX;        }        mMatrix.setTranslate(mTranslate, 0);        //重新设置 mLinearGradient 的平移,会触发 onSizeChanged 方法        //onSizeChanged 方法调用完毕之后又会调 onDraw,从而循环        mLinearGradient.setLocalMatrix(mMatrix);        postInvalidateDelayed(50);    }}

三、RadialGradient 环形渲染

RadialGradient(float centerX, float centerY, float radius, int[] colors, float[] stops, TileMode tileMode);
前两个参数是环形渐变的圆心坐标,第三个为渐变半径(绘制图形大于这个半径,就按设置的填充方式进行填充),后面几个参数就跟 LinearGradient 一样。

这里写图片描述

水波纹扩散

这里写图片描述
这里主要用到了安卓的属性动画来不停的改变 RadialGradient 渲染的半径,属性动画暂时不讲,先大概了解一点。

public class RippleView extends Button {    // 点击位置    private int mX, mY;    private ObjectAnimator mAnimator;    // 默认半径    private int DEFAULT_RADIUS = 50;    private int mCurRadius = 0;    private RadialGradient mRadialGradient;    private Paint mPaint;    public RippleView(Context context) {        super(context);        init();    }    public RippleView(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    private void init() {        mPaint = new Paint();    }    @Override    public boolean onTouchEvent(MotionEvent event) {        if (mX != event.getX() || mY != mY) {            mX = (int) event.getX();            mY = (int) event.getY();            setRadius(DEFAULT_RADIUS);        }        switch (event.getAction()){            case MotionEvent.ACTION_DOWN:                return true;            case MotionEvent.ACTION_UP:            {                if (mAnimator != null && mAnimator.isRunning()) {                    mAnimator.cancel();                }                if (mAnimator == null) {                    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);    }    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);        }        postInvalidate();    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        canvas.drawCircle(mX, mY, mCurRadius, mPaint);    }}

四、SweepGradient 渐变渲染/梯度渲染

SweepGradient(float cx, float cy, int[] colors, float[] positions);
前两个参数为圆心的 坐标,后两个参数分别是渐变颜色数组和渐变位置数组。

这里写图片描述
注:颜色按顺时针渐变

雷达扫描

这里写图片描述

这个比较简单,直接为 SweepGradient 添加一个任务,不同的变化旋转角度即可。

public class MyGradientView extends View{    private Paint mPaint;    private int start = 0;    private Matrix matrix;    private boolean isStart = false;    private int[] mColors = {Color.BLUE, Color.RED};    public MyGradientView(Context context, AttributeSet attrs) {        super(context, attrs);        mPaint = new Paint();        matrix = new Matrix();    }    @Override    protected void onDraw(Canvas canvas) {        // TODO Auto-generated method stub        super.onDraw(canvas);        SweepGradient mSweepGradient = new SweepGradient(100, 100, mColors, null);        mPaint.setShader(mSweepGradient);        mPaint.setAntiAlias(true);        //设置变化矩阵        mSweepGradient.setLocalMatrix(matrix);        canvas.drawCircle(100, 100, 100, mPaint);        //添加判断,只有第一次绘制才会启动线程        if (!isStart) {            isStart = true;            thread.start();        }    }    private Thread thread = new Thread(new Runnable() {        public void run() {            while (true) {                //超过一圈,重置旋转角度                if(start == 360){                    start = 0;                }                start = start + 2;                  //setRotate 每次回重置矩阵,再重新赋值                matrix.setRotate(start, 100, 100);                  postInvalidate();                  try {                      Thread.sleep(10);                  } catch (InterruptedException e) {                      e.printStackTrace();                  }             }        }      });}

五、ComposeShader 组合渲染

从名字就可以看出来,是把渲染效果组合起来。
ComposeShader(Shader shaderA,Shader shaderB, Xfermode mode)
Parameters;
ComposeShader(Shader shaderA,Shader shaderB, PorterDuff.Mode mode);
前两个参数是要组合的渲染器,最后一个参数是组合的模式。

原创粉丝点击