前言

在Android开发中, 我们经常会遇到一些场景, 需要以一些特殊的形状显示图片, 比如圆角矩形、圆形等等。关于如何绘制这类形状, 网上已经有很多的方案,比如自定义控件重写onDraw方法, 通过canvas的各种draw方法进行绘制等。那么, 更复杂的图形呢?比如,五角星?比如组合图形?又或者是各种奇奇怪怪的不规则图形呢?有同学会说, 如果已知不规则图形的具体形状, 那我们就可以通过连接顶点的方式, 找出path, 然后通过drawPath方法绘制出来啊。嗯。。。很有道理, 但是先不说有些图像,可能顶点巨多, 或者弯弯曲曲很难找出具体的顶点, 难道我们要为每一个特殊的形状, 单独写一个独立的控件, 或者一套独立的代码吗?
可以肯定是可以,但是我觉得, 最好还是不要这么做。。于是我有了一个想法, 用一张图片, 告诉控件,我想要什么样的形状, 然后控件自动按照这个形状, 帮我把图片显示出来。于是有了这个项目--android-anyshape。

展示

   
左边是使用了普通ImageView的展示效果, 右边是使用了项目中AnyshapeImageView的效果。想使用AnyshapeImageView达到右边的样式, 仅需提供三张遮罩图片,通过”anyshapeMask”参数提供给控件即可(下文会说明)。
三张“遮罩”图片如下:
      
与普通的遮罩图片不同, 这里要求图片的背景完全透明, 即alpha通道的值为0, 而需要显示的图形,对具体的颜色没有任何要求,不透明即可

使用

控件的使用很简单, 由于继承ImageView, 所以使用方法类似于ImageView,但多了一个重要的自定义参数:anyshapeMask

<cn.lankton.anyshape.AnyshapeImageView
   android:layout_width="150dp"android:layout_height="150dp"android:layout_marginTop="20dp"android:src="@drawable/kumamon"app:anyshapeMask="@drawable/singlestar"/>

在布局文件中加入这段xml, 展示的就是上面图中那头五角星形状的熊本熊~
实现这个功能的思路其实很简单,通过对一张“遮罩”图片各像素透明度的扫描,获得一个Path对象, 该Path对象包含了所有不透明像素的集合。然后就很简单了, 通过Canvas对象的drawPath方法,将我们要显示的图片刷上去即可。

实现

从Bitmap中提取Path

这是这个项目中最重要的部分。代码如下:
PathInfoManager.getPathFromBitmap:

public Path getPathFromBitmap(Bitmap mask) {Path path = new Path();int bWidth = mask.getWidth();int bHeight = mask.getHeight();int[] origin = new int[bWidth];int lastA;for (int i = 0; i < bHeight; i++) {mask.getPixels(origin, 0, bWidth, 0, i, bWidth, 1);lastA = 0;for (int j = 0; j < bWidth; j++) {int a = Color.alpha(origin[j]);if (a != 0 && lastA == 0) {path.moveTo(j, i);} else if (a == 0 && lastA !=0 ) {path.lineTo(j - 1, i);} else if (a != 0 && j == bWidth - 1) {path.lineTo(j, i);}lastA = a;}}return path;
}

我设计的方案很简单,逐行扫描Bitmap中的像素,实现方法是用getPixels方法获得每行的像素数组,然后遍历分析。步骤如下:
1. 遇到一个不透明像素,进行判断, 如果它的上一个像素不透明, 或者它本身就是行首, 那我们就把它看作一段不透明区域的开头,通过moveTo方法将Path移动到此点;
2. 遇到一个透明像素,进行判断,如果它的上一个像素透明,那我们就把它的上一个像素看作一段不透明区域的结尾, 通过lineTo的方式, 将它与之前的开头像素连接。
3. 重复1、2步, 直到扫描完全行。需要注意的是, 如果行尾是不透明像素, 那就直接连上。防止最后一段不透明区域只有起点没有终点。
这样, 每一行的连接结果,就组成了整张图片的扫描结果~

通过Path,显示图像

先看一下AnyshapeImageView的初始化方法:

