盛盾科技网站建设,大连建设工程信息网水电,海外直播tiktok,东莞网站建设公司招聘信息上图是paint中的各种set方法 这些属性大多我们都可以见名知意#xff0c;很好理解#xff0c;即便如此#xff0c;哥还是带大家过一遍逐个剖析其用法#xff0c;其中会不定穿插各种绘图类比如Canvas、Xfermode、ColorFilter等等的用法。 set(Paint src) 顾名思义为当前画笔… 上图是paint中的各种set方法 这些属性大多我们都可以见名知意很好理解即便如此哥还是带大家过一遍逐个剖析其用法其中会不定穿插各种绘图类比如Canvas、Xfermode、ColorFilter等等的用法。 set(Paint src) 顾名思义为当前画笔设置一个画笔说白了就是把另一个画笔的属性设置Copy给我们的画笔不累赘了 setARGB(int a, int r, int g, int b) 不扯了别跟我说不懂 setAlpha(int a) 同上 setAntiAlias(boolean aa) 这个上一节我们用到了打开抗锯齿不过我要说明一点抗锯齿是依赖于算法的算法决定抗锯齿的效率在我们绘制棱角分明的图像时比如一个矩形、一张位图我们不需要打开抗锯齿。 setColor(int color) 不扯 setColorFilter(ColorFilter filter) 设置颜色过滤什么意思呢就像拿个筛子把颜色“滤”一遍获取我们想要的色彩结果感觉像是扯蛋白说一样是不是没事我们慢慢说你一定会懂这个方法需要我们传入一个ColorFilter参数同样也会返回一个ColorFilter实例那么ColorFilter类是什么呢追踪源码进去你会发现其里面很简单几乎没有 ColorMatrixColorFilter、LightingColorFilter和PorterDuffColorFilter也就是说我们在setColorFilter(ColorFilter filter)的时候可以直接传入这三个子类对象作为参数那么这三个子类又是什么东西呢首先我们来看看 ColorMatrixColorFilter 中文直译为色彩矩阵颜色过滤器要明白这玩意你得先了解什么是色彩矩阵。在Android中图片是以RGBA像素点的形式加载到内存中的修改这些像素信息需要一个叫做ColorMatrix类的支持其定义了一个4x5的float[]类型的矩阵 ColorMatrix colorMatrix new ColorMatrix(new float[]{1, 0, 0, 0, 0,0, 1, 0, 0, 0,0, 0, 1, 0, 0,0, 0, 0, 1, 0,
}); 其中第一行表示的R红色的向量第二行表示的G绿色的向量第三行表示的B蓝色的向量最后一行表示A透明度的向量这一顺序必须要正确不能混淆这个矩阵不同的位置表示的RGBA值其范围在0.0F至2.0F之间1为保持原图的RGB值。每一行的第五列数字表示偏移值何为偏移值顾名思义当我们想让颜色更倾向于红色的时候就增大R向量中的偏移值想让颜色更倾向于蓝色的时候就增大B向量中的偏移值这是最最朴素的理解但是事实上色彩偏移的概念是基于白平衡来理解的什么是白平衡呢说得简单点就是白色是什么颜色如果大家是个单反爱好者或者会些PS就会很容易理解这个概念在单反的设置参数中有个色彩偏移其定义的就是白平衡的色彩偏移值就是当你去拍一张照片的时候白色是什么颜色的在正常情况下白色是255, 255, 255, 255但是现实世界中我们是无法找到这样的纯白物体的所以在我们用单反拍照之前就会拿一个我们认为是白色的物体让相机记录这个物体的颜色作为白色然后拍摄时整张照片的颜色都会依据这个定义的白色来偏移而这个我们定义的“白色”比如255, 253, 251, 247和纯白255, 255, 255, 255之间的偏移值0, 2, 4, 8我们称之为白平衡的色彩偏移。 那么说了这么多这玩意到底有啥用呢我们来做个test还是接着昨天那个圆环不过我们今天把它改成绘制一个圆并且去掉线程动画的效果因为我们不需要 public class CustomView extends View {private Paint mPaint;// 画笔private Context mContext;// 上下文环境引用public CustomView(Context context) {this(context, null);}public CustomView(Context context, AttributeSet attrs) {super(context, attrs);mContext context;// 初始化画笔initPaint();}/*** 初始化画笔*/private void initPaint() {// 实例化画笔并打开抗锯齿mPaint new Paint(Paint.ANTI_ALIAS_FLAG);/** 设置画笔样式为描边圆环嘛……当然不能填充不然就么意思了* * 画笔样式分三种 * 1.Paint.Style.STROKE描边 * 2.Paint.Style.FILL_AND_STROKE描边并填充* 3.Paint.Style.FILL填充*/mPaint.setStyle(Paint.Style.FILL);// 设置画笔颜色为自定义颜色mPaint.setColor(Color.argb(255, 255, 128, 103));/** 设置描边的粗细单位像素px 注意当setStrokeWidth(0)的时候描边宽度并不为0而是只占一个像素*/mPaint.setStrokeWidth(10);}Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 绘制圆形canvas.drawCircle(MeasureUtil.getScreenSize((Activity) mContext)[0] / 2, MeasureUtil.getScreenSize((Activity) mContext)[1] / 2, 200, mPaint);}
} 运行下是一个橙红色的圆~~是不是有点萝卜头国旗帜的感脚 下面我们为Paint设置一个色彩矩阵 // 生成色彩矩阵
ColorMatrix colorMatrix new ColorMatrix(new float[]{1, 0, 0, 0, 0,0, 1, 0, 0, 0,0, 0, 1, 0, 0,0, 0, 0, 1, 0,
});
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); 再次运行发现没变化啊草是不是感觉被我坑了如果你真的那么认为我只能说你压根就没认真看上面的文字我说过什么值为1时表示什么表示不改变原色彩的值这时我们改变色彩矩阵 // 生成色彩矩阵
ColorMatrix colorMatrix new ColorMatrix(new float[]{0.5F, 0, 0, 0, 0,0, 0.5F, 0, 0, 0,0, 0, 0.5F, 0, 0,0, 0, 0, 1, 0,
});
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); 再次运行 是不是明显不一样了颜色变深了便淳厚了我们通过色彩矩阵与原色彩的计算得出的色彩就是这样的。那它们是如何计算的呢其实说白了就是矩阵之间的运算乘积 矩阵ColorMatrix的一行乘以矩阵MyColor的一列作为矩阵Result的一行这里MyColor的RGBA值我们需要转换为[0, 1]。那么我们依据此公式来计算下我们得到的RGBA值是否跟我们计算得出来的圆的RGBA值一样 我们计算得出最后的RGBA值应该为0.5, 0.25, 0.2, 1 有兴趣的童鞋可以去PS之类的绘图软件里试试看正不正确对不对~~~这里就不演示了看完这里有朋友又会说了这玩意有毛线用啊改个颜色还这么复杂劳资直接setColor多爽没错你这样想是对的因为毕竟我们只是一个颜色可是如果是一张图片呢一张图片可有还几十万色彩呢你麻痹你跟我说setColor那么我们换张图片来试试呗看看是什么样的效果 public class CustomView extends View {private Paint mPaint;// 画笔private Context mContext;// 上下文环境引用private Bitmap bitmap;// 位图private int x,y;// 位图绘制时左上角的起点坐标public CustomView(Context context) {this(context, null);}public CustomView(Context context, AttributeSet attrs) {super(context, attrs);mContext context;// 初始化画笔initPaint();//初始化资源initRes(context);}/*** 初始化画笔*/private void initPaint() {// 实例化画笔mPaint new Paint(Paint.ANTI_ALIAS_FLAG);}/*** 初始化资源*/private void initRes(Context context) {// 获取位图bitmap BitmapFactory.decodeResource(context.getResources(), R.drawable.a);/** 计算位图绘制时左上角的坐标使其位于屏幕中心* 屏幕坐标x轴向左偏移位图一半的宽度* 屏幕坐标y轴向上偏移位图一半的高度*/x MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;y MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;}Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 绘制位图canvas.drawBitmap(bitmap, x, y, mPaint);}
} 如代码所示我们清除了所有的画笔属性设置因为没必要从资源获取一个Bitmap绘制在画布上 一张灰常漂亮的风景图好现在我们来为我们的画笔添加一个颜色过滤 // 生成色彩矩阵
ColorMatrix colorMatrix new ColorMatrix(new float[]{0.5F, 0, 0, 0, 0,0, 0.5F, 0, 0, 0,0, 0, 0.5F, 0, 0,0, 0, 0, 1, 0,
});
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); 大家看到还是刚才那个色彩矩阵运行下看看什么效果呢变暗了对吧没意思我们来点更刺激的改下ColorMatrix矩阵 ColorMatrix colorMatrix new ColorMatrix(new float[]{0.33F, 0.59F, 0.11F, 0, 0,0.33F, 0.59F, 0.11F, 0, 0,0.33F, 0.59F, 0.11F, 0, 0,0, 0, 0, 1, 0,
}); 噢变灰了还是没意思继续改 ColorMatrix colorMatrix new ColorMatrix(new float[]{-1, 0, 0, 1, 1,0, -1, 0, 1, 1,0, 0, -1, 1, 1,0, 0, 0, 1, 0,
}); 哟呵是不是有点类似PS里反相的效果我们常看到的图片都是RGB的颠覆一下思维看看BGR的试试 ColorMatrix colorMatrix new ColorMatrix(new float[]{0, 0, 1, 0, 0,0, 1, 0, 0, 0,1, 0, 0, 0, 0,0, 0, 0, 1, 0,
}); 这样红色的变成了蓝色而蓝色的就变成了红色继续改 ColorMatrix colorMatrix new ColorMatrix(new float[]{0.393F, 0.769F, 0.189F, 0, 0,0.349F, 0.686F, 0.168F, 0, 0,0.272F, 0.534F, 0.131F, 0, 0,0, 0, 0, 1, 0,
}); 是不是有点类似于老旧照片的感脚继续 ColorMatrix colorMatrix new ColorMatrix(new float[]{1.5F, 1.5F, 1.5F, 0, -1,1.5F, 1.5F, 1.5F, 0, -1,1.5F, 1.5F, 1.5F, 0, -1,0, 0, 0, 1, 0,
}); 类似去色后高对比度的效果继续 ColorMatrix colorMatrix new ColorMatrix(new float[]{1.438F, -0.122F, -0.016F, 0, -0.03F,-0.062F, 1.378F, -0.016F, 0, 0.05F,-0.062F, -0.122F, 1.483F, 0, -0.02F,0, 0, 0, 1, 0,
}); 饱和度对比度加强好了不演示了……累死我了截图粘贴上传 这些各种各样的图像效果在哪见过PS?对的还有各种拍照软件拍摄后的特效处理大致原理都是这么来的有人会问爱哥你傻逼么这么多参数怎么玩谁记得而且TMD用参数调颜色我映像中都是直接在各种绘图软件比如PS里拖进度条的这怎么玩淡定如我所说很多时候你压根不需要了解太多原理只需站在巨人的丁丁上即可所以稍安勿躁再下一个系列教程“设计色彩”中爱哥教你玩转色彩并且让设计和开发无缝结合 ColorMatrixColorFilter和ColorMatrix就是这么个东西ColorMatrix类里面也提供了一些实在的方法比如setSaturation(float sat)设置饱和度而且ColorMatrix每个方法都用了阵列的计算如果大家感兴趣可以自己去深挖来看不过我是真心不推荐的~~~ 下面我们来看看ColorFilter的另一个子类 LightingColorFilter 顾名思义光照颜色过滤这肯定是跟光照是有关的了~~该类有且只有一个构造方法 LightingColorFilter (int mul, int add) 这个方法非常非常地简单mul全称是colorMultiply意为色彩倍增而add全称是colorAdd意为色彩添加这两个值都是16进制的色彩值0xAARRGGBB。这个方法使用也是非常的简单。还是拿上面那张图片来说吧比如我们想要去掉绿色 public class CustomView extends View {private Paint mPaint;// 画笔private Context mContext;// 上下文环境引用private Bitmap bitmap;// 位图private int x, y;// 位图绘制时左上角的起点坐标public CustomView(Context context) {this(context, null);}public CustomView(Context context, AttributeSet attrs) {super(context, attrs);mContext context;// 初始化画笔initPaint();// 初始化资源initRes(context);}/*** 初始化画笔*/private void initPaint() {// 实例化画笔mPaint new Paint(Paint.ANTI_ALIAS_FLAG);// 设置颜色过滤mPaint.setColorFilter(new LightingColorFilter(0xFFFF00FF, 0x00000000));}/*** 初始化资源*/private void initRes(Context context) {// 获取位图bitmap BitmapFactory.decodeResource(context.getResources(), R.drawable.a);/** 计算位图绘制时左上角的坐标使其位于屏幕中心* 屏幕坐标x轴向左偏移位图一半的宽度* 屏幕坐标y轴向上偏移位图一半的高度*/x MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;y MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;}Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 绘制位图canvas.drawBitmap(bitmap, x, y, mPaint);}
} 运行后你会发现绿色确实是没了但是原来偏绿的部分现在居然成了红色为毛敬请关注下一系列设计色彩文章哈哈哈当LightingColorFilter(0xFFFFFFFF, 0x00000000)的时候原图是不会有任何改变的如果我们想增加红色的值那么LightingColorFilter(0xFFFFFFFF, 0x00XX0000)就好其中XX取值为00至FF。那么这个方法有什么存在的意义呢存在必定合理这个方法存在一定是有它可用之处的前些天有个盆友在群里问点击一个图片如何直接改变它的颜色而不是为他多准备另一张点击效果的图片这种情况下该方法就派上用场了如下图一个灰色的星星我们点击后让它变成黄色 代码如下注释很清楚我就不再多说了 public class CustomView extends View {private Paint mPaint;// 画笔private Context mContext;// 上下文环境引用private Bitmap bitmap;// 位图private int x, y;// 位图绘制时左上角的起点坐标private boolean isClick;// 用来标识控件是否被点击过public CustomView(Context context) {this(context, null);}public CustomView(Context context, AttributeSet attrs) {super(context, attrs);mContext context;// 初始化画笔initPaint();// 初始化资源initRes(context);setOnClickListener(new OnClickListener() {Overridepublic void onClick(View v) {/** 判断控件是否被点击过*/if (isClick) {// 如果已经被点击了则点击时设置颜色过滤为空还原本色mPaint.setColorFilter(null);isClick false;} else {// 如果未被点击则点击时设置颜色过滤后为黄色mPaint.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0X00FFFF00));isClick true;}// 记得重绘invalidate();}});}/*** 初始化画笔*/private void initPaint() {// 实例化画笔mPaint new Paint(Paint.ANTI_ALIAS_FLAG);}/*** 初始化资源*/private void initRes(Context context) {// 获取位图bitmap BitmapFactory.decodeResource(context.getResources(), R.drawable.a2);/** 计算位图绘制时左上角的坐标使其位于屏幕中心* 屏幕坐标x轴向左偏移位图一半的宽度* 屏幕坐标y轴向上偏移位图一半的高度*/x MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;y MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;}Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 绘制位图canvas.drawBitmap(bitmap, x, y, mPaint);}
} 运行后点击星星即可变成黄色再点击变回灰色当我们不想要颜色过滤的效果时setColorFilter(null)并重绘视图即可那么为什么要叫光照颜色过滤呢原因很简单因为它所呈现的效果就像有色光照在物体上染色一样~~~哎不说这方法了看下一个也是最后一个ColorFilter的子类。 PorterDuffColorFilter PorterDuffColorFilter跟LightingColorFilter一样只有一个构造方法 PorterDuffColorFilter(int color, PorterDuff.Mode mode) 这个构造方法也接受两个值一个是16进制表示的颜色值这个很好理解而另一个是PorterDuff内部类Mode中的一个常量值这个值表示混合模式。那么什么是混合模式呢混合混合必定是有两种东西混才行第一种就是我们设置的color值而第二种当然就是我们画布上的元素了比如这里我们把Color的值设为红色而模式设为PorterDuff.Mode.DARKEN变暗 public class CustomView extends View {private Paint mPaint;// 画笔private Context mContext;// 上下文环境引用private Bitmap bitmap;// 位图private int x, y;// 位图绘制时左上角的起点坐标public CustomView(Context context) {this(context, null);}public CustomView(Context context, AttributeSet attrs) {super(context, attrs);mContext context;// 初始化画笔initPaint();// 初始化资源initRes(context);}/*** 初始化画笔*/private void initPaint() {// 实例化画笔mPaint new Paint(Paint.ANTI_ALIAS_FLAG);// 设置颜色过滤mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DARKEN));}/*** 初始化资源*/private void initRes(Context context) {// 获取位图bitmap BitmapFactory.decodeResource(context.getResources(), R.drawable.a);/** 计算位图绘制时左上角的坐标使其位于屏幕中心* 屏幕坐标x轴向左偏移位图一半的宽度* 屏幕坐标y轴向上偏移位图一半的高度*/x MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;y MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;}Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 绘制位图canvas.drawBitmap(bitmap, x, y, mPaint);}
} 我们尝试在画布上Draw刚才的那张图片看看 变暗了……也变红了……这就是PorterDuff.Mode.DARKEN模式给我们的效果当然PorterDuff.Mode还有其他很多的混合模式大家可以尝试但是这里要注意一点PorterDuff.Mode中的模式不仅仅是应用于图像色彩混合还应用于图形混合比如PorterDuff.Mode.DST_OUT就表示裁剪混合图如果我们在PorterDuffColorFilter中强行设置这些图形混合的模式将不会看到任何对应的效果关于图形混合我们将在下面详解。 setXfermode(Xfermode xfermode) [只对图像有效drawBitmapXXX()] Xfermode国外有大神称之为过渡模式这种翻译比较贴切但恐怕不易理解大家也可以直接称之为图像混合模式因为所谓的“过渡”其实就是图像混合的一种这个方法跟我们上面讲到的setColorFilter蛮相似的 同理可得其必然有一定的子类去实现一些方法供我们使用查看API文档发现其果然有三个子类AvoidXfermode, PixelXorXfermode和PorterDuffXfermode这三个子类实现的功能要比setColorFilter的三个子类复杂得多主要是是涉及到图像处理的一些知识可能对大家来说会比较难以理解不过我会尽量以通俗的方式阐述它们的作用那好先来看看我们的第一个子类 AvoidXfermode 首先我要告诉大家的是这个API因为不支持硬件加速在API 16已经过时了大家可以在HardwareAccel查看那些方法不支持硬件加速……如果想在高于API 16的机子上测试这玩意必须现在应用或手机设置中关闭硬件加速在应用中我们可以通过在AndroidManifest.xml文件中设置application节点下的android:hardwareAccelerated属性为false来关闭硬件加速 applicationandroid:allowBackuptrueandroid:hardwareAcceleratedfalseandroid:icondrawable/ic_launcherandroid:labelstring/app_nameandroid:themeandroid:style/Theme.Black.NoTitleBar.Fullscreen activityandroid:namecom.aigestudio.customviewdemo.activities.MainActivityandroid:labelstring/app_name intent-filteraction android:nameandroid.intent.action.MAIN /category android:nameandroid.intent.category.LAUNCHER //intent-filter/activity
/application AvoidXfermode只有一个含参的构造方法AvoidXfermode(int opColor, int tolerance, AvoidXfermode.Mode mode)其具体实现和ColorFilter一样都被封装在C/C内它怎么实现我们不管我们只要知道这玩意怎么用就行对吧。AvoidXfermode有三个参数第一个opColor表示一个16进制的可以带透明通道的颜色值例如0x12345678第二个参数tolerance表示容差值那么什么是容差呢你可以理解为一个可以标识“精确”或“模糊”的东西待会我们细讲最后一个参数表示AvoidXfermode的具体模式其可选值只有两个AvoidXfermode.Mode.AVOID或者AvoidXfermode.Mode.TARGET两者的意思也非常简单我们先来看 AvoidXfermode.Mode.TARGET 在该模式下Android会判断画布上的颜色是否会有跟opColor不一样的颜色比如我opColor是红色那么在TARGET模式下就会去判断我们的画布上是否有存在红色的地方如果有则把该区域“染”上一层我们画笔定义的颜色否则不“染”色而tolerance容差值则表示画布上的像素和我们定义的红色之间的差别该是多少的时候才去“染”的比如当前画布有一个像素的色值是(200, 20, 13)而我们的红色值为(255, 0, 0)当tolerance容差值为255时即便(200, 20, 13)并不等于红色值也会被“染”色容差值越大“染”色范围越广反之则反空说无凭我们来看看具体的实现和效果 public class CustomView extends View {private Paint mPaint;// 画笔private Context mContext;// 上下文环境引用private Bitmap bitmap;// 位图private AvoidXfermode avoidXfermode;// AV模式private int x, y, w, h;// 位图绘制时左上角的起点坐标public CustomView(Context context) {this(context, null);}public CustomView(Context context, AttributeSet attrs) {super(context, attrs);mContext context;// 初始化画笔initPaint();// 初始化资源initRes(context);}/*** 初始化画笔*/private void initPaint() {// 实例化画笔mPaint new Paint(Paint.ANTI_ALIAS_FLAG);/** 当画布中有跟0XFFFFFFFF色不一样的地方时候才“染”色*/avoidXfermode new AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.TARGET);}/*** 初始化资源*/private void initRes(Context context) {// 获取位图bitmap BitmapFactory.decodeResource(context.getResources(), R.drawable.a);/** 计算位图绘制时左上角的坐标使其位于屏幕中心* 屏幕坐标x轴向左偏移位图一半的宽度* 屏幕坐标y轴向上偏移位图一半的高度*/x MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;y MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;w MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 bitmap.getWidth() / 2;h MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 bitmap.getHeight() / 2;}Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 先绘制位图canvas.drawBitmap(bitmap, x, y, mPaint);// “染”什么色是由我们自己决定的mPaint.setARGB(255, 211, 53, 243);// 设置AV模式mPaint.setXfermode(avoidXfermode);// 画一个位图大小一样的矩形canvas.drawRect(x, y, w, h, mPaint);}
} 在高于API 16的测试机上会得到一个矩形的色块API 16的都类似改ROM和关闭了硬件加速的除外 我们再用低于API 16或高于API 16但关闭了硬件加速的测试机运行就会得到另一个不同的效果 大家可以看到在我们的模式为TARGET容差值为0的时候此时只有当图片中像色颜色值为0XFFFFFFFF的地方才会被染色而其他地方不会有改变 AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.TARGET) 而当容差值为255的时候只要是跟0XFFFFFFFF有点接近的地方都会被染色 而另外一种模式 AvoidXfermode.Mode.AVOID 则与TARGET恰恰相反TARGET是我们指定的颜色是否与画布的颜色一样而AVOID是我们指定的颜色是否与画布不一样其他的都与TARGET类似 AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.AVOID) 当模式为AVOID容差值为0时只有当图片中像素颜色值与0XFFFFFFFF完全不一样的地方才会被染色 AvoidXfermode(0XFFFFFFFF, 255, AvoidXfermode.Mode.AVOID) 当容差值为255时只要与0XFFFFFFFF稍微有点不一样的地方就会被染色 那么这玩意究竟有什么用呢比如说当我们只想在白色的区域画点东西或者想把白色区域的地方替换为另一张图片的时候就可以采取这种方式 Xfermode的第二个子类 PixelXorXfermode 与AvoidXfermode一样也在API 16过时了该类也提供了一个含参的构造方法PixelXorXfermode(int opColor)该类的计算实现很简单从官方给出的计算公式来看就是op ^ src ^ dst像素色值的按位异或运算如果大家感兴趣可以自己用一个纯色去尝试并自己计算异或运算的值是否与得出的颜色值一样这里我就不讲了Because it was deprecated and useless。 Xfermode的最后一个子类也是惟一一个没有过时且沿用至今的子类 PorterDuffXfermode 该类同样有且只有一个含参的构造方法PorterDuffXfermode(PorterDuff.Mode mode)这个PorterDuff.Mode大家看后是否会有些面熟它跟上面我们讲ColorFilter时候用到的PorterDuff.Mode是一样的麻雀虽小五脏俱全虽说构造方法的签名列表里只有一个PorterDuff.Mode的参数但是它可以实现很多酷毙的图形效果而PorterDuffXfermode就是图形混合模式的意思其概念最早来自于SIGGRAPH的Tomas Proter和Tom Duff混合图形的概念极大地推动了图形图像学的发展延伸到计算机图形图像学像Adobe和AutoDesk公司著名的多款设计软件都可以说一定程度上受到影响而我们PorterDuffXfermode的名字也来源于这俩人的人名组合PorterDuff那PorterDuffXfermode能做些什么呢我们先来看一张API DEMO里的图片 这张图片从一定程度上形象地说明了图形混合的作用两个图形一圆一方通过一定的计算产生不同的组合效果在API中Android为我们提供了18种比上图多了两种ADD和OVERLAY模式 来定义不同的混合效果这18种模式Android还为我们提供了它们的计算方式比如LIGHTEN的计算方式为[Sa Da - Sa*Da, Sc*(1 - Da) Dc*(1 - Sa) max(Sc, Dc)]其中Sa全称为Source alpha表示源图的Alpha通道Sc全称为Source color表示源图的颜色Da全称为Destination alpha表示目标图的Alpha通道Dc全称为Destination color表示目标图的颜色细心的朋友会发现“[……]”里分为两部分其中“,”前的部分为“Sa Da - Sa*Da”这一部分的值代表计算后的Alpha通道而“,”后的部分为“Sc*(1 - Da) Dc*(1 - Sa) max(Sc, Dc)”这一部分的值代表计算后的颜色值图形混合后的图片依靠这个矢量来计算ARGB的值如果大家感兴趣可以查看维基百科中对Alpha合成的解释http://en.wikipedia.org/wiki/Alpha_compositing。作为一个猿我们不需要知道复杂的图形学计算但是一定要知道这些模式会为我们提供怎样的效果当大家看到上面API DEMO给出的效果时一定会觉得PorterDuffXfermode其实就是简单的图形交并集计算比如重叠的部分删掉或者叠加等等事实上呢PorterDuffXfermode的计算绝非是根据于此上面我们也说了PorterDuffXfermode的计算是要根据具体的Alpha值和RGB值的既然如此我们就来看一个比API DEMO稍微复杂的例子来更有力地说明PorterDuffXfermode是如何工作而我们又能用它做些什么在这个例子中我将用到两个带有Alpha通道的渐变图形Bitmap 我们将在不同的模式下混合这两个Bitmap来看看这两个渐变色的颜色值在不同的混合模式下究竟发生了什么先看看我们的测试代码 TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class PorterDuffView extends View {/** PorterDuff模式常量* 可以在此更改不同的模式测试*/private static final PorterDuff.Mode MODE PorterDuff.Mode.ADD;private static final int RECT_SIZE_SMALL 400;// 左右上方示例渐变正方形的尺寸大小private static final int RECT_SIZE_BIG 800;// 中间测试渐变正方形的尺寸大小private Paint mPaint;// 画笔private PorterDuffBO porterDuffBO;// PorterDuffView类的业务对象private PorterDuffXfermode porterDuffXfermode;// 图形混合模式private int screenW, screenH;// 屏幕尺寸private int s_l, s_t;// 左上方正方形的原点坐标private int d_l, d_t;// 右上方正方形的原点坐标private int rectX, rectY;// 中间正方形的原点坐标public PorterDuffView(Context context, AttributeSet attrs) {super(context, attrs);// 实例化画笔并设置抗锯齿mPaint new Paint(Paint.ANTI_ALIAS_FLAG);// 实例化业务对象porterDuffBO new PorterDuffBO();// 实例化混合模式porterDuffXfermode new PorterDuffXfermode(MODE);// 计算坐标calu(context);}/*** 计算坐标* * param context* 上下文环境引用*/private void calu(Context context) {// 获取包含屏幕尺寸的数组int[] screenSize MeasureUtil.getScreenSize((Activity) context);// 获取屏幕尺寸screenW screenSize[0];screenH screenSize[1];// 计算左上方正方形原点坐标s_l 0;s_t 0;// 计算右上方正方形原点坐标d_l screenW - RECT_SIZE_SMALL;d_t 0;// 计算中间方正方形原点坐标rectX screenW / 2 - RECT_SIZE_BIG / 2;rectY RECT_SIZE_SMALL (screenH - RECT_SIZE_SMALL) / 2 - RECT_SIZE_BIG / 2;}Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 设置画布颜色为黑色以便我们更好地观察canvas.drawColor(Color.BLACK);// 设置业务对象尺寸值计算生成左右上方的渐变方形porterDuffBO.setSize(RECT_SIZE_SMALL);/** 画出左右上方两个正方形* 其中左边的的为src右边的为dis*/canvas.drawBitmap(porterDuffBO.initSrcBitmap(), s_l, s_t, mPaint);canvas.drawBitmap(porterDuffBO.initDisBitmap(), d_l, d_t, mPaint);/** 将绘制操作保存到新的图层更官方的说法应该是离屏缓存我们将在1/3中学习到Canvas的全部用法这里就先follow me*/int sc canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.ALL_SAVE_FLAG);// 重新设置业务对象尺寸值计算生成中间的渐变方形porterDuffBO.setSize(RECT_SIZE_BIG);// 先绘制dis目标图canvas.drawBitmap(porterDuffBO.initDisBitmap(), rectX, rectY, mPaint);// 设置混合模式mPaint.setXfermode(porterDuffXfermode);// 再绘制src源图canvas.drawBitmap(porterDuffBO.initSrcBitmap(), rectX, rectY, mPaint);// 还原混合模式mPaint.setXfermode(null);// 还原画布canvas.restoreToCount(sc);}
} 代码中我们使用到了View的离屏缓冲也通俗地称之为层这个概念很简单我们在绘图的时候新建一个“层”所有的绘制操作都在该层上而不影响该层以外的图像比如代码中我们在绘制了画布颜色和左右上方两个方形后就新建了一个图层来绘制中间的大正方形这个方形和左右上方的方形是在两个不同的层上的 注图中所显示色彩效果与我们的代码不同上图只为演示图层概念 当我们绘制完成后要通过restore将所有缓冲层中的绘制操作还原到画布以结束绘制具体关于画布的知识在自定义控件其实很简单1/3这里就不多说了下面我们看具体各种模式的计算效果 PSSrc为源图像意为将要绘制的图像Dis为目标图像意为我们将要把源图像绘制到的图像……是不是感脚很拗口 Fuck……意会意会~~ PorterDuff.Mode.ADD 计算方式Saturate(S D)Chinese饱和相加 从计算方式和显示的结果我们可以看到ADD模式简单来说就是对图像饱和度进行相加这个模式在应用中不常用我唯一一次使用它是通过代码控制RGB通道的融合生成图片。 PorterDuff.Mode.CLEAR 计算方式[0, 0]Chinese清除 清除图像很好理解不扯了。 PorterDuff.Mode.DARKEN 计算方式[Sa Da - Sa*Da, Sc*(1 - Da) Dc*(1 - Sa) min(Sc, Dc)]Chinese变暗 这个模式计算方式目测很复杂其实效果很好理解两个图像混合较深的颜色总是会覆盖较浅的颜色如果两者深浅相同则混合如图黄色覆盖了红色而蓝色和青色因为是跟透明混合所以不变。细心的朋友会发现青色和黄色之间有一层类似橙色的过渡色这就是混合的结果。在实际的测试中源图和目标图的DARKEN混合偶尔会有相反的结果比如红色覆盖了黄色这源于Android对颜色值“深浅”的定义我暂时没有在官方查到有关资料不知道是否与图形图像学一致。DARKEN模式的应用在图像色彩方面比较广泛我们可以利用其特性来获得不同的成像效果这点与之前介绍的ColorFilter有点类似。 该模式处理过后会感觉效果变暗即进行对应像素的比较取较暗值如果色值相同则进行混合 从算法上看alpha值变大色值上如果都不透明则取较暗值非完全不透明情况下使用上面算法进行计算受到源图和目标图对应色值和alpha值影响 PorterDuff.Mode.DST 计算方式[Da, Dc]Chinese只绘制目标图像 如Chinese所说很好理解。 PorterDuff.Mode.DST_ATOP 计算方式[Sa, Sa * Dc Sc * (1 - Da)]Chinese在源图像和目标图像相交的地方绘制目标图像而在不相交的地方绘制源图像 PorterDuff.Mode.DST_IN 计算方式[Sa * Da, Sa * Dc]Chinese只在源图像和目标图像相交的地方绘制目标图像 PorterDuff.Mode.DST_OUT 计算方式[Da * (1 - Sa), Dc * (1 - Sa)]Chinese只在源图像和目标图像不相交的地方绘制目标图像 PorterDuff.Mode.DST_OVER 计算方式[Sa (1 - Sa)*Da, Rc Dc (1 - Da)*Sc]Chinese在源图像的上方绘制目标图像 这个就不说啦就是两个图片谁在上谁在下的意思 PorterDuff.Mode.LIGHTEN 计算方式[Sa Da - Sa*Da, Sc*(1 - Da) Dc*(1 - Sa) max(Sc, Dc)]Chinese变亮 可以和 DARKEN 对比起来看DARKEN 的目的是变暗LIGHTEN 的目的则是变亮如果在均完全不透明的情况下 色值取源色值和目标色值中的较大值 PorterDuff.Mode.MULTIPLY 计算方式[Sa * Da, Sc * Dc]Chinese正片叠底 该模式通俗的计算方式很简单源图像素颜色值乘以目标图像素颜色值除以255即得混合后图像像素的颜色值该模式在设计领域应用广泛因为其特性黑色与任何颜色混合都会得黑色在手绘的上色、三维动画的UV贴图绘制都有应用具体效果大家自己尝试我就不说了 PorterDuff.Mode.OVERLAY 计算方式未给出Chinese叠加 这个模式没有在官方的API DEMO中给出谷歌也没有给出其计算方式在实际效果中其对亮色和暗色不起作用也就是说黑白色无效它会将源色与目标色混合产生一种中间色这种中间色生成的规律也很简单如果源色比目标色暗那么让目标色的颜色倍增否则颜色递减。 PorterDuff.Mode.SCREEN 计算方式[Sa Da - Sa * Da, Sc Dc - Sc * Dc]Chinese滤色 计算方式我不解释了滤色产生的效果我认为是Android提供的几个色彩混合模式中最好的它可以让图像焦媃幻化有一种色调均和的感觉 PorterDuff.Mode.SRC 计算方式[Sa, Sc]Chinese显示源图 只绘制源图SRC类的模式跟DIS的其实差不多就不多说了 PorterDuff.Mode.SRC_ATOP 计算方式[Da, Sc * Da (1 - Sa) * Dc]Chinese在源图像和目标图像相交的地方绘制源图像在不相交的地方绘制目标图像 PorterDuff.Mode.SRC_IN 计算方式[Sa * Da, Sc * Da]Chinese只在源图像和目标图像相交的地方绘制源图像 PorterDuff.Mode.SRC_OUT 计算方式[Sa * (1 - Da), Sc * (1 - Da)]Chinese只在源图像和目标图像不相交的地方绘制源图像 PorterDuff.Mode.SRC_OVER 计算方式[Sa (1 - Sa)*Da, Rc Sc (1 - Sa)*Dc]Chinese在目标图像的顶部绘制源图像 PorterDuff.Mode.XOR 计算方式[Sa Da - 2 * Sa * Da, Sc * (1 - Da) (1 - Sa) * Dc]Chinese在源图像和目标图像重叠之外的任何地方绘制他们而在重叠的地方不绘制任何内容 一张详细的图看了就明白 更多资料请参考 http://www.youtube.com/watch?vduefsFTJXzcfeatureresults_videoplaynext1listPL01724209851DF753 http://ssp.impulsetrain.com/porterduff.html http://stackoverflow.com/questions/8280027/what-does-porterduff-mode-mean-in-android-graphics-what-does-it-do Paint类中我们还有一个方法没讲 setShader(Shader shader) 这个方法呢其实也没有什么特别的那么为什么我们要把它单独分离出来讲那么异类呢难道它贿赂了我吗显然不是的哥视金钱如粪土我的要求很低只需要一克反物质即可怎么可能做出如此下三滥的事情之所以要把这货单独拿出来是为了引出Android在图形变换中非常重要的一个类这个类是什么呢我也先不说咱还是先来看看Shader Shader类呢也是个灰常灰常简单的类它有五个子类像PathEffect一样每个子类都实现了一种ShaderShader在三维软件中我们称之为着色器其作用嘛就像它的名字一样是来给图像着色的或者更通俗的说法是上色这么说该懂了吧再不懂去厕所哭去这五个Shader里最异类的是BitmapShader因为只有它是允许我们载入一张图片来给图像着色那我们还是先来看看这个怪胎吧 BitmapShader 只有一个含参的构造方法BitmapShader (Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)而其他的四个兄弟姐妹呢都有两个它只有一个蛋又一魂谈那好吧我们来看看它是什么个效果顺便呢也学习一下Shader的用法先来看我们熟悉的代码 public class ShaderView extends View {private static final int RECT_SIZE 400;// 矩形尺寸的一半private Paint mPaint;// 画笔private int left, top, right, bottom;// 矩形坐上右下坐标public ShaderView(Context context, AttributeSet attrs) {super(context, attrs);// 获取屏幕尺寸数据int[] screenSize MeasureUtil.getScreenSize((Activity) context);// 获取屏幕中点坐标int screenX screenSize[0] / 2;int screenY screenSize[1] / 2;// 计算矩形左上右下坐标值left screenX - RECT_SIZE;top screenY - RECT_SIZE;right screenX RECT_SIZE;bottom screenY RECT_SIZE;// 实例化画笔mPaint new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);// 获取位图Bitmap bitmap BitmapFactory.decodeResource(getResources(), R.drawable.a);// 设置着色器mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));}Overrideprotected void onDraw(Canvas canvas) {// 绘制矩形canvas.drawRect(left, top, right, bottom, mPaint);}
} 如果上面我们没有设置Shader mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); 那么我们Draw出来的图像一定是一个位于屏幕正中黑色的正方形但是我们设置了Shader后还是一样的吗看看效果 我靠这什么玩意罪过罪过真是看不懂别急Shader.TileMode里有三种模式CLAMP、MIRROR和REPETA我们看看其他两种模式是什么效果呢 mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)); 诶这效果还能接受我们还能看得出一点效果说白了就是上下左右的镜像而已那再看看REPETA mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)); 这个就更简单了明显的一个重复效果而REPEAT也就是重复的意思同理MIRROR也就是镜像的意思这个很好理解吧。那第一个CLAMP模式究竟特么的是什么东西呢看效果根本看不出来我们不妨换个思维BitmapShader (Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)的第一个参数是位图这个很显然而后两个参数则分别表示XY方向上的着色模式既然可以分开设置那么我们是不是可以这样设置一个Shader呢 mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.MIRROR)); 也就是说我们在X轴方向上采取CLAMP模式而Y轴方向上采取MIRROR模式那么这样肯定是可行的撒 大家可以看到图像分为两部分左边呢Y轴镜像了而右边像是被拉伸了一样怪怪的其实CLAMP的意思就是边缘拉伸的意思比如上图中左边Y轴镜像了而右边会紧挨着左边将图像边缘上的第一个像素沿X轴复制产生一种被拉伸的效果就像扯蛋不过这里扯的不是蛋而是图像边缘的第一个像素就是这么简单。但是作为一个严谨的男人必须要又一个严谨的态度这时我就会想如果两种模式互换会怎样呢比如 mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.CLAMP)); 这样来看应该是X轴会镜像而Y轴会拉伸对吧看看效果 这…………好像跟我们想象中的不大一样唉……是我们做错了吗不是的结合上一个例子大家有没有注意BitmapShader是先应用了Y轴的模式而X轴是后应用的所以着色是先在Y轴拉伸了然后再沿着X轴重复对吧阴笑ing…… 来看看另一个Shader叫做LinearGradient 线性渐变顾名思义这锤子玩意就是来画渐变的实际上Shader的五个子类中除了上面我们说的那个怪胎还有个变形金刚ComposeShader外其余三个都是渐变只是效果不同而已而这个LinearGradient线性渐变一说大家估计都懂先来看张效果图 是不是秒懂了恩说明你头脑简单这个实现也很简单具体代码跟上面的BitmapShader一样只是把BitmapShader换成了LinearGradient而已 mPaint.setShader(new LinearGradient(left, top, right, bottom, Color.RED, Color.YELLOW, Shader.TileMode.REPEAT)); 上面我们提到过除了BitmapShader外其他子类都有两个构造方法上面我们用到了 LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile) 这是LinearGradient最简单的一个构造方法参数虽多其实很好理解x0和y0表示渐变的起点坐标而x1和y1则表示渐变的终点坐标这两点都是相对于屏幕坐标系而言的而color0和color1则表示起点的颜色和终点的颜色这些即便是213也能懂 - - ……Shader.TileMode上面我们给的是REPEAT重复但是并没有任何效果这时因为我们渐变的起点和终点都落在了图形的两端整个渐变shader已经填充了图形所以不起作用如果我们改改把终点坐标变一下 mPaint.setShader(new LinearGradient(left, top, right - RECT_SIZE, bottom - RECT_SIZE, Color.RED, Color.YELLOW, Shader.TileMode.REPEAT)); 此时我们渐变终点坐标落在了图形的终点上根据我们的REPEAT模式会呈现一个渐变重复的效果 仅仅两种颜色的渐变根本无法满足我们身体的欲望太单调乏味我们是不是可以定义多种颜色渐变呢答案是必须的LinearGradient的另一个构造方法 LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile) 就为我们实现了这么一个功能 mPaint.setShader(new LinearGradient(left, top, right, bottom, new int[] { Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE }, new float[] { 0, 0.1F, 0.5F, 0.7F, 0.8F }, Shader.TileMode.MIRROR)); 前面四个参数也是定义坐标的不扯了colors是一个int型数组我们用来定义所有渐变的颜色positions表示的是渐变的相对区域其取值只有0到1上面的代码中我们定义了一个[0, 0.1F, 0.5F, 0.7F, 0.8F]意思就是红色到黄色的渐变起点坐标在整个渐变区域left, top, right, bottom定义了渐变的区域的起点而终点则在渐变区域长度 * 10%的地方而黄色到绿色呢则从渐变区域10%开始到50%的地方以此类推positions可以为空 mPaint.setShader(new LinearGradient(left, top, right, bottom, new int[] { Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE }, null, Shader.TileMode.MIRROR)); 为空时各种颜色的渐变将会均分整个渐变区域 SweepGradient的意思是梯度渐变也称之为扫描式渐变因为其效果有点类似雷达的扫描效果他也有两个构造方法 SweepGradient(float cx, float cy, int color0, int color1) 其实都跟LinearGradient差不多的简直没什么可说的直接上效果跳过无聊的讲解 mPaint.setShader(new SweepGradient(screenX, screenY, Color.RED, Color.YELLOW)); RadialGradient 环形渲染简单点就是个圆形中心向四周渐变的效果 RadialGradient (float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode) RadialGradient (float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode) ComposeShader 就是组合Shader的意思顾名思义就是两个Shader组合在一起作为一个新Shader……老掉牙的剧情是吧同样这锤子玩意也有两个构造方法 ComposeShader (Shader shaderA, Shader shaderB, Xfermode mode)
ComposeShader (Shader shaderA, Shader shaderB, PorterDuff.Mode mode) 两个都差不多的只不过一个指定了只能用PorterDuff的混合模式而另一个只要是Xfermode下的混合模式都没问题 实例 最上面的是BitmapShader效果图第二排的左边是LinearGradient的效果图第二排的右边是RadialGradient的效果图第三排的左边是ComposeShader的效果图LinearGradient与RadialGradient的混合效果第三排的右边是SweepGradient的效果图。 MyView.Java源码
package com.example.android_imageshader;import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ComposeShader;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.graphics.drawable.BitmapDrawable;
import android.view.View;SuppressLint({ DrawAllocation, DrawAllocation, DrawAllocation })
public class MyView extends View {Bitmap mBitmap null; //Bitmap对象 Shader mBitmapShader null; //Bitmap渲染对象Shader mLinearGradient null; //线性渐变渲染对象Shader mComposeShader null; //混合渲染对象Shader mRadialGradient null; //环形渲染对象Shader mSweepGradient null; //梯度渲染对象public MyView(Context context) {super(context);//加载图像资源mBitmap ((BitmapDrawable) getResources().getDrawable(R.drawable.snow)).getBitmap();//创建Bitmap渲染对象mBitmapShader new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);//创建线性渲染对象int mColorLinear[] {Color.RED, Color.GREEN, Color.BLUE, Color.WHITE}; mLinearGradient new LinearGradient(0, 0, 100, 100, mColorLinear, null, Shader.TileMode.REPEAT);//创建环形渲染对象int mColorRadial[] {Color.GREEN, Color.RED, Color.BLUE, Color.WHITE};mRadialGradient new RadialGradient(350, 325, 75, mColorRadial, null, Shader.TileMode.REPEAT);//创建混合渲染对象mComposeShader new ComposeShader(mLinearGradient, mRadialGradient, PorterDuff.Mode.DARKEN);//创建梯形渲染对象int mColorSweep[] {Color.GREEN, Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN};mSweepGradient new SweepGradient(370, 495, mColorSweep, null); }public void onDraw(Canvas canvas) {super.onDraw(canvas);Paint mPaint new Paint();canvas.drawColor(Color.GRAY); //背景置为灰色//绘制Bitmap渲染的椭圆mPaint.setShader(mBitmapShader);canvas.drawOval(new RectF(90, 20, 90mBitmap.getWidth(), 20mBitmap.getHeight()), mPaint);//绘制线性渐变的矩形mPaint.setShader(mLinearGradient);canvas.drawRect(10, 250, 250, 400, mPaint);//绘制环形渐变的圆mPaint.setShader(mRadialGradient);canvas.drawCircle(350, 325, 75, mPaint);//绘制混合渐变(线性与环形混合)的矩形mPaint.setShader(mComposeShader);canvas.drawRect(10, 420, 250, 570, mPaint);//绘制梯形渐变的矩形mPaint.setShader(mSweepGradient);canvas.drawRect(270, 420, 470, 570, mPaint);}
} 注意BitmapShader是从画布的左上方开始着色回到我们刚才的问题这个着色方式必须是这样的么显然不是在Shader类中有一对setter和getter方法setLocalMatrix(Matrix localM)和getLocalMatrix(Matrix localM)我们可以利用它们来设置或获取Shader的变换矩阵比如上面的例子我还是绘制成一个边长为800的矩形 public class ShaderView extends View {private static final int RECT_SIZE 400;// 矩形尺寸的一半private Paint mPaint;// 画笔private int left, top, right, bottom;// 矩形坐上右下坐标private int screenX, screenY;public ShaderView(Context context, AttributeSet attrs) {super(context, attrs);// 获取屏幕尺寸数据int[] screenSize MeasureUtil.getScreenSize((Activity) context);// 获取屏幕中点坐标screenX screenSize[0] / 2;screenY screenSize[1] / 2;// 计算矩形左上右下坐标值left screenX - RECT_SIZE;top screenY - RECT_SIZE;right screenX RECT_SIZE;bottom screenY RECT_SIZE;// 实例化画笔mPaint new Paint(Paint.ANTI_ALIAS_FLAG);// 获取位图Bitmap bitmap BitmapFactory.decodeResource(getResources(), R.drawable.a);// 实例化一个ShaderBitmapShader bitmapShader new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);// 实例一个矩阵对象Matrix matrix new Matrix();// 设置矩阵变换matrix.setTranslate(left, top);// 设置Shader的变换矩阵bitmapShader.setLocalMatrix(matrix);// 设置着色器mPaint.setShader(bitmapShader);// mPaint.setShader(new LinearGradient(left, top, right - RECT_SIZE, bottom - RECT_SIZE, Color.RED, Color.YELLOW, Shader.TileMode.MIRROR));// mPaint.setShader(new LinearGradient(left, top, right, bottom, new int[] { Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE }, null, Shader.TileMode.MIRROR));// mPaint.setShader(new SweepGradient(screenX, screenY, Color.RED, Color.YELLOW));// mPaint.setShader(new SweepGradient(screenX, screenY, new int[] { Color.GREEN, Color.WHITE, Color.GREEN }, null));}Overrideprotected void onDraw(Canvas canvas) {// 绘制矩形canvas.drawRect(left, top, right, bottom, mPaint);// canvas.drawRect(0, 0, screenX * 2, screenY * 2, mPaint);}
} 不一样的是我在给画笔设置着色器前为我们的着色器设置了一个变换矩阵让我们的Shader依据自身的坐标→平移left个单位↓平移top个单位也就是说原本shader的原点应该是画布注意不是屏幕这里只是刚好画布更屏幕重合了而已切记的左上方[0,0]的位置通过变换移至了[left,top]的位置如果没问题Shader此时应该是刚好是从我们矩形的左上方开始着色 Matrix Matrix是一个3 x 3的矩阵他对图片的处理分为四个基本类型 1、Translate————平移变换 2、Scale————缩放变换 3、Rotate————旋转变换 4、Skew————错切变换 在Android的API里对于每一种变换都提供了三种操作方式set用于设置Matrix中的值、post后乘根据矩阵的原理相当于左乘、pre先乘相当于矩阵中的右乘。默认时这四种变换都是围绕00点变换的当然可以自定义围绕的中心点通常围绕中心点。 首先说说平移在对图片处理的过程中最常用的就是对图片进行平移操作该方法为setTranslate()平移意味着在x轴和y轴上简单地移动图像。setTranslate方法采用两个浮点数作为参数表示在每个轴上移动的数量。第一个参数是图像将在x轴上移动的数量而第二个参数是图像将在y轴上移动的数量。在x轴上使用正数进行平移将向右移动图像而使用负数将向左移动图像。在y轴上使用正数进行平移将向下移动图像而使用负数将向上移动图像。 再看缩放Matrix类中另一个有用的方法是setScale方法。它采用两个浮点数作为参数分别表示在每个轴上所产生的缩放量。第一个参数是x轴的缩放比例而第二个参数是y轴的缩放比例。如matrix.setScale(1.5f,1);比较复杂的就是图片的旋转了内置的方法之一是setRotate方法。它采用一个浮点数表示旋转的角度。围绕默认点(0,0)正数将顺时针旋转图像而负数将逆时针旋转图像其中默认点是图像的左上角如 Matrix matrix new Matrix();matrix.setRotate(15); 另外也可以使用旋转的角度及围绕的旋转点作为参数调用setRotate方法。选择图像的中心点作为旋转点如matrix.setRotate(15,bmp.getWidth()/2,bmp.getHeight()/2); 对于错切变换在数学上又称为Shear mapping(可译为“剪切变换”)或者Transvection(缩并)它是一种比较特殊的线性变换。错切变换的效果就是让所有点的x坐标(或者y坐标)保持不变而对应的y坐标(或者x坐标)则按比例发生平移且平移的大小和该点到x轴(或y轴)的垂直距离成正比。错切变换属于等面积变换即一个形状在错切变换的前后其面积是相等的。 对于程序中一个特别有用的方法对是setScale和postTranslate它们允许跨单个轴(或者两个轴)翻转图像。如果以一个负数缩放那么会将该图像绘制到坐标系统的负值空间。由于(0,0)点位于左上角使用x轴上的负数会导致向左绘制图像。因此我们需要使用postTranslate方法将图像向右移动如 matrix.setScale(-1, 1);matrix.postTranslate(bmp.getWidth(),0); 可以在y轴上做同样的事情翻转图像以使其倒置。通过将图像围绕两个轴上的中心点旋转180°可以实现相同的效果如matrix.setScale(1, -1);matrix.postTranslate(0, bmp.getHeight()); 注意每一次setXXX方法都会把前面的变化重置 举例 1.matrix.preScale(0.5f, 1); 2.matrix.preTranslate(10, 0); 3.matrix.postScale(0.7f, 1); 4.matrix.postTranslate(15, 0); 等价于translate(10, 0) - scale(0.5f, 1) - scale(0.7f, 1) - translate(15, 0)注意后调用的pre操作先执行而后调用的post操作则后执行。 set方法一旦调用即会清空之前matrix中的所有变换例如1.matrix.preScale(0.5f, 1); 2.matrix.setScale(1, 0.6f); 3.matrix.postScale(0.7f, 1); 4.matrix.preTranslate(15, 0); 等价于translate(15, 0) - scale(1, 0.6f) - scale(0.7f, 1) matrix.preScale (0.5f, 1)将不起作用。 可以使用Matrix的getValues(float[])方法去验证自己不确定的东西同时呢我们也可以使用Matrix的setValues(float[])方法来直接给Matrix设置一个矩阵数组 好了对Matrix的一个简单介绍就到这里正如我所说Matrix的应用是相当广泛的不仅仅是在我们的Shader我们的canvas也有setMatrix(matrix)方法来设置矩阵变换更常见的是在ImageView中对ImageView进行变换当我们手指在屏幕上划过一定的距离后根据这段距离来平移我们的控件根据两根手指之间拉伸的距离和相对于上一次旋转的角度来缩放旋转我们的图片 public class MatrixImageView extends ImageView {private static final int MODE_NONE 0x00123;// 默认的触摸模式private static final int MODE_DRAG 0x00321;// 拖拽模式private static final int MODE_ZOOM 0x00132;// 缩放or旋转模式private int mode;// 当前的触摸模式private float preMove 1F;// 上一次手指移动的距离private float saveRotate 0F;// 保存了的角度值private float rotate 0F;// 旋转的角度private float[] preEventCoor;// 上一次各触摸点的坐标集合private PointF start, mid;// 起点、中点对象private Matrix currentMatrix, savedMatrix;// 当前和保存了的Matrix对象private Context mContext;// Fuck……public MatrixImageView(Context context, AttributeSet attrs) {super(context, attrs);this.mContext context;// 初始化init();}/*** 初始化*/private void init() {/** 实例化对象*/currentMatrix new Matrix();savedMatrix new Matrix();start new PointF();mid new PointF();// 模式初始化mode MODE_NONE;/** 设置图片资源*/Bitmap bitmap BitmapFactory.decodeResource(getResources(), R.drawable.mylove);bitmap Bitmap.createScaledBitmap(bitmap, MeasureUtil.getScreenSize((Activity) mContext)[0], MeasureUtil.getScreenSize((Activity) mContext)[1], true);setImageBitmap(bitmap);}Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction() MotionEvent.ACTION_MASK) {case MotionEvent.ACTION_DOWN:// 单点接触屏幕时savedMatrix.set(currentMatrix);start.set(event.getX(), event.getY());mode MODE_DRAG;preEventCoor null;break;case MotionEvent.ACTION_POINTER_DOWN:// 第二个点接触屏幕时preMove calSpacing(event);if (preMove 10F) {savedMatrix.set(currentMatrix);calMidPoint(mid, event);mode MODE_ZOOM;}preEventCoor new float[4];preEventCoor[0] event.getX(0);preEventCoor[1] event.getX(1);preEventCoor[2] event.getY(0);preEventCoor[3] event.getY(1);saveRotate calRotation(event);break;case MotionEvent.ACTION_UP:// 单点离开屏幕时case MotionEvent.ACTION_POINTER_UP:// 第二个点离开屏幕时mode MODE_NONE;preEventCoor null;break;case MotionEvent.ACTION_MOVE:// 触摸点移动时/** 单点触控拖拽平移*/if (mode MODE_DRAG) {currentMatrix.set(savedMatrix);float dx event.getX() - start.x;float dy event.getY() - start.y;currentMatrix.postTranslate(dx, dy);}/** 两点触控拖放旋转*/else if (mode MODE_ZOOM event.getPointerCount() 2) {float currentMove calSpacing(event);currentMatrix.set(savedMatrix);/** 指尖移动距离大于10F缩放*/if (currentMove 10F) {float scale currentMove / preMove;currentMatrix.postScale(scale, scale, mid.x, mid.y);}/** 保持两点时旋转*/if (preEventCoor ! null) {rotate calRotation(event);float r rotate - saveRotate;currentMatrix.postRotate(r, getMeasuredWidth() / 2, getMeasuredHeight() / 2);}}break;}setImageMatrix(currentMatrix);return true;}/*** 计算两个触摸点间的距离*/private float calSpacing(MotionEvent event) {float x event.getX(0) - event.getX(1);float y event.getY(0) - event.getY(1);return (float) Math.sqrt(x * x y * y);}/*** 计算两个触摸点的中点坐标*/private void calMidPoint(PointF point, MotionEvent event) {float x event.getX(0) event.getX(1);float y event.getY(0) event.getY(1);point.set(x / 2, y / 2);}/*** 计算旋转角度* * param 事件对象* return 角度值*/private float calRotation(MotionEvent event) {double deltaX (event.getX(0) - event.getX(1));double deltaY (event.getY(0) - event.getY(1));double radius Math.atan2(deltaY, deltaX);return (float) Math.toDegrees(radius);}
} 记得在xml中设置我们MatrixImageView的scaleTypematrix com.aigestudio.customviewdemo.views.MatrixImageViewandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:scaleTypematrix / 转载于:https://www.cnblogs.com/krislight1105/p/5093708.html