Android 正 N 边形圆角头像的实现

相应的布局文件实现

<?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”

/>


正 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)mDrawableRadius2,(int)mDrawableRadius2,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(a_n),Yn=sin(a_n)]。

圆心的实际坐标是外接矩形的中心:[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;

总结

这次面试问的还是还是有难度的,要求当场写代码并且运行,也是很考察面试者写代码
因为Android知识体系比较庞大和复杂的,涉及到计算机知识领域的方方面面。在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
Length) / 2f;

case CIRCLY:

ClipHelper.setCirclePath(path,width,height);

break;

case RECTAHGE:

ClipHelper.setRectangle(path,calculateBounds(),cornerRadius);

break;

总结

这次面试问的还是还是有难度的,要求当场写代码并且运行,也是很考察面试者写代码
因为Android知识体系比较庞大和复杂的,涉及到计算机知识领域的方方面面。在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-N1Efjg6f-1714688645817)]
里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 28
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值