public AnyshapeImageView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);this.context = context;TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AnyShapeImageView, defStyleAttr, 0);int n = a.getIndexCount();for (int i = 0; i < n; i++){final int attr = a.getIndex(i);if (attr == R.styleable.AnyShapeImageView_anyshapeMask) {maskResId = a.getResourceId(attr, 0);if (0 == maskResId) {//did not set maskcontinue;}} else if (attr == R.styleable.AnyShapeImageView_anyshapeBackColor) {backColor = a.getColor(attr, Color.TRANSPARENT);}}a.recycle();
}

其实就是调用通过anyshapeMask参数, 获得“遮罩”图片的资源ID以及背景色。 真正通过资源ID解析获取遮罩的过程放在了onMeaaure中。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int mWidth = getMeasuredWidth();int mHeight = getMeasuredHeight();if (mWidth != 0 && mHeight != 0) {if (maskResId <= 0) {return;}PathInfo pi = PathManager.getInstance().getPathInfo(maskResId);if (null != pi) {originMaskPath = pi.path;originMaskWidth = pi.width;originMaskHeight = pi.height;} else {BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeResource(context.getResources(), maskResId, options);int widthRatio = (int)(options.outWidth * 1f / mWidth);int heightRatio = (int)(options.outHeight * 1f / mHeight);if (widthRatio > heightRatio) {options.inSampleSize = widthRatio;} else {options.inSampleSize = heightRatio;}if (options.inSampleSize == 0) {options.inSampleSize = 1;}options.inJustDecodeBounds = false;Bitmap maskBitmap = BitmapFactory.decodeResource(context.getResources(), maskResId, options);originMaskPath = PathManager.getInstance().getPathFromBitmap(maskBitmap);originMaskWidth = maskBitmap.getWidth();originMaskHeight = maskBitmap.getHeight();pi = new PathInfo();pi.height = originMaskHeight;pi.width = originMaskWidth;pi.path = originMaskPath;PathManager.getInstance().addPathInfo(maskResId, pi);maskBitmap.recycle();}}
}

PathInfo:

public class PathInfo {public Path path;public int width;public int height;
}

然而我们看到,用户进行生成Bitmap-获取Path这一系列耗时、耗内存操作之前,先会判断缓存里是否已经有与该资源ID匹配的PathInfo, 如果有, 则不用进行这部分操作。如果没有,根据传入的资源ID,生成PathInfo对象,并存入缓存。同时,根据控件的宽高,对decode做了限制,预防了OOM 和 加载资源过大的问题。
关于这块缓存,下面会说明。

再看onSizeChanged方法:

protected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);vHeight = getHeight();vWidth = getWidth();if (originMaskPath != null) {//scale the size of the path to fit the one of this ViewMatrix matrix = new Matrix();matrix.setScale(vWidth * 1f / originMaskWidth, vHeight * 1f / originMaskHeight);originMaskPath.transform(matrix, realMaskPath);}
}

这里的代码, 主要目的是对Path对象进行缩放, 已匹配控件的实际大小。可以看到, 如果不希望展示的形状被拉伸或者变形, 那么AnyshapeImageView的宽高比, 最好和“遮罩”图片的宽高比保持一致。

接下来就是在onDraw里绘制形状并刷上图片了:

