https://blog.csdn.net/YX_BB/article/details/104561359
在上篇文章中,我们实现了圆形图片圆角图片,基本上已经满足了日常开发中的需要。那如果想要更多的图形效果该怎么办呢?与我们在现实中的绘图方式一致,用笔(Paint)在纸(Canvas)上按照一定的路径(Path),即可形成特定的图画。一个图形的Path是固定,所以,我们可以通过改变Paint和Canvas来实现想要的效果。如下

这里以心形图片为例,展示几种不同的实现方法,首先先实现心形图片的Path。

公式:t 表示点(x,y)在坐标系中的角度
x = 16 sin^3 t
y = 13 cos t - 5 cos 2t - 2 cos 3t - cos 4t

   /*** 心形曲线*/private Path getHeartPath() {int n = 100;// 计算缩放比例float scale = getWidth() / 17f / 2f;// 将360度平均分为 100份,每个弧度对应一个点float interval = (float) (2 * Math.PI / 100);// 定义初始弧度degreefloat degree = 0;Point[] points = new Point[n];for (int i = 0; i < n; i++) {// 根据心形曲线公式,计算出每个弧度对应的点的坐标// 当degree = 90度的时候,x取最大值16。当degree = 180度的时候,y取最小值 -17。// 即保证y * scale * 17 * 2 = height的时候,曲线的与控件相切,以此计算出scale的值float x = (float) ((16 * Math.pow(Math.sin(degree), 3)) * scale);float y = (float) ((13 * Math.cos(degree) - 5 * Math.cos(2 * degree) - 2 * Math.cos(3 * degree) - Math.cos(4 * degree)) * scale);points[i] = new Point(x + getWidth() / 2f, -y + getHeight() / 2f);degree = degree + interval;}// 连线Path path = new Path();path.moveTo(points[0].x, points[0].y);for (int i = 1; i < n; i++) {path.lineTo(points[i].x, points[i].y);}path.close();return path;}

思路1:使用Canvas的clipPath方法直接剪裁Canvas。

优点简单粗暴,可以适应于各种控件且不需要考虑绘制中的各种情形。缺点是剪裁之后,Canvas的绘制区域将被Path限制,任何的内容及后续扩展都局限在了这个Path之内。且clipPath方法不支持硬件加速,当应用开启了硬件加速时,设备在4.0.4与4.0.3这样的版本上使用图片剪裁功能的时候会crash。
重写onDraw()方法

@Overrideprotected void onDraw(Canvas canvas) {// 调用父类的绘制方法之前直接根据模式裁剪Canvascanvas.clipPath(getHeartPath());super.onDraw(canvas);}

控件继承自ImageView,注意要在super.onDraw()之前剪裁Canvas。否则Canvas已经将图片绘制到了View中,这个时候再剪裁Canvas毫无意义

思路2:使用Paint.setShader方法,只给指定区域上色。

Shader在三维软件中称之为着色器,就是用来给空白图形上色用的。
关于Shader的详细介绍可以参考
https://blog.csdn.net/harvic880925/article/details/52039081
首先获取ImageView上的Drawable,将Drawable转化成Bitmap,然后给Paint设置BitmapShader着色器,Bitmap在指定Path中的区域将被上色,Path之外的区域会被忽略。这种方式不会改变Canvas,也不会对后续的绘制工作造成影响
重写onDraw()方法

@Overrideprotected void onDraw(Canvas canvas) {if (mBitmap == null) {mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);Canvas drawCanvas = new Canvas(mBitmap);Drawable drawable = getDrawable();if (drawable != null) {drawable.draw(drawCanvas);}mPaint.setShader(new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));canvas.drawPath(getHeartPath(), mPaint);}}

注意:使用这种方式,不需要再使用super.onDraw(canvas)

思路3:使用PorterDuffXfermode混合图层

分别绘制图片图层,与路径图层。然后将两个图层混合。

 @Overrideprotected void onDraw(Canvas canvas) {// 图层混合绘制方式srcBmp = makeSrc();dstBmp = makeDst();int layerID = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);canvas.drawBitmap(srcBmp, 0, 0, mPaint);mPaint.setXfermode(xfermode);canvas.drawBitmap(dstBmp, 0, 0, mPaint);mPaint.setXfermode(null);canvas.restoreToCount(layerID);}/*** 源图层*/private Bitmap makeSrc() {Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);Drawable drawable = getDrawable();if (drawable != null) {drawable.draw(canvas);}return bitmap;}/*** 目标图层*/private Bitmap makeDst() {Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);canvas.drawPath(getHeartPath(), paint);return bitmap;}

注意:

int layerID = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);
canvas.restoreToCount(layerID);

