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 边形
其实这个问题在上篇博客已经讲到,有两种实现方式。
-
第一种: 使用 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 方法,将其绘制出来
这里的思想主要来自该博客 如何用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);
}
这个问题我一开始的思路是根据圆形的半径,然后计算出各个点的坐标,接着使用 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();
总结
我最近从朋友那里收集到了2020-2021BAT 面试真题解析,内容很多也很系统,包含了很多内容:Android 基础、Java 基础、Android 源码相关分析、常见的一些原理性问题
等等,可以很好地帮助大家深刻理解Android相关知识点的原理以及面试相关知识。
这份资料把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节;还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
这里也分享给广大面试同胞们,希望每位程序猿们都能面试成功~
Android 基础知识点
Java 基础知识点
Android 源码相关分析
常见的一些原理性问题
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2020面试真题解析
参考docs.qq.com/doc/DSkNLaERkbnFoS0ZF
方式进行旋转的,调用 path.transform 方法
Matrix matrix = new Matrix();
总结
我最近从朋友那里收集到了2020-2021BAT 面试真题解析,内容很多也很系统,包含了很多内容:Android 基础、Java 基础、Android 源码相关分析、常见的一些原理性问题
等等,可以很好地帮助大家深刻理解Android相关知识点的原理以及面试相关知识。
这份资料把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节;还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
这里也分享给广大面试同胞们,希望每位程序猿们都能面试成功~
Android 基础知识点
[外链图片转存中…(img-zFq8x3YH-1724337493763)]
Java 基础知识点
[外链图片转存中…(img-rK7twGSX-1724337493763)]
Android 源码相关分析
[外链图片转存中…(img-1zJs5fmN-1724337493764)]
常见的一些原理性问题
[外链图片转存中…(img-baqJEB1z-1724337493765)]
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2020面试真题解析
[外链图片转存中…(img-nulpw79I-1724337493765)]
参考docs.qq.com/doc/DSkNLaERkbnFoS0ZF