@Override
protected void onDraw(Canvas canvas) {if (null == originMaskPath) {// if the mask is null, the view will work as a normal ImageViewsuper.onDraw(canvas);return;}if (vWidth == 0 || vHeight == 0) {return;}paint.reset();paint.setStyle(Paint.Style.STROKE);//get the drawable to show. if not set the src, will use  backColorDrawable showDrawable = getDrawable();if (null != showDrawable) {Bitmap showBitmap = ((BitmapDrawable) showDrawable).getBitmap();Shader shader = new BitmapShader(showBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);Matrix shaderMatrix = new Matrix();float scaleX = vWidth * 1.0f / showBitmap.getWidth();float scaleY = vHeight * 1.0f / showBitmap.getHeight();shaderMatrix.setScale(scaleX, scaleY);shader.setLocalMatrix(shaderMatrix);paint.setShader(shader);} else {//no src , use the backColor to fill the pathpaint.setColor(backColor);}canvas.drawPath(realMaskPath, paint);}

缓存

看了上面的博文, 各位一定清楚了,作为参数传入的资源ID,实际上只是为了获取一个Path对象。那么我们可以建立一个Integer-Path的映射关系, 用来缓存已经读取出来的Path。后面需要Path, 只需要通过资源ID去缓存里寻找即可,毕竟读取Path是一个费时间又费资源的操作。
这样看来,我们已经对AnyshapeImageView的使用进行了优化, 毕竟同一个形状的展示,我们只要执行一次从图片中解析Path对象的操作即可。

总结

这个项目,是我花了将近一周时间断断续续完成的。代码不多, 也不复杂,希望能够帮到大家, 或者为大家提供一些思路。
再贴一下项目的地址, 包括demo在内:
https://github.com/lankton/android-anyshape
如果你觉得这个项目,或者这篇博文对你起到了一些帮助,欢迎star支持一下~

更新

2016-05-12

优化了AnyshapeImageView解析遮罩的过程,PathManager中的createPaths(预先解析Path)变得繁琐且不必要,故删除。 简化后的使用可见项目README。
本次更新后对博文上面代码、讲解内容也有改动。

发布到JCenter-20160519

为方便使用,已将library发布到JCenter,开发者可以使用gradle或者maven进行依赖的配置。

gradle

compile 'cn.lankton:anyshape:1.0.0'

maven

<dependency><groupId>cn.lankton</groupId><artifactId>anyshape</artifactId><version>1.0.0</version><type>pom</type>
</dependency>

【Android】0行代码实现任意形状图片展示--android-anyshape相关推荐

  1. (已加马赛克)10 行代码判定色*情*图片——Python 也可以系列之二

    10 行代码判定色*情*图片--Python 也可以系列之二 作者:赖勇浩(http://blog.csdn.net/lanphaday) 致编辑:我已经给图片打上马赛克了,别再删除了啊,我这是纯技术 ...

  2. Android第一行代码学习思考笔记(碎片、广播、持久化技术和Android数据库)

    Android第一行代码学习思考笔记(碎片.广播.持久化技术和Android数据库 第四章 手机平板要兼顾--探究碎片 4.1碎片是什么(Fragment) 4.2碎片的使用方式 4.2.1碎片的简单 ...

  3. (已加马赛克)10 行代码判定色 情 图片——Python 也可以系列之二

    10 行代码判定色*情*图片--Python 也可以系列之二 作者:赖勇浩(http://blog.csdn.net/lanphaday) 致编辑:我已经给图片打上马赛克了,别再删除了啊,我这是纯技术 ...

  4. Android build.gradle文件详解(转述自《Android第一行代码》第二版)

    Android build.gradle文件详解 1. 最外层目录下的build.gradle文件 1.1 repostories 1.2 dependencies 2. app目录下的build.g ...

  5. Android第一行代码-Activity

    文章目录 Android第一行代码 Activity 1.Activity基本用法 2.创建和加载布局 加载布局(在Activity中加载布局) 在AndroidManifest文件中注册(所有的ac ...

  6. 百行代码构建神经网络黑白图片自动上色系统

    使用神经网络对图片进行风格化渲染是计算机视觉领域的热门应用之一.本文将向你介绍一种简单而有效的黑白图片上色方法,仅需 100 行代码,你也可以搭建自己的神经网络,几秒钟内让计算机自动完成手动工作需要几 ...

  7. Android第一行代码第二版简要总结

    进入安卓的第一本书(简要概括) 第一章 了解大体Android 1.Android系统架构 Linux内核层:为Android设备的各种硬件提供了底层的驱动. 系统运行库层:通过c/c++库来提供主要 ...

  8. Android几行代码实现监听微信聊天

    转载:http://www.cnblogs.com/cxk1995/p/6366273.html 2017.2.7更新: *现在适配微信版本更加容易了,只需要替换一个Recourse-ID即可 *可以 ...

  9. Android第一行代码(第一行代码、活动)

    一.第一行代码 1.了解全貌 1.1Android的系统架构 Android的系统架构:Linux内核层.系统运行库层.应用框架层和应用层 1.Linux内核层 Android系统是基于Linux内核 ...

最新文章

  1. Python五种实用的小技巧
  2. php分享三十三:用php中的register_shutdown_function和fastcgi_finish_request
  3. [云炬学英语]每日一句2020.8.28
  4. [ZJOI2010]网络扩容[网络流24题]
  5. leetcode - 688. “马”在棋盘上的概率
  6. IT NEWS WebSite
  7. 下载并遍历 names.txt 文件,输出长度最长的回文人名。
  8. 【渝粤教育】电大中专药物分析技术基础 (2)_1作业 题库
  9. 基于百度通用翻译API的一个翻译小工具
  10. Unity3D内存释放
  11. 那本失去的《牛津高阶英汉双解词典》第6版 P2666-2665
  12. 最简单DIY基于Android系统的万能蓝牙设备智能遥控器
  13. 中国首个“微信身份证”是怎么做到人脸识别误判率百万分之一的?
  14. android远程协助
  15. 全国第三届SLAM技术会议记录
  16. 深度学习_07_2_随机梯度下降_激活函数及梯度损失函数及梯度
  17. 上班族保护颈椎有诀窍
  18. ae软件安装计算机丢失,安装adobe ae等软件提示找不到msvcp110.dll、msvcp
  19. Jenkins常用插件之Publish Over SSH
  20. 夫兵形象水,水之行避高趋下,兵之形避实击虚;水因地制流,兵因故制胜。故兵无常势水无常形。能因敌变化取胜者谓之神。

热门文章

  1. 京东C++开发工程师 2020校招卷总结
  2. 你认为阻碍自动化测试的最常见的问题是什么?
  3. 【STM32F103】PB3和PB4不能按照设置输出相应的电平问题
  4. 右手坐标系和左手坐标系(转)很详细,有图示
  5. 机器学习——监督学习、非监督学习、半监督式学习、增强学习
  6. 《LeetCode刷题》804. 唯一摩尔斯密码词(java篇)
  7. 神经网络的人脸识别方法,神经网络图像识别技术
  8. pmbook 知识领域 第六版_PMBOK第六版10大知识领域ITTO思维导图-干货
  9. windows7 home basic 安装DB2 Express-C
  10. 拼团小程序源码_拼团小程序哪个模式好用?