如果不加如这段代码,在进行图层混合的时候,源图层将会被直接绘制在原始Canvas上。然后再绘制目标图层的时候,会与整个原始Canvas取交集,这时会将非交集区域的颜色全部清空,从而漏出Activity的底色。

在加入这段代码之后,Canvas.saveLayer(),会生成新的全透明的Bitmap,后续的绘制都是在这个新图层上完成的。混合之后再绘制在原始Canvas上。这时边缘区域显示的颜色为ImageView父布局的背景色。简单理解两者的区别如下
未使用saveLayer时,3 - 2 - 1 = 0
而使用saveLayer时,3 - (2 - 1) = 2
显然两者的计算结果不一样了,而后者才是我们真正需要的绘制流程。
关于saveLayer()的用法可以参考
https://blog.csdn.net/harvic880925/article/details/51317746

完整代码

public class ShapeImageView extends AppCompatImageView {private Paint mPaint;private Bitmap srcBmp;private Bitmap dstBmp;private Xfermode xfermode;public ShapeImageView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);initData();}private void initData() {mPaint = new Paint();xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);}@Overrideprotected void onDraw(Canvas canvas) {// 图层混合绘制方式srcBmp = makeSrc();dstBmp = makeDst();int layerID = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);canvas.drawBitmap(srcBmp, 0, 0, mPaint);mPaint.setXfermode(xfermode);canvas.drawBitmap(dstBmp, 0, 0, mPaint);mPaint.setXfermode(null);canvas.restoreToCount(layerID);// BitmapShader绘制方式
//        if (srcBmp == null) {//            srcBmp = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
//            Canvas drawCanvas = new Canvas(srcBmp);
//            Drawable drawable = getDrawable();
//            if (drawable != null) {//                drawable.draw(drawCanvas);
//            }
//
//            mPaint.setShader(new BitmapShader(srcBmp, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
//            canvas.drawPath(getHeartPath(), mPaint);
//        }// clipPath绘制方式
//        canvas.clipPath(getHeartPath());
//        super.onDraw(canvas);}/*** 源图层*/private Bitmap makeSrc() {Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);Drawable drawable = getDrawable();if (drawable != null) {drawable.draw(canvas);}return bitmap;}/*** 目标图层*/private Bitmap makeDst() {Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);canvas.drawPath(getHeartPath(), paint);return bitmap;}/*** 画心形曲线*/private Path getHeartPath() {int n = 100;// 计算缩放比例float scale = getWidth() / 17f / 2f;// 将360度平均分为 100份,每个弧度对应一个点float interval = (float) (2 * Math.PI / 100);// 定义初始弧度degreefloat degree = 0;Point[] points = new Point[n];for (int i = 0; i < n; i++) {// 根据心形曲线公式,计算出每个弧度对应的点的坐标// 当degree = 90度的时候,x取最大值16。当degree = 180度的时候,y取最小值 -17。// 即保证y * scale * 17 * 2 = height的时候,曲线的与控件相切,以此计算出scale的值float x = (float) ((16 * Math.pow(Math.sin(degree), 3)) * scale);float y = (float) ((13 * Math.cos(degree) - 5 * Math.cos(2 * degree) - 2 * Math.cos(3 * degree) - Math.cos(4 * degree)) * scale);points[i] = new Point(x + getWidth() / 2f, -y + getHeight() / 2f);degree = degree + interval;}// 连线Path path = new Path();path.moveTo(points[0].x, points[0].y);for (int i = 1; i < n; i++) {path.lineTo(points[i].x, points[i].y);}path.close();return path;}public static class Point {float x;float y;public Point(float x, float y) {this.x = x;this.y = y;}}
}

