如题,用自定义View来绘制一个Chrome浏览器的Logo,就是这个
其实这个就是最终完成的作品,看上去还不错吧。这篇文章就手把手教你怎么用自定义View实现Chrome浏览器的Logo的绘制,其实还是很简单的,不过通过自己实现一遍,可以加深自己对自定义View流程的理解,以及对Path,Paint,Canvas的理解。
先在网上下载一张谷歌浏览器的图标备用,然后用PS或者其他的颜色吸管工具,把Logo的颜色吸取到。为了简单,我们就把Logo按照四种颜色来处理。这里直接给大家列出色值,分别是红(0xFFCC5A4C),黄(0xFFF8CC5F),绿(0xFF4C9E66),蓝(0xFF5A8CEE)。
在绘制Logo之前,我们观察一下整个Logo就会发现,整个Logo其实是由一个蓝色小圆和一个三个相同的红黄绿三部分组成的圆环构成的。其实可以理解为三个圆,我大概测量了一下,蓝色小圆的半径大概是最大圆的4/10,中间的小圆是最大圆的半径的二分之一,我们姑且按照这样来计算。
外围圆环的三个部分其实形状都是一样的,只是颜色不一样而已。因此我们只需要绘制出其中一个,其他两个都可以通过旋转Canvas来绘制得到。下面我们就来分析一下怎么来绘制。
以上面的红色块为例,这一块可以看作是由弧AB,线段BC,弧CD,线段DA组成的封闭图形。显然这个图形是不规则的,而且不太容易通过组合来绘制得到,这个时候我们就需要强大的Path出场了,Path对这些不规则形状的绘制就是手到擒来。
明确了绘制方法,我们就需要精确计算A、B、C、D的坐标。首先是三个相同部分均分的圆,所以弧AB和弧CD毫无疑问应该是120°。假设O点坐标为 ( O x , O y ) (O_x, O_y) (Ox,Oy),OC的长度为R,OA=OB=R/2,所以我们可以得出结论∠BOC=60°,所以A点坐标就是 A x = O x − R ∗ 3 / 4 A_x = O_x-R*\sqrt{3}/4 Ax=Ox−R∗3/4 , A y = O y + R / 4 A_y = O_y+R/4 Ay=Oy+R/4。
B坐标为, B x = O x B_x = O_x Bx=Ox , B y = O y − R / 2 B_y = O_y-R/2 By=Oy−R/2;
C坐标为, C x = O x + R ∗ 3 / 2 C_x = O_x+R*\sqrt{3}/2 Cx=Ox+R∗3/2 , C y = O y − R / 2 C_y = O_y-R/2 Cy=Oy−R/2;
D坐标为, D x = O x − R ∗ 3 / 2 D_x = O_x-R*\sqrt{3}/2 Dx=Ox−R∗3/2 , D y = O y − R / 2 D_y = O_y-R/2 Dy=Oy−R/2;
一切准备就绪,下面开始自定义View的部分。
首先新建一个ChromeLogoView,继承自andorid.view.View,然后初始化三个构造函数。
public class ChromeLogoView extends View {
public ChromeLogoView(Context context) {
this(context, null);
}
public ChromeLogoView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ChromeLogoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
}
}
然后定义几个常量备用,分别是各个部分的颜色值和圆的比例,而且我们应该还需要一个变量mRadius来记录当前最大圆的半径,这个半径就取当前View宽高的较小的那一边的一半(保证能画一个完整的圆)。
private static final int DEFAULT_WIDTH = 100;
private static final int DEFAULT_HEIGHT = 100;
private static final float SQRT_3 = (float) Math.sqrt(3);
/**
* the smaller blue circle inside, the radius ratio
*/
private final static float INNER_RADIUS_RATIO = 400f / 1024f;
@ColorInt
private final static int COLOR_YELLOW = 0xFFF8CC5F;
@ColorInt
private final static int COLOR_RED = 0xFFCC5A4C;
@ColorInt
private final static int COLOR_GREEN = 0xFF4C9E66;
@ColorInt
private final static int COLOR_BLUE = 0xFF5A8CEE;
private int[] colors = new int[]{COLOR_RED, COLOR_GREEN, COLOR_YELLOW};
/**
* view width
*/
private int mWidth = DEFAULT_WIDTH;
/**
* view height
*/
private int mHeight = DEFAULT_HEIGHT;
/**
* logo radius
*/
private int mRadius = DEFAULT_WIDTH >> 1;
除此之外,我们还需要A、B、C、D、O四个点的坐标信息,以及画笔Paint和一个Path。
/**
* logo center point O.
*/
private PointF pointO;
private PointF pointA, pointB, pointC, pointD;
private Path itemPath;
private Paint itemPaint;
然后我们在init函数中初始化所有变量。
private void init() {
pointO = new PointF();
pointA = new PointF();
pointB = new PointF();
pointC = new PointF();
pointD = new PointF();
itemPath = new Path();
itemPaint = new Paint();
itemPaint.setColor(colors[0]);
itemPaint.setAntiAlias(true);
itemPaint.setStyle(Paint.Style.FILL_AND_STROKE);
}
我们需要在View的宽、高度改变的时候重置mRadius,所以需要重写onSizeChanged方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(
getDefaultSize(DEFAULT_WIDTH, widthMeasureSpec),
getDefaultSize(DEFAULT_HEIGHT, heightMeasureSpec)
);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mRadius = Math.min(mWidth, mHeight) >> 1;
}
接下来就是重点的绘制了。我们需要重写View的onDraw方法。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//set ponits'postion
float innerRadius = (mRadius >> 1);
pointO.set(mWidth >> 1, mHeight >> 1);
pointA.set(pointO.x - (mRadius >> 2) * SQRT_3, pointO.y + (mRadius >> 2));
pointC.set(pointO.x + innerRadius * SQRT_3, pointO.y - innerRadius);
//绘制其中一块
itemPath.reset();
//设置Path的起点坐标
itemPath.moveTo(pointA.x, pointA.y);
//绘制弧AB
itemPath.arcTo(pointO.x - innerRadius, pointO.y - innerRadius,
pointO.x + innerRadius, pointO.y + innerRadius,
150f, 120f, true);
//绘制线段BC
itemPath.lineTo(pointC.x, pointC.y);
//绘制弧CD
itemPath.arcTo(pointO.x - mRadius, pointO.y - mRadius,
pointO.x + mRadius, pointO.y + mRadius,
-30f, -120f, true);
//绘制线段DA
itemPath.lineTo(pointA.x, pointA.y);
//闭合Path,到这里我们算是把整个单块的路径给描绘出来了。
itemPath.close();
//绘制单个 1/3的图形(红色区域)
itemPaint.setColor(colors[0]);
canvas.drawPath(itemPath, itemPaint);
//逆时针旋转120度,绘制绿色区域
canvas.rotate(-120f, pointO.x, pointO.y);
itemPaint.setColor(colors[1]);
canvas.drawPath(itemPath, itemPaint);
//逆时针继续旋转120度,绘制最后一块(黄色区域)
canvas.rotate(-120f, pointO.x, pointO.y);
itemPaint.setColor(colors[2]);
canvas.drawPath(itemPath, itemPaint);
//绘制中心的蓝色小圆
itemPaint.setColor(COLOR_BLUE);
canvas.drawCircle(pointO.x, pointO.y, mRadius * INNER_RADIUS_RATIO, itemPaint);
}
代码中用到的知识点就是,Path的用法以及Canvas的旋转,注释中我也写的很详细了。关于Path的用法,大家可以自己去查看一下相关API,还是挺简单的,我就不多讲了。值得说一下的就是Path.arcTo函数的参数。
public void arcTo(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, boolean forceMoveTo)
这个arcTo函数其实绘制一段弧线,包括椭圆和圆的都可以,取决于left、top、right、bottom参数,其实就是代表椭圆(圆)的外接矩形的左上角坐标,和右下角坐标。startAngle是开始绘制的弧度,这个弧度是与x轴的夹角,顺时针方向。sweepAngle代表绘制的弧线的度数,也是顺时针方向,如果这个参数是负值(-100f),就是逆时针绘制100度。forceMoveTo,这个参数就比较有意思了,如果设置为true,则path的绘制点就相应的移动到了弧线的末;如果设置为false,则path的绘制点还停留在弧线的额起始点。我们这里明显是要连续绘制的,所以要把这个参数设置成true。如果要设置成false,则还要手动去移动path的绘制点,反而麻烦了许多。因此这里我们的B点和D点其实都不要计算了。
可以给大家看看仅仅绘制一块时的样子,只需要把下面旋转绘制的代码注释掉就可以了。
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.ColorInt;
/**
* @author newcoderzZ
*/
public class ChromeLogoView extends View {
private static final int DEFAULT_WIDTH = 100;
private static final int DEFAULT_HEIGHT = 100;
private static final float SQRT_3 = (float) Math.sqrt(3);
/**
* the smaller blue circle inside, the radius ratio
*/
private final static float INNER_RADIUS_RATIO = 400f / 1024f;
@ColorInt
private final static int COLOR_YELLOW = 0xFFF8CC5F;
@ColorInt
private final static int COLOR_RED = 0xFFCC5A4C;
@ColorInt
private final static int COLOR_GREEN = 0xFF4C9E66;
@ColorInt
private final static int COLOR_BLUE = 0xFF5A8CEE;
private int[] colors = new int[]{COLOR_RED, COLOR_GREEN, COLOR_YELLOW};
/**
* view width
*/
private int mWidth = DEFAULT_WIDTH;
/**
* view height
*/
private int mHeight = DEFAULT_HEIGHT;
/**
* logo radius
*/
private int mRadius = DEFAULT_WIDTH >> 1;
/**
* logo center point O.
*/
private PointF pointO;
private PointF pointA, pointC;
private Path itemPath;
private Paint itemPaint;
public ChromeLogoView(Context context) {
this(context, null);
}
public ChromeLogoView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ChromeLogoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
pointO = new PointF();
pointA = new PointF();
//pointB = new PointF();
pointC = new PointF();
//pointD = new PointF();
itemPath = new Path();
itemPaint = new Paint();
itemPaint.setColor(colors[0]);
itemPaint.setAntiAlias(true);
itemPaint.setStyle(Paint.Style.FILL_AND_STROKE);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(
getDefaultSize(DEFAULT_WIDTH, widthMeasureSpec),
getDefaultSize(DEFAULT_HEIGHT, heightMeasureSpec)
);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mRadius = Math.min(mWidth, mHeight) >> 1;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//set ponits'postion
float innerRadius = (mRadius >> 1);
pointO.set(mWidth >> 1, mHeight >> 1);
pointA.set(pointO.x - (mRadius >> 2) * SQRT_3, pointO.y + (mRadius >> 2));
pointC.set(pointO.x + innerRadius * SQRT_3, pointO.y - innerRadius);
//绘制其中一块
itemPath.reset();
//设置Path的起点坐标
itemPath.moveTo(pointA.x, pointA.y);
//绘制弧AB
itemPath.arcTo(pointO.x - innerRadius, pointO.y - innerRadius,
pointO.x + innerRadius, pointO.y + innerRadius,
150f, 120f, true);
//绘制线段BC
itemPath.lineTo(pointC.x, pointC.y);
//绘制弧CD
itemPath.arcTo(pointO.x - mRadius, pointO.y - mRadius,
pointO.x + mRadius, pointO.y + mRadius,
-30f, -120f, true);
//绘制线段DA
itemPath.lineTo(pointA.x, pointA.y);
//闭合Path,到这里我们算是把整个单块的路径给描绘出来了。
itemPath.close();
//绘制单个 1/3的图形(红色区域)
itemPaint.setColor(colors[0]);
canvas.drawPath(itemPath, itemPaint);
//逆时针旋转120度,绘制绿色区域
canvas.rotate(-120f, pointO.x, pointO.y);
itemPaint.setColor(colors[1]);
canvas.drawPath(itemPath, itemPaint);
//逆时针继续旋转120度,绘制最后一块(黄色区域)
canvas.rotate(-120f, pointO.x, pointO.y);
itemPaint.setColor(colors[2]);
canvas.drawPath(itemPath, itemPaint);
//绘制中心的蓝色小圆
itemPaint.setColor(COLOR_BLUE);
canvas.drawCircle(pointO.x, pointO.y, mRadius * INNER_RADIUS_RATIO, itemPaint);
}
}