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

卖一下广告,欢迎你们关注个人微信公众号,扫一扫下方二维码或搜索微信号 stormjun94(徐公码字),便可关注。 目前专一于 Android 开发,主要分享 Android开发相关知识和一些相关的优秀文章,包括我的总结,职场经验等。android

image

前言

在上一篇博客 Android 圆形头像的两种实现方式 中,咱们塔伦了实现圆形头像的两种实现方式。git

  • 第一种: 使用 Paint 的 Xfermode 实战
  • 第二种: 使用 BitmapShader 实现

今天,让咱们一块儿来看一下怎样实现正 N 变形圆角头像的实现。github

在讲解以前,让咱们先来看一下怎样使用咱们的控件

老规矩,在讲解怎样实现之前,咱们先一块儿来看一下怎样使用咱们的自定义控件。canvas

自定义属性说明

<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" />

image

指定圆角矩形

<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" />

image

指定正 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" />

image

若是须要其旋转相应的角度,咱们只需指定 app:miv_rotate_angle="180" 便可,这里以 180 度为列子讲解说明app

image

若是须要正六边形,只须要更改成 app:miv_sides="6"ide

image

效果图布局

image

相应的布局文件实现post

<!--?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 变形主要有几个难点.net

  • 怎样让咱们的头像变成正 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画一个正多边形

数学原理分析

首先,咱们先来看一张图片

image

从图中能够看一看到,咱们若想绘制出一个正 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 &lt; = 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 &lt; 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 &lt;= 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() &gt; 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,谢谢。

MultiImageView

image

相关文章
相关标签/搜索