Android实现特定形状的图片相关推荐

  1. Android中绘制圆角矩形图片及任意形状图片

    转自http://blog.csdn.net/silangquan/article/details/8056583 圆角矩形图片在苹果的产品中很流行,相比于普通的矩形,很多人都喜欢圆角矩形的图片,因为 ...

  2. android百度地图覆盖物异步加载图片,Android 百度地图marker中图片不显示的解决方法(推荐)...

    目的: 根据提供的多个经纬度,显示所在地的marker样式,如下: 问题: 1.发现marker中在线加载的图片无法显示出来: 2.获取多个对象后,却只显示出了一个marker: 以下为官网实现方法: ...

  3. Android画不规则形状

    经常会在移动应用中看到类似下图的各种图片: 这样的图形在Android上要怎么实现呢?在Android系统中,目前主要有三种方式可以实现上图的形状,下面一一介绍. 一.PorterDuffXfermo ...

  4. 利用svg合成任意形状的图片

    什么是svg: 百度百科这样说: SVG可以算是目前最最火热的图像文件格式了,它的英文全称为Scalable Vector Graphics,意思为可缩放的矢量图形.它是基于XML(Extensibl ...

  5. 知识卡片 生成特定形状的词云

    在学会用Python简单生成词云后,我们来了解特定形状的词云如何生成. 美丽优雅的Cinderella公主是如何出现的呢? 上述词云效果使用了imageio库. imageio的含义为image in ...

  6. Android 自定义View 圆形圆角图片

    [Android 自定义View 圆形圆角图片] 基于Xfermode 实现 1.概述 在很久以前也写过一个利用Xfermode 实现圆形.圆角图片的(Android 完美实现图片圆角和圆形(对实现进 ...

  7. 基于Python的特定形状透明背景词云图绘制

    基于Python的特定形状透明背景词云图绘制 1.需求分析 2.前期准备 2.1文本文件准备 2.2特定形状图片准备 3.代码解析与实现 3.1模块库的导入 3.2停用词表 3.3基于TF-IDF提取 ...

  8. android picasso源码下载,Picasso:一个专为Android制作的强大的图片下载和缓存库

    Picasso:一个专为Android打造的强大的图片下载和缓存库 简介 在Android应用中,图片消费了大量的资源,却为应用提供了很好的视觉体验.幸运的是,Picasso为你的应用提供了非常容易的 ...

  9. android图片gif动画效果,android中类似于gif 实现图片的动画效果

    案例:实现gif动画效果,连续播放图片 由于是转载的,也就没必要多说,直接上代码 案例:在android中实现gif动态图片的效果: EarthAnimationActivity.java packa ...

最新文章

  1. @Mybatis传多个参数
  2. 安卓java增加属性_如何使用Java读取Android属性
  3. SAP CO模块权限控制
  4. 阿里云ecs实例中创建数据库
  5. asp.net core 拦击器制作的权限管理系统DEMO
  6. 大数据可视化大屏设计经验,教给你!
  7. 一文掌握 Docker 技术体系
  8. /etc/hosts/中HOSTNAME错误导致SETUP出错
  9. git lfs linux,Git LFS 操作指南
  10. java 1 20内奇数的乘积_计算所有奇数的乘积
  11. 三、任务切换之PendSV异常
  12. 计算机科学与实践,【计算机科学与技术学院|实践实况】(一)
  13. Mac 开启局域网smb文件共享(附全平台连接方法)
  14. 体验 服务器正在维护升级中 给大,阴阳师体验服9月30日维护 花合战更新
  15. Linux中Shell脚本编程
  16. 高薪程序员面试题精讲系列73之你熟悉servlet、session吗?get与post有哪些区别?
  17. R730调整风扇转速
  18. Hadoop化繁为简-从安装Linux到搭建集群环境
  19. qq音乐播放器2014最新版 v10.21.4270 官方版
  20. Java程序如何自动在线升级

热门文章

  1. 谷歌浏览器安装QQ旋风插件
  2. VC++调用libcurl开源库实现发送邮件的功能(附源码)
  3. 全球与中国口腔CBCT行业市场深度评估及未来发展分析报告
  4. window10 android studio连接不上夜神/mumu/蓝叠模拟器
  5. 你猜!大学里青年教师待遇真的很低吗?
  6. 京东 APP SIGN算法分析
  7. Java网络考试系统
  8. 代码+步骤GM(1,1)灰色预测模型-案例长江水质综合评价赛题-级比检测C的确定-matlab完整代码附送
  9. bC技术绑定10讲①“一元换购”的奥妙!
  10. 宁波大学计算机系有哪些专业,宁波大学有哪些专业