一,自定义 View 基础:
1,画笔 Paint
文本相关:
方法 | 描述 |
---|---|
setColor(@ColorInt int color) | 设置画笔颜色 |
setStrokeWidth(float width) | 设置画笔粗细 |
setTextSkewX(float f) | 设置倾斜,负右斜,正为左 |
setARGB(int a, int r, int g, int b) | 设置颜色,a为透明度 |
setTextSize(float textSize) | 设置绘制文字大小 |
setFakeBoldText(boolean fakeBoldText) | 是否粗体 |
setTextAlign(Paint.Align align) | 设置文字对齐方式,LEFT,CENTER,RIGHT |
setUnderlineText(boolean underlineText) | 设置下划线 |
setStyle(Style style) | 设置画笔样式,FILL,STROKE,FILL_AND_STROKE setTypeface(Typeface typeface |
位图相关
方法 | 描述 |
---|---|
setDither(boolean dither) | 设置抖动处理 |
setAlpha(int a) | 设置透明度 |
setAntiAlias(boolean aa) | 是否开启抗锯齿 |
setFilterBitmap() | 是否开启优化Bitmap |
setColorFilter(ColorFilter filter) | 设置颜色过滤 |
setMaskFilter(MaskFilter maskfilter) | 设置滤镜的效果 |
setShader(Shader shader) | 设置图像渐变效果 |
setStrokeJoin(Paint.Join join) | 设置图像结合方式 |
setXfermode(Xfermode xfermode) | 设置图像重叠效果 |
setPathEffect(PathEffect effect) | 设置路径效果 reset() 恢复默认设置 |
2,路径
moveTo() 绘制起始的点
lineTo() 绘制连接的点
close() 形成闭环
arcTo() 弧线路径 参数:生成椭圆的矩形、弧线开始的角度、弧线扫描过的角度、是否强制地将弧线的起始点作为绘制起始位置
**Region 区域:**一块任意形状的封闭图形。使用RegionIterator,用于区域相交填充操作。
3,画布 Canvas
1)清除画布
/**
* 画布清屏
*/
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
2)绘制画布背景
/**
* 绘制画布背景
*/
//绘制画布背景,argb
canvas.drawARGB(99, 255, 0, 255);
canvas.drawRGB(255, 0, 255);
canvas.drawColor(Color.LTGRAY);
3)绘制圆形
/**
* 绘制圆形
*/
//设置画笔的基本属性
//设置画笔颜色
mPaint.setColor(Color.RED);
//设置画笔填充样式
//仅填充内部
mPaint.setStyle(Paint.Style.FILL);
//填充内部和描边
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
//仅填充描边
mPaint.setStyle(Paint.Style.STROKE);
//设置描边宽度,单位px,当填充样式是FILL_AND_STROKE时候有效
mPaint.setStrokeWidth(20);
//使用画布画圆
canvas.drawCircle(200, 200, 150, mPaint);
mPaint.setColor(Color.BLUE);
canvas.drawCircle(200, 200, 130, mPaint);
4)绘制点
/**
* 绘制点
*/
//点的大小
mPaint.setStrokeWidth(20);
//绘制点,x坐标、y坐标
canvas.drawPoint(340, 340, mPaint);
5)绘制直线
/**
* 绘制直线
*/
//直线粗细
mPaint.setStrokeWidth(20);
//绘制直线起点x坐标、起点y坐标、终点x坐标、终点y坐标
canvas.drawLine(360, 360, 660, 660, mPaint);
6)绘制
/**
* 绘制矩形
*/
//矩形的边框粗细
mPaint.setStrokeWidth(5);
//仅填充矩形描边
mPaint.setStyle(Paint.Style.STROKE);
//绘制矩形,RectF是保存float类型的矩形,Rect是保存int类型的矩形,左上右下
mRectF.set(400F, 400F, 450F, 450F);
mRect.set(500, 500, 550, 550);
canvas.drawRect(mRectF, mPaint);
canvas.drawRect(mRect, mPaint);
7)绘制文字
/**
* 绘制文字
* setColor(@ColorInt int color) 设置画笔颜色
* setStrokeWidth(float width) 设置画笔粗细
* setTextSkewX(float f) 设置倾斜,负右斜,正为左
* setARGB(int a, int r, int g, int b) 设置颜色,a为透明度
* setTextSize(float textSize) 设置绘制文字大小
* setFakeBoldText(boolean fakeBoldText) 是否粗体
* setTextAlign(Paint.Align align) 设置文字对齐方式,LEFT,CENTER,RIGHT setUnderlineText(boolean underlineText) 设置下划线
* setStyle(Style style) 设置画笔样式,FILL,STROKE,FILL_AND_STROKE setTypeface(Typeface typeface) 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等
*/
mPaint.setTextSize(90f);
canvas.drawText("Android Stack", 200, 1000, mPaint);
二,绘制文字
准备基础类BaseView,继承View类。
public class BaseView extends View {
static final int LINE_OFFSET = 60;
protected Paint notePaint = new Paint();
protected Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
protected Rect textRect = new Rect();
public BaseView(Context context) {
this(context, null);
}
public BaseView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BaseView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
在 BaseView 里准备了 2 个画笔。
绘制文字的方法canvas.drawText
,需要指定字符串,绘制起始坐标,画笔 paint。
canvas.drawText(text, x, y, textPaint);
起始坐标的 y 值所在横线在后面也称为 baseline 。
获取文字边界
从图中可以看到文字的部分笔画是可以超出边线的。比如开头的j
。
使用 Paint.getTextBounds
方法获取文字的边界。
textPaint.getTextBounds(onShowText, 0, onShowText.length(), textRect); // 获取text边界
绘制上图的部分代码
private void drawBounds(Canvas canvas, float tx, float ty, String onShowText) {
canvas.drawText(onShowText, tx, ty, textPaint); // 写字
textPaint.getTextBounds(onShowText, 0, onShowText.length(), textRect); // 获取text边界
notePaint.setStrokeWidth(3);
notePaint.setColor(Color.parseColor("#EF6C00"));
// 左边线
canvas.drawLine(tx, ty - textRect.height() - LINE_OFFSET, tx, ty + LINE_OFFSET, notePaint);
// 右边线
canvas.drawLine(tx + textRect.width(), ty - textRect.height() - LINE_OFFSET, tx + textRect.width(), ty + LINE_OFFSET, notePaint);
notePaint.setColor(Color.parseColor("#FF0277BD"));
// 上边线
canvas.drawLine(tx - LINE_OFFSET, ty - textRect.height(), tx + textRect.width() + LINE_OFFSET, ty - textRect.height(), notePaint);
notePaint.setColor(Color.parseColor("#00695C"));
// y - baseline
canvas.drawLine(tx - LINE_OFFSET, ty, tx + textRect.width() + LINE_OFFSET, ty, notePaint);
}
获取 text 尺寸信息
需要使用 Paint.FontMetrics
类。它有 5 个属性
属性 | 介绍 |
---|---|
top | 从baseline到最高的文字顶部的最大距离 |
ascent | 单行距(singled spaced)时,baseline上方空间的推荐距离 |
descent | 单行距时,baseline下方空间的推荐距离 |
bottom | baseline到最下方字符的最大距离 |
leading | 多行文字之间推荐使用的额外空间 |
使用 Paint.getFontMetrics()
方法获取 Paint.FontMetrics
。
Paint.FontMetrics fm = textPaint.getFontMetrics();
注意,top
和 ascent
是负值。
绘制上图的方法
private void drawFontMetrics(Canvas canvas, float x, float y, String text) {
canvas.drawText(text, x, y, textPaint);
textPaint.getTextBounds(text, 0, text.length(), textRect);
Paint.FontMetrics fm = textPaint.getFontMetrics();
notePaint.setStrokeWidth(1);
notePaint.setColor(Color.BLACK);
canvas.drawText(String.format(Locale.CHINA, "top:%.2f, bottom:%.2f", fm.top, fm.bottom), x, y + notePaint.getTextSize() * 2.5f, notePaint);
canvas.drawText(String.format(Locale.CHINA, "ascent:%.2f, descent:%.2f, leading:%.2f", fm.ascent, fm.descent, fm.leading), x, y + notePaint.getTextSize() * 4f, notePaint);
notePaint.setColor(Color.parseColor("#FFD84315"));
// fm top线
canvas.drawLine(x - L_BIAS, y + fm.top, x + textRect.width() + L_BIAS, y + fm.top, notePaint);
notePaint.setColor(Color.parseColor("#FF00695C"));
// fm bottom线
canvas.drawLine(x - L_BIAS, y + fm.bottom, x + textRect.width() + L_BIAS, y + fm.bottom, notePaint);
notePaint.setColor(Color.parseColor("#4527A0"));
// fm ascent线
canvas.drawLine(x - L_BIAS, y + fm.ascent, x + textRect.width() + L_BIAS, y + fm.ascent, notePaint);
notePaint.setColor(Color.parseColor("#0E0822"));
// fm descent线
canvas.drawLine(x - L_BIAS, y + fm.descent, x + textRect.width() + L_BIAS, y + fm.descent, notePaint);
}
对齐文字中间
有了上面的基础,我们可以很快知道如何实现文字“居中对齐”。将文字的水平中心线或竖直中心线对齐到某个点。
例如绘制一个平面坐标系
绘制出2个坐标轴,然后绘制上刻度,并写上数值。 数值文字对应到刻度相应的位置上。我们主要使用 Paint.getTextBounds
获取到文字的尺寸信息,再计算出合适的位置。
private void drawChart(Canvas canvas) {
final float x0 = 100;
final float y0 = 300; // 原点在画布上的坐标
final float halfLine = 5; // 刻度长度的一半
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(20);
notePaint.setStrokeWidth(2);
canvas.drawLine(x0, y0, x0 + 500, y0, notePaint); // 绘制横轴
canvas.drawLine(x0, y0, x0, y0 - 290, notePaint); // 绘制纵轴
// 绘制横轴刻度和数字
for (int i = 1; i <= 4; i++) {
int step = i * 100;
String n = String.valueOf(step);
float x = x0 + step;
canvas.drawLine(x, y0 - halfLine, x, y0 + halfLine, notePaint); // 刻度
// 文字对齐
textPaint.getTextBounds(n, 0, n.length(), textRect);
canvas.drawText(n, x - textRect.width() / 2f, y0 + textRect.height() + halfLine, textPaint);
}
// 绘制纵轴刻度和数字
for (int i = 1; i <= 2; i++) {
int step = i * 100;
String n = String.valueOf(step);
float y = y0 - step;
canvas.drawLine(x0 - halfLine, y, x0 + halfLine, y, notePaint); // 刻度
// 文字对齐
textPaint.getTextBounds(n, 0, n.length(), textRect);
canvas.drawText(n, x0 - halfLine * 2 - textRect.width(), y + textRect.height() / 2f, textPaint);
}
// 绘制原点 0
String origin = "0";
textPaint.getTextBounds(origin, 0, origin.length(), textRect);
canvas.drawText(origin, x0 - textRect.width(), y0 + textRect.height(), textPaint);
}
三,裁剪画布 canvas clip path
使用 canvas.clipPath(path)
方法。
clipPath
方法相当于按给定的路径裁剪画布。 根据这个用途,我们可以做出圆角图片的效果。
圆角图片示例
圆角图片是一种常见的设计。Android 官方库中并没有直接显示圆角图片的方法。 如果是纯色,渐变色的背景,我们可以用 shape 的 corners 来实现圆角。 这里是利用 clipPath
在super.onDraw(canvas)
之前将画布裁出圆角。需要传入一个 Path 来指定裁剪范围。
新建一个 RoundImageView
类,继承自 AppCompatImageView
。
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Path;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatImageView;
public class RoundImageView extends AppCompatImageView {
private float vw, vh;
private Path path = new Path();
private float topLeftR;
private float topRightR;
private float botLeftR;
private float botRightR;
public RoundImageView(Context context) {
this(context, null);
}
public RoundImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundImageView);
topLeftR = typedArray.getDimensionPixelSize(R.styleable.RoundImageView_topLeftR, 0);
topRightR = typedArray.getDimensionPixelSize(R.styleable.RoundImageView_topRightR, 0);
botLeftR = typedArray.getDimensionPixelSize(R.styleable.RoundImageView_botLeftR, 0);
botRightR = typedArray.getDimensionPixelSize(R.styleable.RoundImageView_botRightR, 0);
typedArray.recycle();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
vw = getWidth();
vh = getHeight();
}
@Override
protected void onDraw(Canvas canvas) {
path.reset();
if (vw > topLeftR && vh > topRightR) {
path.moveTo(topLeftR, 0);
path.lineTo(vw - topRightR, 0);
path.quadTo(vw, 0, vw, topRightR);
path.lineTo(vw, vh - botRightR);
path.quadTo(vw, vh, vw - botRightR, vh);
path.lineTo(botLeftR, vh);
path.quadTo(0, vh, 0, vh - botLeftR);
path.lineTo(0, topLeftR);
path.quadTo(0, 0, topLeftR, 0);
path.close();
canvas.clipPath(path);
}
super.onDraw(canvas);
}
}
里面使用了 TypedArray
,需要新增 declare-styleable
资源。
<resources>
<declare-styleable name="RoundImageView">
<attr name="topLeftR" format="dimension" />
<attr name="topRightR" format="dimension" />
<attr name="botLeftR" format="dimension" />
<attr name="botRightR" format="dimension" />
</declare-styleable>
</resources>
使用这个类。在 layout 中直接使用。分别定义 4 个圆角半径参数。给 src
设置纯色。 如果给定图片,图片内容接触到 view 端点时,才会看得出有圆角效果。
<com.rustfisher.tutorial2020.customview.view.RoundImageView
style="@style/RoundIvCube"
app:botLeftR="8dp"
app:botRightR="8dp"
app:topLeftR="8dp"
app:topRightR="8dp" />
<com.rustfisher.tutorial2020.customview.view.RoundImageView
style="@style/RoundIvCube"
app:botLeftR="25dp"
app:botRightR="25dp"
app:topLeftR="25dp"
app:topRightR="25dp" />
<com.rustfisher.tutorial2020.customview.view.RoundImageView
style="@style/RoundIvCube"
app:botLeftR="16dp" />
<com.rustfisher.tutorial2020.customview.view.RoundImageView
style="@style/RoundIvCube"
app:topLeftR="16dp" />
<com.rustfisher.tutorial2020.customview.view.RoundImageView
style="@style/RoundIvCube"
app:botRightR="16dp"
app:topLeftR="16dp" />
style定义
<style name="RoundIvCube">
<item name="android:layout_width">50dp</item>
<item name="android:layout_height">50dp</item>
<item name="android:src">#303F9F</item>
<item name="android:layout_margin">8dp</item>
</style>
运行结果
可以看到各个图形的圆角。但是我们也能发现有锯齿。这个裁剪方法没能做到光滑圆润的圆角。