Android 正 N 边形圆角头像的实现
卖一下广告,欢迎大家关注我的微信公众号,扫一扫下方二维码或搜索微信号 stormjun94(徐公码字),即可关注。 目前专注于 Android 开发,主要分享 Android开发相关知识和一些相关的优秀文章,包括个人总结,职场经验等。
前言
在上一篇博客 Android 圆形头像的两种实现方式 中,我们塔伦了实现圆形头像的两种实现方式。
- 第一种: 使用 Paint 的 Xfermode 实战
- 第二种: 使用 BitmapShader 实现
今天,让我们一起来看一下怎样实现正 N 变形圆角头像的实现。
在讲解之前,让我们先来看一下怎样使用我们的控件
老规矩,在讲解怎样实现以前,我们先一起来看一下怎样使用我们的自定义控件。
自定义属性说明
<attr name="type">
<enum name="circle" value="0" />
<enum name="round" value="1" />
<enum name="polygon" value="2"/>
</attr>
<declare-styleable name="MultiImageView">
<attr name="type"/>
<attr name="miv_border_width" format="dimension" />
<attr name="miv_border_color" format="color" />
<attr name="miv_border_overlay" format="boolean" />
<attr name="miv_fill_color" format="color" />
<attr name="miv_corner_radius" format="dimension"/>
<attr name="miv_sides" format="integer"/>
<attr name="miv_rotate_angle" format="float"/>
</declare-styleable>
参数 | 说明 |
---|---|
type | 相应的值有 circle,round,polygon |
miv_border_width | 表示边界 Path 的宽度 (默认值是 0 ) |
miv_border_color | 表示边界 Path 的 Color |
miv_border_overlay | 表示边界 Path 是否要覆盖在图片上面 |
miv_fill_color | 表示填充圆的颜色,默认是 Translate,即不可见 |
miv_corner_radius | 只有当 type round 或者 polygon 的时候才生效,表示边界 Path 圆角半径的大小, |
miv_sides | 正 N 边形的变数,只有 type 为 polygon 的时候,该属性才生效 |
miv_rotate_angle | 旋转的角度,只有 type 为 polygon 的时候,该属性才生效 |
指定圆形头像
<com.xj.shapeview.MultiImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/tanyan"
app:type="circle"
/>
指定圆角矩形
<com.xj.shapeview.MultiImageView
android:layout_marginLeft="15dp"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/tanyan"
app:type="round"
app:miv_corner_radius="15dp"/>
指定正 N 边形
正五边形
<com.xj.shapeview.MultiImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/tanyan"
app:type="polygon"
app:miv_sides="5"
app:miv_corner_radius="25dp"/>
如果需要其旋转相应的角度,我们只需指定 app:miv_rotate_angle="180" 即可,这里以 180 度为列子讲解说明
如果需要正六边形,只需要更改为 app:miv_sides="6"
效果图
相应的布局文件实现
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<GridLayout
android:columnCount="3"
android:rowCount="3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<com.xj.shapeview.MultiImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/tanyan"
app:type="circle"
app:miv_sides="6"
app:miv_corner_radius="15dp"/>
<com.xj.shapeview.MultiImageView
android:layout_marginLeft="15dp"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/tanyan"
app:miv_sides="5"
app:type="round"
app:miv_corner_radius="15dp"/>
<com.xj.shapeview.MultiImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/tanyan"
app:type="polygon"
app:miv_sides="5"
app:miv_corner_radius="25dp"/>
<com.xj.shapeview.MultiImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/tanyan"
app:type="polygon"
app:miv_sides="5"
app:miv_corner_radius="25dp"
app:miv_rotate_angle="180"/>
<com.xj.shapeview.MultiImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/tanyan"
app:miv_sides="7"
app:type="polygon"
app:miv_corner_radius="0dp"
app:miv_rotate_angle="0"/>
<com.xj.shapeview.MultiImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/tanyan"
app:miv_sides="6"
app:type="polygon"
app:miv_corner_radius="0dp"
app:miv_border_overlay="true"
app:miv_fill_color="@color/colorAccent"/>
<com.xj.shapeview.MultiImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/tanyan"
app:miv_sides="6"
app:type="polygon"
app:miv_corner_radius="0dp"
app:miv_rotate_angle="0"
app:miv_border_overlay="true"
app:miv_border_width="1dp"
app:miv_border_color="@android:color/darker_gray"
/>
<com.xj.shapeview.MultiImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/tanyan"
app:miv_sides="6"
app:type="polygon"
app:miv_corner_radius="0dp"
app:miv_rotate_angle="0"
app:miv_border_overlay="false"
app:miv_border_width="1dp"
app:miv_border_color="@android:color/black"
/>
<com.xj.shapeview.MultiImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/tanyan"
app:miv_sides="7"
app:type="polygon"
app:miv_corner_radius="10dp"
app:miv_rotate_angle="0"
/>
</GridLayout>
</ScrollView>
正 N 边形圆角头像的实现原理分析
要实现正 N 变形主要有几个难点
- 怎样让我们的头像变成正 N 边形
- 怎样绘制正 N 边形
- 怎样绘制带圆角的正 N 边形
怎样让我们的头像变成正 N 边形?
其实这个问题在上篇博客已经讲到,有两种实现方式。
- 第一种: 使用 Paint 的 Xfermode 实战
- 第二种: 使用 BitmapShader 实现
今天,这边博客主要以 BitmapShader 为例子实现。
核心代码实现
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mBitmapPaint.setAntiAlias(true);
mBitmapPaint.setShader(mBitmapShader);
@Override
protected void onDraw(Canvas canvas) {
-----
Path path = getPath(canvas,mType,(int)mDrawableRadius*2,(int)mDrawableRadius*2,mDrawableRadius,mSides,mCornerRadius);
canvas.drawPath(path,mBitmapPaint);
}
核心思路分析:
- 拿到 Bitmap,并使用 BitmapShader 进行包装
- 将 mBitmapShader 设置给画笔 Paint
- 第三步,在 onDraw 方法,将其绘制出来
怎样绘制正 N 边形
这里的思想主要来自该博客 如何用Canvas画一个正多边形
数学原理分析
首先,我们先来看一张图片
从图中可以看一看到,我们若想绘制出一个正 N 边形,那么我们只需要计算出各个点的坐标,然后使用 Path 连接起来即可。
那我们要怎样计算出各个点的坐标呢
- 从图中不难得出,圆心角 a 的度数为 360/n,弧度计算为 2π/n
- 如果把圆心的坐标为(0,0),那么顶点P1的坐标为[X1=cos(a),Y1=sin(a)]。
- 以此类推,顶点Pn坐标为[Xn=cos(an),Yn=sin(an)]。
圆心的实际坐标是外接矩形的中心:[Ox=(rect.right+rect.left)/2 , Oy=(rect.top+rect.bottom)/2]。
所以Pn的实际坐标是[Xn+Ox,Yn+Oy]。
最后我们把把 P0-P1…Pn 连起来,就是我们要的结果了。
核心伪代码实现
float a = 2π / n ; // 角度
Path path = new Path();
for( int i = 0; i < = n; i++ ){
float x = R * cos(a * i);
float y = R * sin(a * i);
if (i = 0){
path.moveTo(x,y); // 移动到第一个顶点
}else{
path.lineTo(x,y); //
}
}
drawPath(path);
实际代码实现
在上面的例子中,我们假设我们的圆形坐标是 (0,0), 但实际上并不是,实际上在 Android 中我们的圆心坐标是 (width/2,height/2)。因此,我们在计算坐标的时候需要加上
圆心坐标
float mX = (rect.right + rect.left) / 2;
float my = (rect.top + rect.bottom) / 2;
// PN点的 x,y 坐标
float nextX = mX + Double.valueOf(r * Math.cos(alpha)).floatValue();
float nextY = my + Double.valueOf(r * Math.sin(alpha)).floatValue();
当然我们这里以可以用 canvas 的 translate 方法来移动。
public static void drawPolygon (RectF rect, Canvas canvas, Paint paintByLevel, int number) {
if(number < 3) {
return;
}
float r = (rect.right - rect.left) / 2;
float mX = (rect.right + rect.left) / 2;
float my = (rect.top + rect.bottom) / 2;
Path path = new Path();
for (int i = 0; i <= number; i++) {
// - 0.5 : Turn 90 ° counterclockwise
float alpha = Double.valueOf(((2f / number) * i - 0.5) * Math.PI).floatValue();
float nextX = mX + Double.valueOf(r * Math.cos(alpha)).floatValue();
float nextY = my + Double.valueOf(r * Math.sin(alpha)).floatValue();
if (i == 0) {
path.moveTo(nextX, nextY);
} else {
path.lineTo(nextX, nextY);
}
}
canvas.drawPath(path, paintByLevel);
}
怎样绘制带有圆角的正 N 边形
这个问题我一开始的思路是根据圆形的半径,然后计算出各个点的坐标,接着使用 path 中的 addArc() 方法来绘制。但是在计算各个点的坐标的时候,遇到很多难度,最后无法得出。
后面查阅了 Android 官方的文档,发现了有这样一个方法
PathEffect setPathEffect (PathEffect effect)
从字面意思很容易理解,就是设置 PathEffect,可以对 Path 产生相应的影响。
那这个 PathEffect 又是什么东东呢?
public class PathEffect extends Object
Known Direct Subclasses
ComposePathEffect,CornerPathEffect,DashPathEffect,DiscretePathEffect,PathDashPathEffect,SumPathEffect
从官方文档可以了解到是继承于 Object 的,实现的子类有 ComposePathEffect, CornerPathEffect, DashPathEffect 等。
看到这里的时候你有没有突然有一种醍醐灌顶的感觉? 这个 CornerPathEffect 是不是就可以实现呢?没错,确实可以实现,而且贼简单。
核心代码只有这几句,就可以让我们绘制出的正 N 边形具有圆角
CornerPathEffect cornerPathEffect = new CornerPathEffect(mCornerRadius);
mBitmapPaint.setPathEffect(cornerPathEffect);
代码实现细节注意事项
当空间的宽度和高度不一致的时候,半径怎样取值?
这里我们选择宽度和高度值较小的一个,然后除以2
mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);
当图片较大的时候,会不会发生 OOM
当图片较大的时候,我们会对其进行相应的缩放,采用的是矩阵的方法
private void updateShaderMatrix() {
float scale;
float dx = 0;
float dy = 0;
mShaderMatrix.set(null);
if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
scale = mDrawableRect.height() / (float) mBitmapHeight;
dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
} else {
scale = mDrawableRect.width() / (float) mBitmapWidth;
dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
}
mShaderMatrix.setScale(scale, scale);
mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
mBitmapShader.setLocalMatrix(mShaderMatrix);
}
自定义控件怎样支持 padding 属性
在绘制图片的时候,我们对其进行相应的处理,确保我们的坐标是正确的。
float left = getPaddingLeft() + (availableWidth - sideLength) / 2f;
float top = getPaddingTop() + (availableHeight - sideLength) / 2f;
case CIRCLY:
ClipHelper.setCirclePath(path,width,height);
break;
case RECTAHGE:
ClipHelper.setRectangle(path,calculateBounds(),cornerRadius);
break;
case POLYGON:
ClipHelper.setPolygon(path,calculateBounds(),sides,mRotateAngles);
break;
private RectF calculateBounds() {
int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom();
int sideLength = Math.min(availableWidth, availableHeight);
float left = getPaddingLeft() + (availableWidth - sideLength) / 2f;
float top = getPaddingTop() + (availableHeight - sideLength) / 2f;
return new RectF(left, top, left + sideLength, top + sideLength);
}
正 N 边形的角度旋转是怎样实现的。
其实,这里,我们采用的是矩阵的方式进行旋转的,调用 path.transform 方法
Matrix matrix = new Matrix();
matrix.postRotate(rotateAngle,mX,my);
path.transform(matrix);
题外话
在开发的时候,一刚开始说要实现圆角六边形的时候,查阅了相关的资料,知道有两种方法
- 第一种方法,让 UI 设计师直接给图, 使用 Paint 的 Xfermode 实现
- 第二种方法:直接绘制 Path;
那时候项目比较赶,采用的是第一种方式实现。不过作为一名程序猿,感觉采用第一种方法实现,总感觉有点 low。后面晚上下班的时候,查阅了相关的资料,最终终于实现了上述的效果。
这种正 N 边形圆角头像的效果,说难也不难,说容易也不容易。因为里面综合了很多知识点,需要一步步去处理。(比如怎样绘制正 N 边形,怎样支持圆角,怎样处理 Padding 等等)。
最后,给大家推荐 github 上面的一个开源库。ShapeOfView,里面实现了很多常见的图片(心形,五角星。六角形等)
参考博客:如何用Canvas画一个正多边形
Android 圆形头像的两种实现方式
Android 正 N 边形圆角头像的实现
如果,你觉得效果还不错,请到我的 github 上面 star,谢谢。
Android 正 N 边形圆角头像的实现的更多相关文章
- Android Demo---如何敲出圆角的Button+圆角头像
经常玩儿App的小伙伴都知道,APP上面有很多按钮都是圆角的,圆形给人感觉饱满,富有张力,不知道设计圆角按钮的小伙伴是不是和小编有着相同的想法`(*∩_∩*)′,听小编公司开发IOS的小伙伴说,他们里 ...
- Unity用户自定义圆角头像
前天朋友遇到一个这样的需求,而且比较棘手让我帮忙解决.需求就是棋牌类的游戏,玩家的个人资料中包括自己的头像而且可以浏览相册中的图片或者使用相机拍照设置.关于这个问题我也查阅一些资料,由于涉及安卓部分知 ...
- Android特效专辑(五)——自定义圆形头像和仿MIUI卸载动画—粒子爆炸
Android特效专辑(五)--自定义圆形头像和仿MIUI卸载动画-粒子爆炸 好的,各位亲爱的朋友,今天讲的特效还是比较炫的,首先,我们会讲一个自定义圆形的imageView,接着,我们会来实现粒子爆 ...
- TZOJ 2392 Bounding box(正n边形三点求最小矩形覆盖面积)
描述 The Archeologists of the Current Millenium (ACM) now and then discover ancient artifacts located ...
- flutter 实现圆角头像的2种方法
圆角头像在开发中应用太普遍了,我总结了2种实现方法,分享给大家 方法一: 使用Container组件的decoration可以实现 Container( width: 40, height: 40, ...
- hdu 5533 正n边形判断 精度处理
Dancing Stars on Me Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Ot ...
- android 自定义控件——(一)圆角按钮
----------------------------------矩形或圆角类型(源代码下有属性解释)------------------------------------------------ ...
- Flutter 圆形/圆角头像图片
图片显示 1.本地图片 Image.asset加载项目资源包的图片 //先将图片拷贝到项目 images 目录中,然后在 pubspec.yaml文件配置文件相对路径到 assets Image.as ...
- Android 中shape的使用(圆角矩形)
一.在res/drawable文件夹下创建一个名为gradient_box的xml文件: <?xml version="1.0" encoding="utf-8&q ...
随机推荐
- activemq的下载与安装
一.介绍 Apache ActiveMQ™是最流行的开源,多协议,基于Java的消息服务器.它支持行业标准协议,因此用户可以通过广泛的语言和平台获得客户选择的好处.可以使用C,C ++,Python, ...
- mac下面有epoll?
没有的,但是mac下面有kqueue,跟epoll原理是差不多的. 这个是没办法的,如果实在需要,就用Ubuntu吧,这个也可以无缝迁移. 更多资源,更多文章由小白技术社提供(是我啦)
- Flutter学习笔记(25)--ListView实现上拉刷新下拉加载
如需转载,请注明出处:Flutter学习笔记(25)--ListView实现上拉刷新下拉加载 前面我们有写过ListView的使用:Flutter学习笔记(12)--列表组件,当列表的数据非常多时,需 ...
- C#数据结构_栈和队列
栈:先进后出,只能在栈顶进行操作. 栈的操作主要包括在栈顶插入元素和删除元素.取栈顶元素和判断栈是否为空等. 栈的接口定义: public interface IStack<T> { in ...
- HTTP网页异常错误代码详解
在调试TomCat,等web服务器的时候我们有时候各种错误代码铺面而来,让人头疼不已,那么这些代码究竟都代表什么呢?知道这些代码会会对我们的调试帮助很大 让我们来看一下这些代码究竟什么意思 400 无 ...
- zookeeper学习之原理
一.zookeeper 是什么 Zookeeper是一个分布式协调服务,可用于服务发现,分布式锁,分布式领导选举,配置管理等.这一切的基础,都是Zookeeper提供了一个类似于Linux文件系统的树 ...
- .NET Core 学习资料精选:进阶
.NET 3.0 这个月就要正式发布了,对于前一篇博文<.NET Core 学习资料精选:入门>大家学的可还开心?这是本系列的第二篇文章:进阶篇,喜欢的园友速度学起来啊. 对于还在使用传统 ...
- 新手学习FFmpeg - 调用API完成录屏并进行H.264编码
Screen Record H.264 目前在网络传输视频/音频流都一般会采用H.264进行编码,所以尝试调用FFMPEG API完成Mac录屏功能,同时编码为H.264格式. 在上一篇文章中,通过调 ...
- ios5与ios7数字输入样式一致
<input name="activeCode" id="activeCode" ontouchstart="this.type='numbe ...
- C#开发BIMFACE系列18 服务端API之获取模型数据3:获取构件属性
系列目录 [已更新最新开发文章,点击查看详细] 本篇主要介绍如何获取单文件/模型下单个构建的属性信息. 请求地址:GET https://api.bimface.com/data/v2/fil ...